跳到正文

数字滚动统计条

Animated Stats Counter

初级通用

官网往下滚到「用数字说话」那一段时,几个统计数字从 0 飞快地数到目标值,然后稳稳停住。「12,800+ 用户」「98.6% 好评」这类数字,数起来比直接摆着更有说服力。

你可能会说:「官网上「多少用户、多少好评」那几个数字,能不能滚到的时候从 0 数上去?」

效果预览

正在加载演示…

怎么做

  1. 1

    目标值写在 data 属性里

    把最终数字写在 data-target 上,页面上先显示 0。读屏软件读到的应该是最终值,所以另放一份对读屏可见的完整文字,动画中的数字对读屏隐藏。

    查看代码html
    <div class="stats">
      <div class="stat">
        <b class="stat__num" data-target="12800" data-suffix="+" aria-hidden="true">0</b>
        <span class="sr-only">注册用户 12,800+</span>
        <span aria-hidden="true">注册用户</span>
      </div>
      <div class="stat">
        <b class="stat__num" data-target="98.6" data-decimals="1" data-suffix="%" aria-hidden="true">0</b>
        <span class="sr-only">好评率 98.6%</span>
        <span aria-hidden="true">好评率</span>
      </div>
    </div>
  2. 2

    等宽数字,避免抖动

    大多数字体里 1 比 8 窄,数字一滚整行就左右抖。tabular-nums 让每一位都一样宽。

    查看代码css
    .stat__num {
      font-size: 2rem;
      font-weight: 900;
      font-variant-numeric: tabular-nums;
    }
    .sr-only {
      position: absolute; width: 1px; height: 1px;
      overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
    }
  3. 3

    进入视野时数一次

    用 IntersectionObserver 等统计条出现再开始,只数一次。每一帧按「已过时间 ÷ 总时长」算进度,再用减速曲线让数字越接近终点走得越慢,最后稳稳停住;每一帧都按最终格式(千分位、小数位)显示。开启减少动效时直接显示最终值。

    查看代码js
    const DURATION = 1600;
    const easeOut = (t) => 1 - Math.pow(1 - t, 3);
    const reduced = matchMedia('(prefers-reduced-motion: reduce)').matches;
    
    function countUp(el) {
      const target = Number(el.dataset.target);
      const decimals = Number(el.dataset.decimals || 0);
      const suffix = el.dataset.suffix || '';
      const format = (n) => n.toLocaleString('zh-CN', {
        minimumFractionDigits: decimals, maximumFractionDigits: decimals,
      }) + suffix;
      if (reduced) return (el.textContent = format(target));
      const start = performance.now();
      const tick = (now) => {
        const t = Math.min(1, (now - start) / DURATION);
        el.textContent = format(target * easeOut(t));
        if (t < 1) requestAnimationFrame(tick);
      };
      requestAnimationFrame(tick);
    }
    
    const observer = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        if (!entry.isIntersecting) return;
        entry.target.querySelectorAll('.stat__num').forEach(countUp);
        observer.unobserve(entry.target);
      });
    }, { threshold: 0.6 });
    document.querySelectorAll('.stats').forEach((el) => observer.observe(el));

适合放在哪

适合用

  • 官网「用户数、好评率、服务客户数」这类成绩展示。
  • 年度报告、活动战报页里的关键数字。
  • 课程或产品详情页的「已有多少人学习」。

不适合用

  • 后台看板这种用户要立刻读准数字的地方,别每次都从 0 数;直接显示,或从旧值过渡到新值。
  • 数字很小(比如 3、5)时,数起来没意义;直接显示。
  • 一屏里有十几个数字时,别全都滚;挑最重要的三四个。

让 AI 帮你做

请给【官网的统计数字区域,如:首页「用数字说话」那一栏】加上数字滚动效果。先阅读这个区域的组件和数据来源,确认数字、单位和小数位从哪里来。要求:用 IntersectionObserver 在区域进入视野约 60% 时开始,只播一次;1.2~2 秒内从 0 数到目标值,用减速缓动;每一帧都按最终格式显示(千分位、小数位、后缀单位);数字使用 tabular-nums 等宽字形;动画中的数字对读屏隐藏,另提供完整的最终文字;开启减少动效时直接显示最终值;组件卸载时取消动画帧。沿用现有写法,不改无关代码。最后说明怎样验证:滚到该区域时只数一次,读屏读到的是最终值。

用到的知识