JS大小数值计数器 实现不同速度运行且动画同步结束
问题原因
原有计数逻辑存在两个核心问题,导致不同目标值的计数器动画不同步:
- 未统一约束总动画时长:代码固定
setTimeout间隔为50ms,单步增量设置为目标值/1000,配合Math.ceil向上取整后,小目标值计数器仅需极少步数就能到达终点,大目标值计数器需要的步数是小数值的上百倍,总耗时差距可达数十倍。 - 增量与取整逻辑不匹配:小目标值计算出的单步增量小于1,
Math.ceil会直接将累加后的小数向上取整为整数,相当于单次跳过多个计数步长,动画过程被压缩到几乎不可见。
修复方案
核心逻辑是给所有计数器设置统一的总动画时长,基于动画已运行时间占总时长的比例,实时计算每个计数器当前应显示的数值,保证所有计数器同步从0增长到目标值、同时结束动画。使用requestAnimationFrame替代固定间隔的setTimeout,动画流畅度更高,时间控制更精准。
修正后完整JS代码
let section_counter = document.querySelector('#section_counter'); let counters = document.querySelectorAll('.counter-item .counter'); // 所有计数器统一动画总时长,单位毫秒,可按需调整 const ANIMATION_DURATION = 2000; // 滚动触发逻辑 let CounterObserver = new IntersectionObserver( (entries, observer) => { let [entry] = entries; if (!entry.isIntersecting) return; // 记录动画启动时间戳 const startTime = performance.now(); function updateCounters() { // 计算当前动画进度,取值范围0-1 const elapsed = performance.now() - startTime; const progress = Math.min(elapsed / ANIMATION_DURATION, 1); // 统一更新所有计数器的当前值 counters.forEach(counter => { const targetNumber = +counter.dataset.target; // 按进度计算当前显示值,整数场景用round取整即可 const currentValue = Math.round(targetNumber * progress); counter.innerText = currentValue; }) // 进度未到100%则继续执行下一帧动画,到达终点后终止 if (progress < 1) { requestAnimationFrame(updateCounters); } } // 启动计数动画 requestAnimationFrame(updateCounters); // 保留原有元素滑入动画逻辑 counters.forEach((counter, index) => { if (counter.parentElement.style.animation) { counter.parentElement.style.animation = ''; } else { counter.parentElement.style.animation = `slide-up 0.3s ease forwards ${ index / counters.length + 0.5 }s`; } }); observer.unobserve(section_counter); }, { root: null, threshold: window.innerWidth > 768 ? 0.4 : 0.3, } ); CounterObserver.observe(section_counter);
调整说明
- 所有计数器共用同一套时间进度计算逻辑,无论目标值是15还是9000,都会在设定的
ANIMATION_DURATION时长内完成计数,天然保持同步结束。 - 用
requestAnimationFrame替代setTimeout,动画和浏览器刷新频率对齐,不会出现卡顿、跳帧问题。 - 去掉原逻辑中每个计数器单独递归更新的写法,统一在单帧内更新所有计数器数值,执行性能更高。
- 如果后续需要支持小数目标值,仅需替换取整逻辑即可,比如保留1位小数就将
Math.round(targetNumber * progress)改为Math.round(targetNumber * progress * 10)/10,不影响多计数器同步效果。
原有HTML结构不需要做任何修改,直接替换JS代码即可生效。
内容的提问来源于stack exchange,提问作者Mario Sosa
相关产品推荐
相关产品推荐

