• 十八、openlayers官网示例Custom Animation解析——地图上添加自定义动画


    官网demo地址:

    Custom Animation

    这篇讲的是如何在地图上添加自定义动画

    先来看一下一次动画的完整流程,每隔一秒地图上增加一个蓝色圈,蓝色圈上增加一个半径慢慢变大,透明度慢慢变小的红色圈,红色圈执行3秒,然后消失,下一个蓝色圈开始出现。

    那代码是如何执行的呢?其实核心就这个几个函数。

    刚开始有一个一秒执行一次的定时器,绑定addRandomFeature函数,addRandomFeature函数每隔一秒往vectorLayer图层上添加一个feature绘制蓝色圈,每添加一次就会触发addfeature事件,事件里调用flash函数,flash函数触发animate函数完成红色圈的动画。

    1. window.setInterval(this.addRandomFeature, 1000);
    2. this.vectorLayer.getSource().on("addfeature", (e) => {
    3. this.flash(e.feature);
    4. });
    5. addRandomFeature() {
    6. this.vectorLayer.getSource().addFeature(feature);
    7. }
    8. flash(feature) {
    9. let listenerKey = this.vectorLayer.on("postrender", animate);
    10. function animate(event) {
    11. }
    12. }

    蓝色圈的产生比较好理解,其中fromLonLat用于转换坐标。

    1. addRandomFeature() {
    2. const x = Math.random() * 360 - 180;
    3. const y = Math.random() * 170 - 85;
    4. //fromLonLat转换坐标
    5. const geom = new Point(fromLonLat([x, y]));
    6. const feature = new Feature(geom);
    7. this.vectorLayer.getSource().addFeature(feature);
    8. },

    重点是红色圈的产生函数。

    首先定义一个动画执行的时间,克隆一个几何形状,并保证每一次添加蓝色圈后都绑定一次annimate事件。

    1. const duration = 3000;
    2. const flashGeom = feature.getGeometry().clone();
    3. let listenerKey = this.vectorLayer.on("postrender", animate);

     const start = Date.now();记录动画开始的时间,红色圈的动画执行是3秒 ,在annimate函数里实时获取动画执行的过程中的每一帧的当前时间,用当前时间减去初始的时间,算出已经过去的时间elapsed,当elapsed时间大于等于3000时终止函数。

    1. const start = Date.now();
    2. function animate(event) {
    3. const frameState = event.frameState;
    4. const elapsed = frameState.time - start;
    5. console.log("elapsed", elapsed);
    6. if (elapsed >= duration) {
    7. // 移除事件监听
    8. unByKey(listenerKey);
    9. return;
    10. }
    11. }

    我在mounted里加了一个settimeout函数清除定时器,让地图上只增加一个蓝色圈便停止,便于观察一次动画代码的执行过程。

    1. let time = window.setInterval(this.addRandomFeature, 1000);
    2. setTimeout(() => {
    3. clearInterval(time);
    4. }, 1000);

     打印下elapsed的值,elapsed增加,当elapsed>=3000,动画结束。

    然后就是绘制红色圈的过程,其中缓动函数easeOut实现了动画的平滑过渡,类似css动画属性中的animation-timing-function。

    1. // 获取矢量上下文,以便绘制矢量图形
    2. const vectorContext = getVectorContext(event);
    3. // 计算已过去时间占总时间的比例 0-1
    4. const elapsedRatio = elapsed / duration;
    5. //动画作用的范围
    6. const scope = 25;
    7. // 半径大小 开始时为5,结束时为scope+5 easeOut更改运动曲线
    8. const radius = easeOut(elapsedRatio) * scope + 5;
    9. // 透明度从1逐渐减小到0
    10. const opacity = easeOut(1 - elapsedRatio);
    11. const style = new Style({
    12. image: new CircleStyle({
    13. radius: radius,
    14. stroke: new Stroke({
    15. color: "rgba(255, 0, 0, " + opacity + ")",
    16. width: 0.25 + opacity,
    17. }),
    18. }),
    19. });
    20. vectorContext.setStyle(style);
    21. // 绘制几何图形
    22. vectorContext.drawGeometry(flashGeom);
    23. // 重新渲染地图
    24. this_.map.render();

    完整代码:

    1. <template>
    2. <div class="box">
    3. <h1>Custom Animation</h1>
    4. <div id="map"></div>
    5. </div>
    6. </template>
    7. <script>
    8. import Feature from "ol/Feature.js";
    9. import Map from "ol/Map.js";
    10. import Point from "ol/geom/Point.js";
    11. import View from "ol/View.js";
    12. import { Circle as CircleStyle, Stroke, Style } from "ol/style.js";
    13. import { OSM, Vector as VectorSource } from "ol/source.js";
    14. import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer.js";
    15. import { easeOut } from "ol/easing.js";
    16. import { fromLonLat } from "ol/proj.js";
    17. import { getVectorContext } from "ol/render.js";
    18. import { unByKey } from "ol/Observable.js";
    19. export default {
    20. name: "",
    21. components: {},
    22. data() {
    23. return {
    24. map: null,
    25. vectorLayer: null,
    26. tileLayer: null,
    27. };
    28. },
    29. computed: {},
    30. created() {},
    31. mounted() {
    32. this.initMap();
    33. this.addVectorLayer();
    34. let time = window.setInterval(this.addRandomFeature, 1000);
    35. // setTimeout(() => {
    36. // clearInterval(time);
    37. // }, 1000);
    38. this.vectorLayer.getSource().on("addfeature", (e) => {
    39. this.flash(e.feature);
    40. });
    41. },
    42. methods: {
    43. initMap() {
    44. this.tileLayer = new TileLayer({
    45. source: new OSM({
    46. wrapX: false,
    47. }),
    48. });
    49. this.map = new Map({
    50. layers: [this.tileLayer],
    51. target: "map",
    52. view: new View({
    53. center: [0, 0],
    54. zoom: 1,
    55. multiWorld: true,
    56. }),
    57. });
    58. },
    59. addVectorLayer() {
    60. const source = new VectorSource({
    61. wrapX: false,
    62. });
    63. this.vectorLayer = new VectorLayer({
    64. source: source,
    65. });
    66. this.map.addLayer(this.vectorLayer);
    67. },
    68. addRandomFeature() {
    69. const x = Math.random() * 360 - 180;
    70. const y = Math.random() * 170 - 85;
    71. //fromLonLat转换坐标
    72. const geom = new Point(fromLonLat([x, y]));
    73. const feature = new Feature(geom);
    74. this.vectorLayer.getSource().addFeature(feature);
    75. },
    76. flash(feature) {
    77. // 动画持续时间为3000毫秒(3秒)
    78. const duration = 3000;
    79. // 克隆特征的几何形状
    80. const flashGeom = feature.getGeometry().clone();
    81. // 注册一个在每次地图渲染后触发的动画函数
    82. let listenerKey = this.vectorLayer.on("postrender", animate);
    83. let this_ = this;
    84. // 记录动画开始的时间
    85. const start = Date.now();
    86. function animate(event) {
    87. // 获取当前帧的状态
    88. const frameState = event.frameState;
    89. // 计算已过去的时间
    90. const elapsed = frameState.time - start;
    91. // console.log("elapsed",elapsed);
    92. // 如果动画时间超过了设定的持续时间
    93. if (elapsed >= duration) {
    94. // 移除事件监听
    95. unByKey(listenerKey);
    96. return;
    97. }
    98. // 获取矢量上下文,以便绘制矢量图形
    99. const vectorContext = getVectorContext(event);
    100. // 计算已过去时间占总时间的比例 0-1
    101. const elapsedRatio = elapsed / duration;
    102. //动画作用的范围
    103. const scope = 25;
    104. // 半径大小 开始时为5,结束时为scope+5 easeOut更改运动曲线
    105. const radius = easeOut(elapsedRatio) * scope + 5;
    106. // 透明度从1逐渐减小到0
    107. const opacity = easeOut(1 - elapsedRatio);
    108. const style = new Style({
    109. image: new CircleStyle({
    110. radius: radius,
    111. stroke: new Stroke({
    112. color: "rgba(255, 0, 0, " + opacity + ")",
    113. width: 0.25 + opacity,
    114. }),
    115. }),
    116. });
    117. vectorContext.setStyle(style);
    118. // 绘制几何图形
    119. vectorContext.drawGeometry(flashGeom);
    120. // 重新渲染地图
    121. this_.map.render();
    122. }
    123. },
    124. },
    125. };
    126. </script>
    127. <style lang="scss" scoped>
    128. #map {
    129. width: 100%;
    130. height: 500px;
    131. }
    132. .box {
    133. height: 100%;
    134. }
    135. </style>

  • 相关阅读:
    刷题笔记(第一天)
    考华为认证有没有用,哪里可以考?
    Linux 内核(Kernel)组成分析
    jQuery【事件】
    mybatis中判断传入的数组与集合是否为空+mybatis中Foreach的使用详解
    OdeInt与GPU
    Linux教程||Linux 系统启动过程
    力扣(leetcode)第485题最大连续1的个数(Python)
    科学计算三维可视化笔记(第五周 交互界面)
    Oracle数据库---JDBC连接
  • 原文地址:https://blog.csdn.net/aaa_div/article/details/139008224