如何优化setTimeout实现的数字计数器,提升动画运行速度?
优化数字计数器动画速度的方案
你的问题核心在于每次仅递增1,且依赖setTimeout的1ms延迟——浏览器对定时器存在最小延迟限制(通常为4ms左右),当目标值达到数千时,需要执行数千次定时器回调,自然会慢得滑稽。以下是两种高效的优化方案:
方案一:固定总时长+requestAnimationFrame(推荐)
不管目标值是100还是10000,都在固定时间内完成动画(比如1.5秒),利用requestAnimationFrame和时间比例计算当前数值,既流畅又高效:
const stat = document.querySelectorAll(".stats span"); export const startCounter = () => { stat.forEach(stat => { const target = parseInt(stat.dataset.count); const duration = 1500; // 动画总时长,单位ms let startTime = null; function updateCount(timestamp) { if (!startTime) startTime = timestamp; // 计算已过去时间占总时长的比例,最大不超过1 const progress = Math.min((timestamp - startTime) / duration, 1); // 根据比例计算当前显示数值,取整 const current = Math.floor(progress * target); stat.innerText = current.toLocaleString(); if (progress < 1) { requestAnimationFrame(updateCount); } else { // 最后确保显示准确的目标值 stat.innerText = target.toLocaleString(); } } requestAnimationFrame(updateCount); }); }; startCounter();
方案二:动态递增步长
如果不想固定时长,可以根据目标值大小调整每次递增的步长,目标值越大步长越大,避免逐次加1的低效操作:
const stat = document.querySelectorAll(".stats span"); export const startCounter = () => { stat.forEach(stat => { const target = parseInt(stat.dataset.count); let counter = 0; // 根据目标值计算步长,取目标值的1%,最少为1 const step = Math.max(Math.floor(target / 100), 1); function updateCount() { // 最后一步直接跳到目标值,避免停在接近值 const next = Math.min(counter + step, target); stat.innerText = next.toLocaleString(); if (next < target) { requestAnimationFrame(updateCount); } } updateCount(); }); }; startCounter();
关键优化点说明
requestAnimationFrame:比setTimeout更适合动画场景,它会和浏览器刷新频率同步(通常60帧/秒),不会出现定时器堆积或卡顿问题。- 避免逐次加1:通过比例或步长计算当前值,大幅减少DOM更新次数,直接提升动画速度。
- 统一体验:固定时长方案不管数值大小,动画耗时一致,不会出现小数值快、大数值慢的违和感。
内容的提问来源于stack exchange,提问作者Marya
相关产品推荐
相关产品推荐

