OpenLayers 散点涟漪效果
本章将介绍如何在 OpenLayers 中实现散点的涟漪动画效果。
示例
代码实现
vue
<template>
<div class="h-screen">
<div id="map" class="w-full h-full"></div>
</div>
</template>
<script setup>
import { onMounted, ref } from "vue";
import "ol/ol.css";
import Map from "ol/Map";
import View from "ol/View";
import TileLayer from "ol/layer/Tile";
import XYZ from "ol/source/XYZ";
import { fromLonLat } from "ol/proj";
import Feature from "ol/Feature";
import Point from "ol/geom/Point";
import { Vector as VectorLayer } from "ol/layer";
import { Vector as VectorSource } from "ol/source";
import { Style, Circle, Fill, Stroke } from "ol/style";
onMounted(() => {
// 初始化地图
const map = new Map({
target: "map",
view: new View({
center: fromLonLat([116.4074, 39.9042]),
zoom: 12,
}),
});
// 添加天地图图层
const tdtLayer = new TileLayer({
source: new XYZ({
url: "http://t0.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=d32c6748c80f81a44acd8633cea41dfd",
}),
});
map.addLayer(tdtLayer);
// 创建矢量图层
const vectorSource = new VectorSource();
const vectorLayer = new VectorLayer({
source: vectorSource,
});
map.addLayer(vectorLayer);
// 添加点位数据
const points = [
[116.4074, 39.9042],
[116.4174, 39.9142],
[116.3974, 39.8942],
];
points.forEach((point) => {
const feature = new Feature({
geometry: new Point(fromLonLat(point)),
});
// 自定义样式
feature.setStyle(
new Style({
image: new Circle({
radius: 12,
fill: new Fill({
color: "rgba(33, 150, 243, 0.8)",
}),
stroke: new Stroke({
color: "rgba(33, 150, 243, 0.6)",
width: 2,
}),
}),
})
);
vectorSource.addFeature(feature);
});
// 添加涟漪动画效果
points.forEach((point) => {
const rippleFeature = new Feature({
geometry: new Point(fromLonLat(point)),
});
let phase = 0;
const animate = () => {
// 确保半径始终为正数
const radius = Math.max(12 + 30 * Math.sin(phase), 1);
const opacity = Math.max(0.8 - 0.6 * Math.sin(phase), 0);
rippleFeature.setStyle(
new Style({
image: new Circle({
radius: radius,
fill: new Fill({
color: `rgba(33, 150, 243, ${opacity})`,
}),
}),
})
);
phase += 0.1;
if (phase > Math.PI * 2) {
phase = 0;
}
requestAnimationFrame(animate);
};
animate();
vectorSource.addFeature(rippleFeature);
});
});
</script>
<style scoped>
.h-screen {
height: 24rem;
}
.w-full {
width: 100%;
}
.h-full {
height: 100%;
}
.ripple-marker {
width: 24px;
height: 24px;
border-radius: 50%;
background: rgba(33, 150, 243, 0.8);
position: relative;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.3), 0 0 10px rgba(33, 150, 243, 0.5);
}
.ripple-marker::before,
.ripple-marker::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 100%;
height: 100%;
border-radius: 50%;
background: rgba(33, 150, 243, 0.6);
transform: translate(-50%, -50%);
}
.ripple-marker::before {
animation: ripple 2s infinite ease-out;
}
.ripple-marker::after {
animation: ripple 2s infinite ease-out 0.5s;
}
@keyframes ripple {
0% {
transform: translate(-50%, -50%) scale(1);
opacity: 0.8;
}
50% {
opacity: 0.4;
}
100% {
transform: translate(-50%, -50%) scale(3.5);
opacity: 0;
}
}
</style>