⚡ 优化趋势图高刷动画
This commit is contained in:
@@ -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 })
|
||||
|
||||
Reference in New Issue
Block a user