2026-09-11

数字カウントアップの実装方法【JSコピペ】

カウントアップ演出はなぜ効くのか

「導入実績1,200社」「累計販売50,000個」——数字がパラパラと増えていく演出は、単なる飾りではありません。静止した数字よりも視線を引きつけ、閲覧者に「勢いのあるサービスだ」という印象を一瞬で与えます。実績・料金・満足度など、信頼を数字で語りたいセクションに置くだけで、ページの説得力が変わります。本記事では、コピペで動くバニラJS実装から、スクロールで自動発火させる方法、数字表示の作法までをまとめます。

基本の実装: requestAnimationFrameで数値を刻む

setIntervalで一定間隔ごとに数値を足していく方法もありますが、滑らかさとパフォーマンスを両立するならrequestAnimationFrameを使うのが定番です。以下はそのまま使えるコピペコードです。


<span class="js-countup" data-to="1200" data-duration="1600">0</span>

function animateCountUp(el) {
  const to = Number(el.dataset.to || 0);
  const duration = Number(el.dataset.duration || 1500);
  const start = performance.now();
  const from = 0;

  function tick(now) {
    const elapsed = now - start;
    const progress = Math.min(elapsed / duration, 1);
    // easeOutExpo: 序盤は速く、終盤でゆっくり収束させる
    const eased = progress === 1 ? 1 : 1 - Math.pow(2, -10 * progress);
    const current = Math.round(from + (to - from) * eased);
    el.textContent = current.toLocaleString('ja-JP');
    if (progress < 1) requestAnimationFrame(tick);
  }
  requestAnimationFrame(tick);
}

document.querySelectorAll('.js-countup').forEach(animateCountUp);

ポイントはイージング関数です。単純な線形(progressをそのまま使う)だと機械的な印象になりがちなので、easeOutExpoのように終盤が滑らかに収束するカーブを使うと、数字が「着地する」ような気持ちよさが出ます。

スクロールで自動発火させる(Intersection Observer)

実績セクションはページの下の方にあることが多いため、ページ読み込み時に即カウントするのではなく、要素が画面に入ったタイミングで発火させるのが基本です。Intersection Observerを使えば軽量に実装できます。


const targets = document.querySelectorAll('.js-countup');
if (targets.length) {
  const io = new IntersectionObserver((entries, observer) => {
    entries.forEach(entry => {
      if (!entry.isIntersecting) return;
      animateCountUp(entry.target);
      observer.unobserve(entry.target);
    });
  }, { threshold: 0.4 });
  targets.forEach(el => io.observe(el));
}

unobserveで監視を解除することで、スクロールを行き来しても二重にカウントが走らないようにしています。実績バンドの複数数字にもそのまま使えます。

▲ 実際に動いています(スクロール・ホバーも試せます)|この部品をカタログで見る

桁区切りと単位の扱い

日本語サイトで数字を見せるときにありがちな失敗が、桁区切りの表記ゆれです。toLocaleString('ja-JP')を使えば「1200」が自動的に「1,200」になり、カンマ区切りが手に入ります。「社」「件」「%」などの単位はJS側で数値と結合せず、HTML側に固定テキストとして置くと、アニメーション中に単位までガタつくのを防げます。


<p><span class="js-countup" data-to="98">0</span><span class="unit">%</span> のお客様が継続利用</p>

小数を扱う場合はtoFixed(1)などで桁数を固定してからtoLocaleStringに渡すと、カウント中に桁数が変わってチラつく事故を避けられます。

オドメーター風にワンランク上げる

数字が単に増えるだけでなく、レジやメーターのようにドラム状に桁がスライドして切り替わる「オドメーター」表現にすると、さらに視線を引く演出になります。各桁を独立したスパンにし、transform: translateY()で縦方向にスクロールさせるのが基本の作り方です。桁上がりの制御が地味に複雑なので、既製の動きを参考にするのが近道です。

▲ 実際に動いています(スクロール・ホバーも試せます)|この部品をカタログで見る

実装時に気をつけたい3つのこと

1つ目はprefers-reduced-motionへの配慮です。検知した場合はアニメーションを省略し、最終値を即座に表示しましょう。2つ目はアクセシビリティで、数値が変化する間はスクリーンリーダーが逐一読み上げないよう、確定値のみを伝える設計が望ましいです。3つ目はパフォーマンスで、数字が10個以上並ぶ場合は1つのrequestAnimationFrameループで全要素をまとめて更新すると負荷を抑えられます。


@media (prefers-reduced-motion: reduce) {
  .js-countup { transition: none; }
}

if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
  document.querySelectorAll('.js-countup').forEach(el => {
    el.textContent = Number(el.dataset.to || 0).toLocaleString('ja-JP');
  });
}

まとめ

カウントアップ演出は、コードとしては数十行程度の小さな実装ですが、実績セクションの説得力を大きく底上げしてくれます。まずは今回紹介したrequestAnimationFrame版をそのまま貼り付けて動きを確認し、慣れてきたらIntersection Observerでのスクロール連動、オドメーター表現へとステップアップしていくのがおすすめです。自分のサイトの数字が「動く」だけで、閲覧者の記憶に残るページに変わります。

動くパーツ、探すより選ぶ方が早いかもしれません。

この記事のようなアニメーションが、コピペで使える形で500点。ぜんぶ実物が動くカタログで公開中です。

無料でカタログを見る →
© NOVAMOR / motionAETHER 特定商取引法に基づく表記 プライバシーポリシー