/** * 动画服务类 */ import type { Map } from 'ol' import { Vector as VectorLayer } from 'ol/layer' import { Vector as VectorSource } from 'ol/source' import { Feature } from 'ol' import { Point } from 'ol/geom' import { Style, Circle, Fill, Stroke } from 'ol/style' import { fromLonLat } from 'ol/proj' import type { MarkerData } from '../types/map.types' import { ANIMATION_CONFIG } from '../constants/map.constants' export class AnimationService { private rippleLayer: VectorLayer | null = null private animationTimer: number | null = null private map: Map | null = null constructor(map: Map) { this.map = map this.createRippleLayer() } private transparentFill: Fill = new Fill({ color: 'transparent' }) /** * 创建波纹图层 */ private createRippleLayer() { const source = new VectorSource() this.rippleLayer = new VectorLayer({ source: source, zIndex: 1, style: (feature) => { const startTime = feature.get('animationStart') const color = feature.get('rippleColor') const elapsed = (Date.now() - startTime) / 1000 // 秒 // 创建多个波纹圈 const styles: Style[] = [] for (let i = 0; i < ANIMATION_CONFIG.rippleCount; i++) { const phase = (elapsed + i * ANIMATION_CONFIG.phaseOffset) % ANIMATION_CONFIG.duration const progress = phase / ANIMATION_CONFIG.duration // 0-1的进度 // 使用缓动函数使动画更平滑 const easeProgress = 1 - Math.pow(1 - progress, 3) // ease-out cubic // 计算半径和透明度 const radius = ANIMATION_CONFIG.minRadius + easeProgress * (ANIMATION_CONFIG.maxRadius - ANIMATION_CONFIG.minRadius) const opacity = Math.max(0, 1 - easeProgress) // 1到0的透明度 if (opacity > ANIMATION_CONFIG.minOpacity) { // 计算颜色透明度 const alpha = Math.floor(opacity * 255) .toString(16) .padStart(2, '0') const strokeColor = color + alpha styles.push( new Style({ image: new Circle({ radius: radius, fill: this.transparentFill, stroke: new Stroke({ color: strokeColor, width: Math.max(1, 3 - i * 0.4) // 动态调整线宽 }) }) }) ) } } return styles } }) if (this.map) { this.map?.addLayer(this.rippleLayer) } } clear() { const source = this.rippleLayer?.getSource() source?.clear(true) } addAll(markers: MarkerData[]) { this.clear() const source = this.rippleLayer?.getSource() const features: Feature[] = [] // 为每个标记添加波纹效果 markers.forEach((marker) => { const feature = new Feature({ geometry: new Point(fromLonLat(marker.coordinates)), markerData: marker }) // 设置动画开始时间 feature.set('animationStart', Date.now()) feature.set('rippleColor', marker.statusColor) features.push(feature) }) source?.addFeatures(features) } show() { this.rippleLayer?.setVisible(true) this.startAnimation() } hide() { this.stopAnimation() this.rippleLayer?.setVisible(false) } /** * 启动波纹动画 */ startAnimation(): void { let lastUpdateTime = 0 const frameInterval = 1000 / ANIMATION_CONFIG.targetFPS // 帧间隔 const animateRipples = (currentTime: number) => { if (this.rippleLayer && currentTime - lastUpdateTime >= frameInterval) { this.rippleLayer.getSource()?.changed() lastUpdateTime = currentTime } this.animationTimer = requestAnimationFrame(animateRipples) } animateRipples(0) } /** * 停止波纹动画 */ stopAnimation(): void { if (this.animationTimer) { cancelAnimationFrame(this.animationTimer) this.animationTimer = null } } /** * 销毁动画服务 */ destroy(): void { this.stopAnimation() if (this.rippleLayer && this.map) { this.map?.removeLayer(this.rippleLayer) } this.rippleLayer = null this.map = null } }