用 requestAnimationFrame 驱动插值并配合 IntersectionObserver 触发,是仪表盘数字滚动最稳最省资源的方案;CSS @keyframes 因无法响应动态目标值、位数变化错配、无法驱动文本更新而不可行。

直接用 requestAnimationFrame 驱动数值插值,配合 IntersectionObserver 控制触发时机,是仪表盘数字滚动增长最稳、最省资源的方案。CSS 动画无法响应动态目标值,setInterval 在失焦或低端设备上会跳帧甚至提前结束。
为什么不能用 CSS @keyframes 做仪表盘数字滚动
CSS @keyframes 只能预设起止状态,比如从 transform: translateY(0) 到 transform: translateY(-200px),但仪表盘数字(如 “12,847”)是运行时才确定的目标值,位数、大小都不固定。强行套用会导致:
- 数字位数变化时(如 999 → 1000),容器高度错配,出现截断或空白
-
content或innerText无法被 CSS 动画驱动,所谓“滚动”只是视觉欺骗,实际文本不会更新 - 控制台可能报
InvalidStateError,尤其在动态插入元素后立即绑定动画
用 requestAnimationFrame 实现平滑增长的关键细节
核心不是“每帧加 1”,而是按真实经过时间做线性(或缓动)插值,并强制兜底。容易忽略的点:
- 必须用
performance.now()记录起始时间,Date.now()可能因系统时间调整而跳变 - 进度计算要加
Math.min(elapsed / duration, 1),防帧延迟导致超调 - 最后一帧必须手动设为
target,避免浮点误差造成显示差 1(如显示 1234 而非 1235) - 多个数字同时动时,不要每个都调自己的
requestAnimationFrame——统一调度,批量更新textContent,减少重排
仪表盘常见需求对应的实操处理
仪表盘数字常带格式(千分位、单位、小数),这些不能塞进动画循环里:
立即学习“前端免费学习笔记(深入)”;
- 千分位:动画中只更新纯整数或保留一位小数,最后一帧再用
value.toLocaleString()或正则value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')格式化 - 单位(如 “万元”、“人次”):用独立
<span class="unit"></span>包裹,JS 只动数字部分,避免反复拼接字符串 - 进视口才启动:用
IntersectionObserver,threshold: 0.1比0.5更早响应,且触发后立刻unobserve防重复执行 - 动态数据(如 AJAX 加载后渲染):插入 DOM 后手动调
observer.observe(el),不能只靠页面加载时的一次遍历
真正难的不是让数字动起来,而是让多位数同步到位、格式不闪、失焦不崩、视口触发不漏——这些细节堆在一起,才构成一个可交付的仪表盘动效。



















