From c3be8450aa785ef0dffe28c27f9aedc130a768eb Mon Sep 17 00:00:00 2001 From: cnliucheng Date: Wed, 8 Jul 2026 11:36:35 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20utils/countUp=20=E6=95=B0=E5=AD=97?= =?UTF-8?q?=E6=BB=9A=E5=8A=A8=E5=B7=A5=E5=85=B7=20(easeOut=20cubic)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/countUp.js | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 utils/countUp.js diff --git a/utils/countUp.js b/utils/countUp.js new file mode 100644 index 0000000..9ded4f8 --- /dev/null +++ b/utils/countUp.js @@ -0,0 +1,49 @@ +/** + * 数字滚动工具 — 替代第三方库 + * 用 setTimeout(fn, 16) 模拟 60fps,兼容性最稳 + * 使用 easeOut 缓动: 1 - (1-t)^3 + * + * 使用: + * const task = countUp({ from: 0, to: 30, duration: 800, onUpdate: setDisplayTime }) + * // 不再需要时: + * task.cancel() + */ + +function countUp({ from = 0, to, duration = 800, decimals = 0, onUpdate, onComplete } = {}) { + // 边界:数值相同 或 时长 <= 0,跳过动画 + if (from === to || duration <= 0) { + if (typeof onUpdate === 'function') onUpdate(to) + if (typeof onComplete === 'function') onComplete() + return { cancel: () => {} } + } + + const start = Date.now() + const delta = to - from + let cancelled = false + + const tick = () => { + if (cancelled) return + const elapsed = Date.now() - start + let t = Math.min(elapsed / duration, 1) + // easeOut cubic + t = 1 - Math.pow(1 - t, 3) + const current = from + delta * t + const value = decimals > 0 + ? Number(current.toFixed(decimals)) + : Math.round(current) + if (typeof onUpdate === 'function') onUpdate(value) + if (elapsed >= duration) { + if (typeof onComplete === 'function') onComplete() + return + } + setTimeout(tick, 16) + } + + setTimeout(tick, 16) + + return { + cancel() { cancelled = true } + } +} + +module.exports = { countUp }