You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

页面滚动时jQuery数字计数动画卡顿重复问题排查

问题:数字滚动动画在页面滚动时卡顿,JSFiddle中却正常

我在网站中实现了数字计数动画——当数字元素进入视口可见区域后,会从0向上滚动到目标数值。原本功能正常,但现在出现了奇怪的问题:用户手动滚动页面时,动画会暂停一秒,停止滚动后又重复播放,整体表现十分卡顿。但我把完全相同的代码放到JSFiddle中测试时,却没有出现这种卡顿现象。


复现代码

JavaScript

jQuery(function($) { $(function($, win) { $.fn.inViewport = function(cb) { return this.each(function(i, el) { function visPx() { var H = $(this).height(), r = el.getBoundingClientRect(), t = r.top, b = r.bottom; return cb.call(el, Math.max(0, t > 0 ? H - t : (b < H ? b : H))); } visPx(); $(win).on("resize scroll", visPx); }); }; }(jQuery, window));
$(".fig-number").inViewport(function(px) { // if px>0 (entered V.port) and // if prop initNumAnim flag is not yet set = Animate numbers
if (px > 0 && !this.initNumAnim) { this.initNumAnim = true; // Set flag to true to prevent re-running the same animation
$(this).prop('Counter', 0).animate({ Counter: $(this).text() }, { duration: 10000, step: function(now) { $(this).text(Math.ceil(now)); } });
}
}); });

CSS

html, body { height: 100%; }
.spacer { height: 100%; width: 100%; display: block; background: red; color: white; }

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="spacer"> scroll down </div>
<div class="number-box">
<h1 class="fig-number">1000</h1>
<h1 class="fig-number">1500</h1>
</div>
<div class="spacer"> scroll down </div>
<div class="number-box">
<h1 class="fig-number">2000</h1>
<h1 class="fig-number">2500</h1>
</div>
<div class="spacer"> scroll down </div>
<div class="number-box">
<h1 class="fig-number">3000</h1>
<h1 class="fig-number">3500</h1>
</div>

问题根源分析

咱们先理清楚为什么同一个代码在两个环境表现不一样:

  1. 滚动事件触发太频繁,没做防抖:原代码里scroll事件直接绑了visPx函数,滚动时浏览器每秒会触发几十次这个函数,大量的计算直接占满主线程,jQuery的动画只能被迫暂停。JSFiddle环境干净,没有其他脚本抢资源,所以卡顿不明显;但你的网站肯定有其他插件、脚本或者复杂样式,双重负载下问题就暴露了。
  2. 视口检测计算冗余:每次触发都要计算元素的高度、位置,反复调用getBoundingClientRect()也是个小性能杀手,积累起来就会拖慢动画。
  3. 网站环境的额外性能消耗:比如第三方广告、统计脚本、其他滚动监听效果,这些都会占用主线程,让你的数字动画“抢不到资源”,出现卡停重复的情况。

解决方案

1. 给滚动/resize事件加防抖

这是最关键的一步,减少函数触发频率,让浏览器喘口气:

jQuery(function($) { 
  // 防抖函数:指定时间内只执行最后一次调用
  function debounce(func, wait = 100) {
    let timeoutId;
    return function(...args) {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(() => func.apply(this, args), wait);
    };
  }

  $(function($, win) { 
    $.fn.inViewport = function(cb) { 
      return this.each(function(i, el) { 
        function visPx() { 
          const H = $(this).height();
          const r = el.getBoundingClientRect();
          const t = r.top;
          const b = r.bottom;
          return cb.call(el, Math.max(0, t > 0 ? H - t : (b < H ? b : H))); 
        }
        visPx();
        // 用防抖包装事件处理函数
        const debouncedVisPx = debounce(visPx);
        $(win).on("resize scroll", debouncedVisPx); 
      }); 
    }; 
  }(jQuery, window));

  $(".fig-number").inViewport(function(px) { 
    if (px > 0 && !this.initNumAnim) { 
      this.initNumAnim = true; 
      $(this).prop('Counter', 0).animate({ Counter: $(this).text() }, { 
        duration: 10000, 
        step: function(now) { 
          $(this).text(Math.ceil(now)); 
        } 
      }); 
    }
  }); 
});

2. 简化视口检测逻辑(可选)

如果你不需要精确计算可见像素,只是判断元素是否进入视口,可以简化检测逻辑,减少计算量:

function visPx() {
  const rect = el.getBoundingClientRect();
  // 只要元素有部分进入视口就算可见
  const isVisible = rect.top < window.innerHeight && rect.bottom > 0;
  return cb.call(el, isVisible ? 1 : 0);
}

3. 排查网站的其他性能瓶颈

打开浏览器开发者工具的Performance面板,录制一次滚动操作,看看有没有其他脚本占用大量主线程时间:

  • 第三方广告、统计插件
  • 其他滚动监听事件
  • 复杂的CSS动画/过渡
    这些都是常见的性能杀手,找到后可以考虑优化或者延迟加载。

4. 用原生JS实现动画(可选)

如果jQuery的animate还是不够流畅,可以试试用requestAnimationFrame实现,它能更好地和浏览器重绘周期同步:

// 替换原有的jQuery animate调用
function animateNumber(element, target) {
  let current = 0;
  // 根据总时长计算每帧递增的值(16ms约为一帧的时间)
  const increment = target / (10000 / 16);
  function update() {
    current += increment;
    if (current >= target) {
      element.textContent = Math.ceil(target);
      return;
    }
    element.textContent = Math.ceil(current);
    requestAnimationFrame(update);
  }
  requestAnimationFrame(update);
}

// 在inViewport的回调里调用
$(".fig-number").inViewport(function(px) { 
  if (px > 0 && !this.initNumAnim) { 
    this.initNumAnim = true; 
    animateNumber(this, parseInt($(this).text(), 10));
  }
});

总结

核心问题就是滚动事件触发过于频繁导致主线程阻塞,加上网站环境的额外性能负载,才出现了JSFiddle中没有的卡顿。先加防抖,再排查其他性能瓶颈,应该就能解决问题了。

内容的提问来源于stack exchange,提问作者Cray

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 04:27:55