十八、openlayers官网示例Custom Animation解析——地图上添加自定义动画、圆圈涟漪效果

官网demo地址:

Custom Animation

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

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

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

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

window.setInterval(this.addRandomFeature, 1000);
this.vectorLayer.getSource().on("addfeature", (e) => {
      this.flash(e.feature);
 });
addRandomFeature() {
      this.vectorLayer.getSource().addFeature(feature);
}
flash(feature) {
    let listenerKey = this.vectorLayer.on("postrender", animate);
    function animate(event) {

    }

}

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

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

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

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

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

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


const start = Date.now();
function animate(event) {
      const frameState = event.frameState;
      const elapsed = frameState.time - start;
      console.log("elapsed", elapsed);
      if (elapsed >= duration) {
         // 移除事件监听
         unByKey(listenerKey);
         return;
       }
}

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

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

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

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

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

完整代码:

<template>
  <div class="box">
    <h1>Custom Animation</h1>
    <div id="map"></div>
  </div>
</template>

<script>
import Feature from "ol/Feature.js";
import Map from "ol/Map.js";
import Point from "ol/geom/Point.js";
import View from "ol/View.js";
import { Circle as CircleStyle, Stroke, Style } from "ol/style.js";
import { OSM, Vector as VectorSource } from "ol/source.js";
import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer.js";
import { easeOut } from "ol/easing.js";
import { fromLonLat } from "ol/proj.js";
import { getVectorContext } from "ol/render.js";
import { unByKey } from "ol/Observable.js";
export default {
  name: "",
  components: {},
  data() {
    return {
      map: null,
      vectorLayer: null,
      tileLayer: null,
    };
  },
  computed: {},
  created() {},
  mounted() {
    this.initMap();
    this.addVectorLayer();
    let time = window.setInterval(this.addRandomFeature, 1000);
    // setTimeout(() => {
    //   clearInterval(time);
    // }, 1000);
    this.vectorLayer.getSource().on("addfeature", (e) => {
      this.flash(e.feature);
    });
  },
  methods: {
    initMap() {
      this.tileLayer = new TileLayer({
        source: new OSM({
          wrapX: false,
        }),
      });
      this.map = new Map({
        layers: [this.tileLayer],
        target: "map",
        view: new View({
          center: [0, 0],
          zoom: 1,
          multiWorld: true,
        }),
      });
    },
    addVectorLayer() {
      const source = new VectorSource({
        wrapX: false,
      });
      this.vectorLayer = new VectorLayer({
        source: source,
      });
      this.map.addLayer(this.vectorLayer);
    },
    addRandomFeature() {
      const x = Math.random() * 360 - 180;
      const y = Math.random() * 170 - 85;
      //fromLonLat转换坐标
      const geom = new Point(fromLonLat([x, y]));
      const feature = new Feature(geom);
      this.vectorLayer.getSource().addFeature(feature);
    },
    flash(feature) {
      // 动画持续时间为3000毫秒(3秒)
      const duration = 3000;
      // 克隆特征的几何形状
      const flashGeom = feature.getGeometry().clone();
      // 注册一个在每次地图渲染后触发的动画函数
      let listenerKey = this.vectorLayer.on("postrender", animate);
      let this_ = this;
      // 记录动画开始的时间
      const start = Date.now();
      function animate(event) {
        // 获取当前帧的状态
        const frameState = event.frameState;
        // 计算已过去的时间
        const elapsed = frameState.time - start;
        // console.log("elapsed",elapsed);
        // 如果动画时间超过了设定的持续时间
        if (elapsed >= duration) {
          // 移除事件监听
          unByKey(listenerKey);
          return;
        }
        // 获取矢量上下文,以便绘制矢量图形
        const vectorContext = getVectorContext(event);
        // 计算已过去时间占总时间的比例 0-1
        const elapsedRatio = elapsed / duration;
        //动画作用的范围
        const scope = 25;
        // 半径大小   开始时为5,结束时为scope+5 easeOut更改运动曲线
        const radius = easeOut(elapsedRatio) * scope + 5;
        // 透明度从1逐渐减小到0
        const opacity = easeOut(1 - elapsedRatio);
        const style = new Style({
          image: new CircleStyle({
            radius: radius,
            stroke: new Stroke({
              color: "rgba(255, 0, 0, " + opacity + ")",
              width: 0.25 + opacity,
            }),
          }),
        });
        vectorContext.setStyle(style);
        // 绘制几何图形
        vectorContext.drawGeometry(flashGeom);
        // 重新渲染地图
        this_.map.render();
      }
    },
  },
};
</script>

<style lang="scss" scoped>
#map {
  width: 100%;
  height: 500px;
}
.box {
  height: 100%;
}
</style>

  • 9
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值