官网demo地址:

这篇讲的是如何在地图上添加自定义动画
先来看一下一次动画的完整流程,每隔一秒地图上增加一个蓝色圈,蓝色圈上增加一个半径慢慢变大,透明度慢慢变小的红色圈,红色圈执行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>
-