カウントアップとは

統計などの数字が 0 から目標値まで一気に増えていくように見せるアニメーション。

概要

「導入企業数 1,200 社」のような統計の数字を、0 から目標値まで一気に増えていくアニメーションで見せる 演出です。数字がただ表示されるより勢いや実績の大きさが伝わりやすく、サービス紹介ページの実績エリアで よく使われます。

別名

表記ゆれ:カウントアップ、数字アニメーション、数字のカウント、count up

「数字アニメーション」はカウントアップに限らず、数字が変化するアニメーション全般を指すやや広い言い方です。

AI への伝え方

「実績の数字をカウントアップで表示して」で通じます。ページを開いた瞬間に再生するか、画面にスクロール して入ってきたタイミングで再生するかは仕様が分かれる点なので、「スクロールで表示されたら再生して」の ように伝えてください。表示領域の判定には IntersectionObserver を使いますが、iframe のように表示領域が 最初から画面内に収まっている環境では交差状態の変化が起きず自動再生されないことがあるため、確認用の 再生ボタンも併設しておくと安全です。数字が苦手な人に配慮し、prefers-reduced-motion が有効な環境では アニメーションさせず最終的な数字をそのまま表示する実装にします。

似たパーツとの違い

「スクロールアニメーション」は要素がフェードインするなど見た目全般の登場演出を指す、より広い言葉です。 カウントアップはその中でも「数字が増えていく」動きに特化したパーツという位置づけになります。

デモ

ボタンでも再生できます。下にスクロールすると自動でも再生されます。

導入企業数
0
満足度
0
累計利用者数
0

コード

HTML
<div class="count-up-demo">
  <p class="count-up-demo__intro">ボタンでも再生できます。下にスクロールすると自動でも再生されます。</p>
  <button class="count-up-demo__replay" type="button">再生する</button>
  <div class="count-up-demo__spacer" aria-hidden="true"></div>
  <dl class="count-up-demo__stats">
    <div class="count-up-demo__stat">
      <dt>導入企業数</dt>
      <dd><span class="count-up-demo__number" data-target="1200" data-suffix="社">0</span></dd>
    </div>
    <div class="count-up-demo__stat">
      <dt>満足度</dt>
      <dd><span class="count-up-demo__number" data-target="98" data-suffix="%">0</span></dd>
    </div>
    <div class="count-up-demo__stat">
      <dt>累計利用者数</dt>
      <dd><span class="count-up-demo__number" data-target="50000" data-suffix="人">0</span></dd>
    </div>
  </dl>
</div>
CSS
.count-up-demo {
  font-family: sans-serif;
  color: #333333;
}

.count-up-demo__intro {
  margin: 0 0 12px;
  font-size: 13px;
  color: #666666;
}

.count-up-demo__replay {
  padding: 8px 20px;
  border: 1px solid #0017c1;
  border-radius: 999px;
  background: #ffffff;
  color: #0017c1;
  font-size: 13px;
  cursor: pointer;
}

.count-up-demo__replay:hover {
  background: #f2f2f2;
}

.count-up-demo__spacer {
  height: 120px;
}

.count-up-demo__stats {
  display: flex;
  flex-wrap: wrap;
  gap: 24px;
  margin: 0;
}

.count-up-demo__stat {
  min-width: 120px;
}

.count-up-demo__stat dt {
  margin: 0 0 4px;
  font-size: 12px;
  color: #666666;
}

.count-up-demo__stat dd {
  margin: 0;
}

.count-up-demo__number {
  font-size: 28px;
  font-weight: bold;
  color: #0017c1;
}
JS
const numbers = document.querySelectorAll(".count-up-demo__number");
const replayButton = document.querySelector(".count-up-demo__replay");
const stats = document.querySelector(".count-up-demo__stats");

const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

function animate(el) {
  const target = Number(el.dataset.target);
  const suffix = el.dataset.suffix ?? "";

  if (prefersReducedMotion) {
    el.textContent = `${target.toLocaleString("ja-JP")}${suffix}`;
    return;
  }

  const duration = 1200;
  const start = performance.now();

  function step(now) {
    const progress = Math.min((now - start) / duration, 1);
    const current = Math.round(target * progress);
    el.textContent = `${current.toLocaleString("ja-JP")}${suffix}`;
    if (progress < 1) {
      requestAnimationFrame(step);
    }
  }

  requestAnimationFrame(step);
}

function playAll() {
  numbers.forEach((el) => {
    el.textContent = "0";
    animate(el);
  });
}

replayButton.addEventListener("click", playAll);

// スクロールで表示領域に入ったら自動再生する。iframe 等、監視対象が最初から画面内に
// 収まっている環境では交差状態の「変化」が起きず発火しないことがあるため、
// ボタンによる手動再生を必ず併設している。
let hasAutoPlayed = false;

const observer = new IntersectionObserver(
  (entries) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting && !hasAutoPlayed) {
        hasAutoPlayed = true;
        playAll();
        observer.disconnect();
      }
    });
  },
  { threshold: 0.4 }
);

observer.observe(stats);