直接++导致数字跳变生硬是因为dom即时更新,而自然跳动需通过requestanimationframe实现插值过渡(如99→99.3→100),全程保留小数参与计算,仅渲染时math.round()取整,并用data属性或cancelanimationframe防动画叠加冲突。

为什么直接 ++ 会让数字跳得生硬?
因为浏览器渲染不是即时的,count++ 一执行就立刻更新 DOM,人眼看到的是“啪”一下从 99 变成 100,没有中间过程。真正自然的跳动,是让数字在视觉上「过渡」——比如从 99 → 99.3 → 99.6 → 99.9 → 100,哪怕只持续 300ms。
关键不在加法本身,而在「控制更新节奏」和「避免整数截断干扰动画感」。
- 别用
parseInt()或Math.floor()中途取整,它会砍掉小数部分,破坏渐进感 - 用
requestAnimationFrame()替代setTimeout(),更贴合屏幕刷新节奏 - 起始值和目标值都要转成
Number,防止字符串拼接(比如"99" + 1变成"991")
怎么写一个可复用的跳动函数?
核心是插值计算 + 帧驱动更新。下面这个 animateCounter() 支持任意起点、终点、时长:
function animateCounter(el, start, end, duration = 300) {
const startTime = performance.now();
const increment = end - start;
<p>function step(timestamp) {
const elapsed = timestamp - startTime;
const progress = Math.min(elapsed / duration, 1);
// 使用 easeOutQuad 让减速更自然
const eased = 1 - Math.pow(1 - progress, 2);
const current = start + increment * eased;</p><pre class="brush:php;toolbar:false;">el.textContent = Math.round(current); // 仅最后一步取整,保持视觉连贯
if (progress <p>}</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/shouce/1862" title="使用HTML,CSS,JavaScript开发Android应用程序 英文文字pdf版附源文件"><img
src="https://img.php.cn/upload/manual/000/000/010/170901721079108.gif" alt="使用HTML,CSS,JavaScript开发Android应用程序 英文文字pdf版附源文件" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/shouce/1862" title="使用HTML,CSS,JavaScript开发Android应用程序 英文文字pdf版附源文件" class="overflowclass">使用HTML,CSS,JavaScript开发Android应用程序 英文文字pdf版附源文件</a>
<p class="overflowclass">如果你了解HTML,CSS和JavaScript,您已经拥有所需的工具开发Android应用程序。本动手本书展示了如何使用这些开源web标准设计和建造,可适应任何Android设备的应用程序 - 无需使用Java。您将学习如何创建一个在您选择的平台的Andr​​oid友好的网络应用程序,然后转换与自由PhoneGap框架到一个原生的Andr​​oid应用程序。了解为什么设备无关的移动应用是未来的潮流,并开始构建应用程序,提供更</p>
</div>
<a rel="nofollow" href="/xiazai/shouce/1862" title="使用HTML,CSS,JavaScript开发Android应用程序 英文文字pdf版附源文件" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><p>requestAnimationFrame(step);
}</p>调用示例:animateCounter(document.querySelector('.like-count'), 99, 100)。
-
eased用的是easeOutQuad,比线性更像真实物理惯性 -
Math.round()只在渲染时做,内部全程保留小数参与插值 - 如果按钮被快速连点,需加节流或禁用按钮,否则多个动画会叠加冲突
连点时数字重复跳、错乱怎么办?
根本原因是没处理「动画未结束就触发新计数」。常见错误是每次点击都无条件调用 animateCounter(),导致多个 requestAnimationFrame() 同时改同一个元素。
- 给目标元素加个自定义属性标记状态:
el.dataset.animating = "true" - 动画开始前检查:
if (el.dataset.animating === "true") return; - 动画结束回调里清空标记:
el.dataset.animating = "false" - 更稳妥的做法:把当前动画引用存到元素上(
el._animId),用cancelAnimationFrame()主动终止旧动画
移动端 Safari 上跳不动?
部分 iOS 版本对 requestAnimationFrame() 在 background-tab 或低功耗模式下有节流,导致动画卡顿或直接跳过中间帧。
- 确保元素有明确的 CSS
will-change: contents,提示浏览器优化渲染 - 避免在
click事件里直接调用动画——改用touchend,并加preventDefault()防止延迟 - 降级方案:当检测到
performance.now()不可用时,fallback 到setTimeout()+ 16ms 间隔(但要手动模拟 easing)
跳动是否自然,不取决于代码多短,而在于你有没有让数字「呼吸」——它得有加速、有惯性、有收尾,而不是冷冰冰地执行一次赋值。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










