官网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>