Browse Source

动画缓存计算样式

master
xh 5 days ago
parent
commit
e90d525a89
  1. 2
      web/src/views/HandDevice/Home/components/constants/map.constants.ts
  2. 183
      web/src/views/HandDevice/Home/components/services/animation.service.ts
  3. 31
      web/src/views/HandDevice/Home/components/services/marker.service.ts

2
web/src/views/HandDevice/Home/components/constants/map.constants.ts

@ -73,7 +73,7 @@ export const ANIMATION_CONFIG = {
/**
*
*/
targetFPS: 25,
targetFPS: 32,
/**
*
*/

183
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<number, Stroke> = {}
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<VectorSource> | 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<string> = new Set()
private statusColorStyleMap: Map<string, Style[]> = 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
}

31
web/src/views/HandDevice/Home/components/services/marker.service.ts

@ -21,12 +21,12 @@ export class MarkerService {
private map: OpenLayersMap | null = null
markerLayer: VectorLayer<Cluster> | 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<Point>[] = []
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<Feature<Point>>
const clusterSource = this.markerLayer?.getSource() as Cluster<Feature<Point>>
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<Cluster> | null = null
console.log('聚合图层')
const vectorSource = new VectorSource()
const clusterSource = new Cluster({
source: this.vectorSource,
source: vectorSource,
distance: 20 // 单位是像素
})

Loading…
Cancel
Save