diff --git a/web/src/views/HandDevice/Home/components/constants/map.constants.ts b/web/src/views/HandDevice/Home/components/constants/map.constants.ts index 6d50b4e..fcaed83 100644 --- a/web/src/views/HandDevice/Home/components/constants/map.constants.ts +++ b/web/src/views/HandDevice/Home/components/constants/map.constants.ts @@ -73,7 +73,7 @@ export const ANIMATION_CONFIG = { /** * 目标帧率 */ - targetFPS: 25, + targetFPS: 32, /** * 聚合图层阈值 */ diff --git a/web/src/views/HandDevice/Home/components/services/animation.service.ts b/web/src/views/HandDevice/Home/components/services/animation.service.ts index b31a432..64de4bb 100644 --- a/web/src/views/HandDevice/Home/components/services/animation.service.ts +++ b/web/src/views/HandDevice/Home/components/services/animation.service.ts @@ -1,7 +1,7 @@ /** * 动画服务类 */ -import type { Map } from 'ol' +import type { Map as OpenLayersMap } from 'ol' import { Vector as VectorLayer } from 'ol/layer' import { Vector as VectorSource } from 'ol/source' @@ -13,18 +13,51 @@ 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: Map | null = null - constructor(map: Map) { + private map: OpenLayersMap | null = null + constructor(map: OpenLayersMap) { this.map = map this.createRippleLayer() } - private transparentFill: Fill = new Fill({ - color: 'transparent' - }) + private statusColor: Set = new Set() + private statusColorStyleMap: Map = new Map() + + private globalAnimationStartTime: number = Date.now() /** * 创建波纹图层 */ @@ -35,49 +68,60 @@ export class AnimationService { source: source, zIndex: 1, style: (feature) => { - const startTime = feature.get('animationStart') + // const startTime = feature.get('animationStart') + // console.time('animation style') 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) // 动态调整线宽 - }) - }) - }) - ) - } + 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) { @@ -86,14 +130,21 @@ export class AnimationService { } clear() { + console.time('animationService clear') + const source = this.rippleLayer?.getSource() source?.clear(true) + console.timeEnd('animationService clear') } addAll(markers: MarkerData[]) { - this.clear() - const source = this.rippleLayer?.getSource() + console.time('animationService addAll') + this.statusColor.clear() + this.statusColorStyleMap.clear() + // this.clear() + // const source = this.rippleLayer?.getSource() + const source = new VectorSource() const features: Feature[] = [] // 为每个标记添加波纹效果 @@ -104,18 +155,26 @@ export class AnimationService { }) // 设置动画开始时间 - feature.set('animationStart', Date.now()) + // 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) } @@ -128,6 +187,32 @@ export class AnimationService { 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 } diff --git a/web/src/views/HandDevice/Home/components/services/marker.service.ts b/web/src/views/HandDevice/Home/components/services/marker.service.ts index fc2cbc1..1904710 100644 --- a/web/src/views/HandDevice/Home/components/services/marker.service.ts +++ b/web/src/views/HandDevice/Home/components/services/marker.service.ts @@ -21,12 +21,12 @@ export class MarkerService { private map: OpenLayersMap | null = null markerLayer: VectorLayer | null = null - private vectorSource: VectorSource + // private vectorSource: VectorSource private animationService: AnimationService | null = null constructor(map: OpenLayersMap) { this.map = map - this.vectorSource = new VectorSource() + this.animationService = new AnimationService(map) this.createMarkerLayer() } @@ -42,14 +42,6 @@ export class MarkerService { * 更新标记数据 */ async updateData(markers: MarkerData[]) { - console.time('animationService clear') - this.animationService?.clear() - console.timeEnd('animationService clear') - await nextTick() - console.time('vectorSource clear') - this.vectorSource.clear(true) - console.timeEnd('vectorSource clear') - await nextTick() console.time('create features') const features: Feature[] = [] markers.forEach((marker) => { @@ -65,16 +57,19 @@ export class MarkerService { console.timeEnd('create features') await nextTick() console.time('add features') - this.vectorSource.addFeatures(features) + const vectorSource = new VectorSource() + + vectorSource.addFeatures(features) + + this.markerLayer?.getSource()?.setSource(vectorSource) console.timeEnd('add features') this.getSinglePointsInView() } setClusterDistance = debounce(() => { if (!this.map) return - const clusterLayer = this.markerLayer - if (!clusterLayer) return - const clusterSource = clusterLayer.getSource() as Cluster> + + const clusterSource = this.markerLayer?.getSource() as Cluster> if (!clusterSource) return const zoom = this.map.getView().getZoom() || 0 let distance = 2 @@ -91,7 +86,7 @@ export class MarkerService { } console.log('zoom', zoom, 'distance', distance) clusterSource?.setDistance(distance) - }, 200) + }, 20) /** * 获取视图内聚合图层中没有聚合的单个点 * @returns {Array} 单个点要素数组 @@ -126,7 +121,7 @@ export class MarkerService { } }) this.animationService?.addAll(singlePoints || []) - }, 300) + }, 50) /** * 从props创建markerLayer(内部方法) @@ -137,9 +132,9 @@ export class MarkerService { let newLayer: VectorLayer | null = null console.log('聚合图层') - + const vectorSource = new VectorSource() const clusterSource = new Cluster({ - source: this.vectorSource, + source: vectorSource, distance: 20 // 单位是像素 })