优化趋势图高刷动画

This commit is contained in:
2026-06-05 13:56:52 +08:00
parent 5d24055517
commit 47fef40654
+56 -17
View File
@@ -72,8 +72,15 @@ const drag = ref<DragState | null>(null)
const hoverX = ref<number | null>(null)
const velocity = ref(0)
const rafId = ref<number | null>(null)
const inertiaRafId = ref<number | null>(null)
const lastInertiaTime = ref(0)
const tooltip = ref<TooltipState | null>(null)
const INERTIA_REFERENCE_FRAME_MS = 1000 / 120
const INERTIA_MAX_FRAME_MS = 1000 / 30
const INERTIA_DECAY_PER_120HZ_FRAME = 0.96
const INERTIA_STOP_VELOCITY = 0.01
const panelClass = computed(() =>
props.isDarkMode
? 'border-[#40545c] bg-[linear-gradient(135deg,rgba(34,49,56,0.96),rgba(24,35,42,0.92))] shadow-md shadow-black/25'
@@ -130,8 +137,12 @@ const draw = () => {
clampState()
const dpr = window.devicePixelRatio || 1
canvas.width = rect.width * dpr
canvas.height = rect.height * dpr
const targetWidth = Math.round(rect.width * dpr)
const targetHeight = Math.round(rect.height * dpr)
if (canvas.width !== targetWidth || canvas.height !== targetHeight) {
canvas.width = targetWidth
canvas.height = targetHeight
}
ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
ctx.clearRect(0, 0, rect.width, rect.height)
@@ -299,12 +310,12 @@ const draw = () => {
}
}
const pan = (deltaX: number) => {
const pan = (deltaX: number, shouldScheduleDraw = true) => {
const rect = canvasRef.value?.getBoundingClientRect()
if (!rect) return
offset.value += (-deltaX / Math.max(1, rect.width)) * Math.max(1, windowSize.value - 1) * 1.15
clampState()
scheduleDraw()
if (shouldScheduleDraw) scheduleDraw()
}
const zoom = (factor: number, center = 0.5) => {
@@ -318,11 +329,32 @@ const zoom = (factor: number, center = 0.5) => {
scheduleDraw()
}
const applyInertia = () => {
if (Math.abs(velocity.value) < 0.02) return
pan(velocity.value)
velocity.value *= 0.92
window.requestAnimationFrame(applyInertia)
const cancelInertia = () => {
if (inertiaRafId.value !== null) {
window.cancelAnimationFrame(inertiaRafId.value)
inertiaRafId.value = null
}
lastInertiaTime.value = 0
}
const applyInertia = (timestamp: number) => {
if (Math.abs(velocity.value) < INERTIA_STOP_VELOCITY) {
cancelInertia()
return
}
const elapsed = lastInertiaTime.value
? Math.min(timestamp - lastInertiaTime.value, INERTIA_MAX_FRAME_MS)
: INERTIA_REFERENCE_FRAME_MS
lastInertiaTime.value = timestamp
pan(velocity.value * elapsed, false)
draw()
velocity.value *= Math.pow(
INERTIA_DECAY_PER_120HZ_FRAME,
elapsed / INERTIA_REFERENCE_FRAME_MS
)
inertiaRafId.value = window.requestAnimationFrame(applyInertia)
}
const handleWheel = (event: WheelEvent) => {
@@ -332,28 +364,34 @@ const handleWheel = (event: WheelEvent) => {
const handlePointerDown = (event: PointerEvent) => {
canvasRef.value?.setPointerCapture(event.pointerId)
cancelInertia()
velocity.value = 0
drag.value = { lastX: event.clientX, time: performance.now() }
drag.value = { lastX: event.clientX, time: event.timeStamp || performance.now() }
}
const handlePointerMove = (event: PointerEvent) => {
const rect = canvasRef.value?.getBoundingClientRect()
if (rect) hoverX.value = event.clientX - rect.left
const pointerEvents = event.getCoalescedEvents?.() || [event]
const latestEvent = pointerEvents[pointerEvents.length - 1] || event
if (rect) hoverX.value = latestEvent.clientX - rect.left
if (!drag.value) {
scheduleDraw()
return
}
const now = performance.now()
const deltaX = event.clientX - drag.value.lastX
const deltaTime = Math.max(16, now - drag.value.time)
velocity.value = deltaX * (16 / deltaTime)
pointerEvents.forEach((pointerEvent) => {
const now = pointerEvent.timeStamp || performance.now()
const deltaX = pointerEvent.clientX - drag.value!.lastX
const deltaTime = Math.max(1, now - drag.value!.time)
velocity.value = deltaX / deltaTime
pan(deltaX)
drag.value = { lastX: event.clientX, time: now }
drag.value = { lastX: pointerEvent.clientX, time: now }
})
}
const handlePointerUp = () => {
drag.value = null
window.requestAnimationFrame(applyInertia)
cancelInertia()
inertiaRafId.value = window.requestAnimationFrame(applyInertia)
}
const handlePointerCancel = () => {
@@ -392,6 +430,7 @@ onMounted(async () => {
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize)
if (rafId.value !== null) window.cancelAnimationFrame(rafId.value)
cancelInertia()
})
defineExpose({ zoom, resetWindow })