/** * 动画服务类 */ import type { Map as OpenLayersMap } 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' // 全局复用:透明填充 const TRANSPARENT_FILL = new Fill({ color: 'transparent' }) // 预生成 Stroke 缓存(按波纹层级) const STROKE_CACHE: Record = {} for (let i = 0; i < ANIMATION_CONFIG.rippleCount; i++) { const width = Math.max(1, 3 - i * 0.4) STROKE_CACHE[i] = new Stroke({ width }) } // 预生成 Style 模板(避免每帧 new) const createRippleStyle = ( radius: number, stroke: Stroke, opacity: number, color: string ): Style => { // 注意:color 必须带 alpha,如 #ff0000aa const strokeColor = color.slice(0, 7) + Math.floor(opacity * 255) .toString(16) .padStart(2, '0') stroke.setColor(strokeColor) return new Style({ image: new Circle({ radius, fill: TRANSPARENT_FILL, stroke }) }) } export class AnimationService { private rippleLayer: VectorLayer | null = null private animationTimer: number | null = null private map: OpenLayersMap | null = null constructor(map: OpenLayersMap) { this.map = map this.createRippleLayer() } private statusColor: Set = new Set() private statusColorStyleMap: Map = new Map() private globalAnimationStartTime: number = Date.now() /** * 创建波纹图层 */ private createRippleLayer() { const source = new VectorSource() this.rippleLayer = new VectorLayer({ source: source, zIndex: 1, style: (feature) => { // const startTime = feature.get('animationStart') // console.time('animation style') const color = feature.get('rippleColor') const styles = this.statusColorStyleMap.get(color) || [] if (styles.length === 0) { return [] } // console.timeEnd('animation style') return styles // const elapsed = (Date.now() - this.globalAnimationStartTime) / 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) // 动态调整线宽 // // }) // // }) // // }) // // ) // // / 复用 Stroke,动态改颜色 // const style = createRippleStyle(radius, STROKE_CACHE[i], opacity, color) // styles.push(style) // } // } // return styles } }) if (this.map) { this.map?.addLayer(this.rippleLayer) } } clear() { console.time('animationService clear') const source = this.rippleLayer?.getSource() source?.clear(true) console.timeEnd('animationService clear') } addAll(markers: MarkerData[]) { console.time('animationService addAll') this.statusColor.clear() this.statusColorStyleMap.clear() // this.clear() // const source = this.rippleLayer?.getSource() const source = new VectorSource() 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) if (marker.statusColor) { this.statusColor.add(marker.statusColor) } }) source?.addFeatures(features) this.rippleLayer?.setSource(source) console.timeEnd('animationService addAll') } show() { this.globalAnimationStartTime = Date.now() // ✅ 重置动画起点 this.rippleLayer?.setVisible(true) this.startAnimation() } hide() { console.log('动画暂停') 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) { const elapsed = (Date.now() - this.globalAnimationStartTime) / 1000 // 秒 for (const color of this.statusColor) { // 创建多个波纹圈 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 style = createRippleStyle(radius, STROKE_CACHE[i], opacity, color) styles.push(style) } } this.statusColorStyleMap.set(color, styles) } 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 } }