import { ref, watch, onScopeDispose } from 'vue'
|
|
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
// 数字滚动:只在值变化时做一次 500ms 缓动,尊重系统减弱动效设置
|
export function useCountUp(source, duration = 500) {
|
const display = ref(0)
|
let raf = 0
|
|
const run = (to) => {
|
const target = Number(to)
|
if (!isFinite(target)) {
|
display.value = 0
|
return
|
}
|
if (reduceMotion || target === display.value) {
|
display.value = target
|
return
|
}
|
cancelAnimationFrame(raf)
|
const from = Number(display.value) || 0
|
const start = performance.now()
|
const step = (now) => {
|
const p = Math.min((now - start) / duration, 1)
|
const eased = 1 - Math.pow(1 - p, 3)
|
display.value = from + (target - from) * eased
|
if (p < 1) raf = requestAnimationFrame(step)
|
}
|
raf = requestAnimationFrame(step)
|
}
|
|
watch(source, (v) => run(v), { immediate: true })
|
onScopeDispose(() => cancelAnimationFrame(raf))
|
|
return display
|
}
|
|
// 千分位格式化(保留整数或小数)
|
export function formatNum(n, digits) {
|
const num = Number(n || 0)
|
if (digits !== undefined) {
|
return num.toLocaleString('zh-CN', { minimumFractionDigits: digits, maximumFractionDigits: digits })
|
}
|
return num.toLocaleString('zh-CN')
|
}
|