数字が増えていくカウンターをCSSとJSで|0から数え上がる実績の数字、コピペ4種類

数字が0から増えていくカウンター。見本とコード

会社案内やトップページで見かける「創業25年」「お取引先120社」のような数字。ページを開いたときに0から数え上がっていくと、そこに目が止まります。この動きは、短いJSか、新しめのCSSの指定だけで作れます。

使いどころ

数字そのものが伝えたいこと(実績・規模・年数)なら使う。ページに3つまで、数える時間は2秒ほど。値段や在庫など、正確に読んでほしい数字には使わない。

今回は、CSSだけで数える形、JSで数える基本の形、桁区切りと小数つき、実績の数字を3つ並べる形の4種類を、コピペ用のコードつきで紹介します。数える時間や止まり方の目安、読み上げソフトや動きが苦手な人への配慮もまとめました。

トミー

連載「WEBの技術」の28回目です。
今回は、数字が増えていくカウンター。見た目は派手ですが、作りはシンプルです。

完成見本:数え上がる4種類

下の4つは、どれもHTMLの数字にCSSとJSで動きを付けたものです。動きが分かるように、見本では数秒ごとに0から数え直しています。見本の数字は、すべて架空の例です。

CSS ONLY1. CSSだけで数えるJSを入れられない場所に。桁区切りは付けられない
JAVASCRIPT2. JSで数える(だんだんゆっくり)いちばん基本の形。どのブラウザでも同じように動く
FORMAT3. 桁区切り・小数つき4桁を超える数字や、パーセントを見せるとき
STATS4. 実績の数字を3つ並べる会社案内やトップページの「数字で見る」欄に

※ 見本は読み上げソフトでは読まれないようにしています。端末で動きを減らす設定にしている場合は、数え直さず最後の数字だけが出ます。

コピペ用のコード

CSSは、サイトのCSSファイルや、WordPressならテーマの「追加CSS」などに入れます。クラス名の頭の kd-cn は、サイトのほかのデザインと名前がぶつからないようにするための目印です。1のCSSの前半(数字の見た目)は2〜4でも使います。2〜4のJSは共通で、最後にまとめて載せています。

1. CSSだけで数える

@property(CSSの変数に「整数」などの種類を登録する指定)で --kd-n を整数として登録し、アニメーションで0から --to の数字まで変えています。数字の表示には counter()(番号を文字として出す仕組み)を使います。画面に出る数字は読み上げソフトには読ませず、代わりに kd-cn-sr の数字を読ませます。下のCSSの前半は、2〜4でも共通で使う「数字の見た目」です。

HTML
<p class="kd-cn">
  <span class="kd-cn-num kd-cn-css" style="--to: 120" aria-hidden="true"></span><span class="kd-cn-sr">120</span><span class="kd-cn-unit">社</span>
  <span class="kd-cn-label">お取引先</span>
</p>
CSS
/* 1〜4で共通:数字の見た目 */
.kd-cn {
  margin: 0;
  text-align: center;
  color: #10283A;
}
.kd-cn-num {
  font-size: 56px;
  font-weight: 800;
  line-height: 1.1;
  color: #0369A1;
  /* 数字の幅をそろえて、数えている間に横にがたつかないようにする */
  font-variant-numeric: tabular-nums;
}
.kd-cn-unit {
  margin-left: 4px;
  font-size: 20px;
  font-weight: 700;
}
.kd-cn-label {
  display: block;
  margin-top: 6px;
  font-size: 14px;
  color: #33495B;
}
/* 数字を「整数」として登録する(途中の値をブラウザが計算できるようになる) */
@property --kd-n {
  syntax: '<integer>';
  initial-value: 0;
  inherits: false;
}
.kd-cn-css {
  --kd-n: var(--to);
  counter-reset: kd-n var(--kd-n);
  animation: kd-cn-up 2s ease-out;
}
/* 数字そのものは ::after に出す */
.kd-cn-css::after {
  content: counter(kd-n);
}
@keyframes kd-cn-up {
  from { --kd-n: 0; }
}
/* 読み上げ用の数字(画面には出さない) */
.kd-cn-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
@media (prefers-reduced-motion: reduce) {
  .kd-cn-css {
    animation: none;
  }
}

2. JSで数える(だんだんゆっくり)

数字の箱に data-to="120"(数え終わりの数字)を書き、JSが0からその数字まで2秒かけて書きかえます。最初は速く、終わりに近づくほどゆっくりになる計算にしているので、ぴたりと止まって見えます。HTMLには最初から「120」と書いておき、JSが動かなくても正しい数字が出るようにしています。CSSは1の前半(共通の部分)だけで動きます。

HTML
<p class="kd-cn">
  <span class="kd-cn-num" data-to="120">120</span><span class="kd-cn-unit">社</span>
  <span class="kd-cn-label">お取引先</span>
</p>
CSS
/* 1〜4で共通:数字の見た目 */
.kd-cn {
  margin: 0;
  text-align: center;
  color: #10283A;
}
.kd-cn-num {
  font-size: 56px;
  font-weight: 800;
  line-height: 1.1;
  color: #0369A1;
  /* 数字の幅をそろえて、数えている間に横にがたつかないようにする */
  font-variant-numeric: tabular-nums;
}
.kd-cn-unit {
  margin-left: 4px;
  font-size: 20px;
  font-weight: 700;
}
.kd-cn-label {
  display: block;
  margin-top: 6px;
  font-size: 14px;
  color: #33495B;
}

3. 桁区切り・小数つき

JSは2と同じです。数字を文字にするときに Intl.NumberFormat(数字を国や地域に合った書き方にする仕組み)を使っているので、12345は「12,345」と3桁ごとに区切られます。data-to="98.5" のように小数を書くと、小数点以下の桁数をそろえたまま数えます。

HTML
<div class="kd-cn-row">
  <p class="kd-cn">
    <span class="kd-cn-num" data-to="12345">12,345</span><span class="kd-cn-unit">件</span>
    <span class="kd-cn-label">お問い合わせ</span>
  </p>
  <p class="kd-cn">
    <span class="kd-cn-num" data-to="98.5">98.5</span><span class="kd-cn-unit">%</span>
    <span class="kd-cn-label">満足度</span>
  </p>
</div>
CSS
.kd-cn-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px 40px;
}

4. 実績の数字を3つ並べる

2のカウンターを3つ、グリッド(格子状に並べる指定)で横に並べました。JSが画面に半分以上入ったところで数え始めるので、ページの下のほうに置いても、見る人の目に入ってから動きます。スマホの幅(480px以下)では縦に1列になります。

HTML
<div class="kd-cn-grid">
  <p class="kd-cn">
    <span class="kd-cn-num" data-to="25">25</span><span class="kd-cn-unit">年</span>
    <span class="kd-cn-label">創業から</span>
  </p>
  <p class="kd-cn">
    <span class="kd-cn-num" data-to="340">340</span><span class="kd-cn-unit">件</span>
    <span class="kd-cn-label">年間の施工</span>
  </p>
  <p class="kd-cn">
    <span class="kd-cn-num" data-to="18">18</span><span class="kd-cn-unit">人</span>
    <span class="kd-cn-label">スタッフ</span>
  </p>
</div>
CSS
.kd-cn-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
  margin: 0;
  padding: 24px 12px;
  border-radius: 14px;
  background: #F2F8FC;
}
.kd-cn-grid .kd-cn-num {
  font-size: 40px;
}
.kd-cn-grid .kd-cn-unit {
  font-size: 16px;
}
@media (max-width: 480px) {
  .kd-cn-grid {
    grid-template-columns: 1fr;
  }
}

2〜4で共通のJS

data-to を書いた数字を探し、画面に半分以上入ったところで、1回だけ0から数えます。数え終わったら見張るのをやめるので、上へ戻って読み直しても、もう一度は動きません。</body> の直前などに入れてください。data-duration="3000" のように書くと、その数字だけ数える時間(ミリ秒)を変えられます。

JS
// 0から data-to の数字まで数える(だんだんゆっくり止まる)
function kdCount(el) {
  var to = parseFloat(el.dataset.to);
  var dec = (el.dataset.to.split('.')[1] || '').length;
  var dur = Number(el.dataset.duration || 2000);
  var fmt = new Intl.NumberFormat('ja-JP', {
    minimumFractionDigits: dec,
    maximumFractionDigits: dec
  });
  // 動きを減らす設定では、最後の数字をすぐ出す
  if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
    el.textContent = fmt.format(to);
    return;
  }
  var start = null;
  function step(now) {
    if (start === null) start = now;
    var p = Math.min((now - start) / dur, 1);
    var eased = 1 - Math.pow(1 - p, 3);
    el.textContent = fmt.format(to * eased);
    if (p < 1) requestAnimationFrame(step);
  }
  requestAnimationFrame(step);
}
// 画面に半分以上入ったら、1回だけ数える
var kdIo = new IntersectionObserver(function (entries) {
  entries.forEach(function (entry) {
    if (entry.isIntersecting) {
      kdCount(entry.target);
      kdIo.unobserve(entry.target);
    }
  });
}, { threshold: 0.5 });
document.querySelectorAll('.kd-cn-num[data-to]').forEach(function (el) {
  kdIo.observe(el);
});

仕組み:「最後の数字」をHTMLに書いておく

JSは、表示を書きかえているだけ

2〜4のHTMLには、最初から「120」のように数え終わりの数字を書いています。JSは、その数字を0から順に書きかえて見せているだけです。こうしておくと、JSが読み込めなかったときや、検索エンジンがページを読むときにも、正しい数字が伝わります。最初のHTMLを「0」にしてしまうと、JSが動かない環境では0のまま残ります。

requestAnimationFrame で、なめらかに書きかえる

数字の書きかえには requestAnimationFrame(画面を描きかえる直前に処理を呼んでもらう仕組み)を使っています。毎回「始めてから何秒たったか」を計り、その割合から今の数字を出すので、パソコンが重くて途中が飛んでも、2秒で数え終わる長さは変わりません。

割合は、そのままではなく 1 - (1 - p)³ という式で変えています。最初は大きく進み、終わりに近づくほど少しずつ進むので、数字がゆっくり止まって見えます。

CSSだけの形は、@property がかぎ

ふつうのCSSの変数(--kd-n のような名前のもの)は、ブラウザから見るとただの文字なので、0と120の途中の値を計算できません。@property で「整数」と登録すると、途中の値をブラウザが計算できるようになり、アニメーションで少しずつ変えられます。その値を counter-reset と counter() で文字として表示しています。

MDNでは、@property は Baseline 2024(2024年7月から主なブラウザの最新版で使える)と表示されています。それより古いブラウザでは途中の数字が出ず、少し待ってから最後の数字に切りかわります。数字そのものは正しく出るので、見た目の動きがなくなるだけです。

数字の幅をそろえる

多くの文字の形(フォント)では、「1」は「8」より幅がせまく作られています。数えている間に数字の幅が変わると、横の「社」や、真ん中に寄せた数字全体が小刻みに揺れて見えます。font-variant-numeric: tabular-nums(数字の幅をそろえる指定)を付けると、どの数字も同じ幅になり、揺れがおさまります。

数値を変えるとどうなるか

数える時間

2のカウンターの、data-duration(数える時間)だけを変えて、自動でくり返しています。

0.5秒:速すぎて読めない
2秒:目安の長さ
5秒:待たされる

0.5秒では、数えているのか、ちらついたのか分からないうちに終わります。5秒では、数字を読もうとした人が、止まるまで待たされます。1.5〜2.5秒ほどが目安です。今回の見本は2秒にしています。

止まり方

1(CSSだけ)の animation-timing-function(時間の進み方)だけを変えました。

linear:同じ速さで止まる
ease-out:ゆっくり止まる
steps(6):6回で区切る

linear は最後まで同じ速さで進み、急ブレーキのように止まります。ease-out は終わりに向けてゆっくりになり、数字が「落ち着いた」ように見えます。steps(6) は6回に区切って進むので、なめらかさはありませんが、目立ちすぎない動きになります。

変えるところ小さく・短くすると大きく・長くすると
数える時間(2の 2000)動いたことに気づかれない最後の数字を読むまで待たされる
見え始めの割合(JSの threshold: 0.5)画面の端で数え終わり、見逃されるかなりスクロールするまで動かない
数字の大きさ(56px)数えている動きが目立たないスマホで1行に収まらない桁が出る

スマホと、見る人への配慮

読み上げソフトには、最後の数字だけを伝える

2〜4は、HTMLに書いた最後の数字が読み上げられます。数えている途中の数字は、aria-live(中身が変わったら読み上げソフトに伝える属性)を付けていないので、伝わりません。ここに aria-live を付けると、数字が変わるたびに伝えようとするので、付けないでおきます。

1は、画面の数字を ::after で出しているので、aria-hidden="true" で読み上げから外し、代わりに画面には見えない kd-cn-sr の数字を読ませています。

動きを減らす設定では、最後の数字をすぐ出す

端末で動きを減らす設定にしている人には、prefers-reduced-motion で見分けて、数えずに最後の数字を出します。1はCSSでアニメーションを止め、2〜4はJSの最初で確かめています。数字の意味は変わらないので、動きがなくても困りません。

スマホでは桁数に気をつける

数字は大きく見せたくなりますが、56pxで「12,345件」と出すと、スマホの幅ではほぼ1行いっぱいになります。4のように3つ並べるときは、スマホでは縦に1列にしています。桁の多い数字を並べるときは、文字の大きさを下げるか、「1.2万件」のように書き方を変えます。

やりすぎ注意

カウンターは、数字が動くことで目を引く部品です。ページのあちこちで数え上がると、どれも目立たなくなり、読むのを邪魔する動きになります。1ページに1か所、数字は3つまでにとどめています。

トミー

カウンターは「すごそうに見せる」ためではなく、数字を読んでもらうための動きです。
数え終わった数字が、ちゃんと読める大きさと時間かを先に決めます。

AIに頼むなら

カウンターは、Claudeなどの生成AIに頼むとすぐ作ってもらえます。そのときは、数える時間だけでなく、「HTMLに何を書いておくか」と「いつ数えるか」まで伝えると、手直しが少なく済みます。

実績の数字(120社・25年・340件)が0から2秒で数え上がるカウンターを作って。
HTMLには最初から最後の数字を書き、画面に半分入ったら1回だけ数える。
桁区切りは Intl.NumberFormat、数字は tabular-nums、prefers-reduced-motion では数えないで。

AIが出したコードは、そのまま使う前に、次の3つを確かめます。

  • HTMLの数字が「0」になっていないか。JSで数えることを前提に、HTMLに0と書かれていることがあります。JSを止めて(ブラウザの開発者ツールで無効にして)ページを開き、正しい数字が出るかを見ます
  • aria-live が付いていないか。数字が変わるたびに読み上げられ、2秒の間に何十回も伝えようとします
  • スクロールのたびに数え直していないか。画面に入るたびに数え直す書き方だと、上下に読み返すたびに数字が0に戻ります。1回で止まるかを、スクロールして確かめます

KAKERUならこう使う/使わない

最後に、作り手としての判断です。

使う場面

  • 会社案内の「数字で見る」欄。創業年数・取引先の数・スタッフの人数など、文章で読むより数字で見たほうが伝わる情報に使います
  • 採用ページやサービス紹介の、いちばん伝えたい1つの数字。数字が1つだけなら、2の形で大きく見せます
  • JSを入れにくい場所では、1のCSSだけの形。ただし桁区切りが付けられないので、3桁までの数字にします

使わない場面

  • 値段・在庫・期限など、正確に読んでほしい数字。数えている途中の数字が目に入ると、読み間違いのもとになります
  • 根拠を示せない数字。動きで目立たせるほど、「どうやって数えたのか」が気になる数字になります。数え方を書けない数字には使いません
  • 表やグラフの中の数字。ほかの数字と比べて読むものなので、動かさずに最初から見せます
トミー

目立つ数字ほど、「いつ時点の数字か」を小さく添えておくようにしています。
動きよりも、そちらのほうが信頼につながると考えています。

まとめ

  • HTMLには最初から最後の数字を書き、JSは表示を書きかえるだけにする
  • 数える時間は2秒ほど、終わりに向けてゆっくり止める
  • CSSだけなら @property で整数を登録し、counter() で表示する(桁区切りは付けられない)
  • tabular-nums で数字の幅をそろえ、横揺れを防ぐ
  • aria-live は付けない。動きを減らす設定では、最後の数字をすぐ出す

まずは2のカウンターをコピーして、data-to を自分のサイトの数字に書きかえてみてください。HTMLの数字も同じ数字にそろえるのを忘れずに。

参考にした資料

いずれも2026年10月28日に確認しました。ブラウザの対応状況は変わることがあります。最新の情報は各ページでご確認ください。

ふだんの制作の話や、作ったものの裏側は Instagram と X でも出しています。

関連記事

スクロールでふわっと表示するCSSの動き。コピペで5種類と、使う・使わないの判断
文字を1文字ずつ出すタイピング風。日本語と英数字の数え方の違いまで
CSSだけで作るローディングアニメーション コピペで使える5種類と、見る人への配慮
カードを並べるCSS。グリッドで自動で折り返す、auto-fillとauto-fitの違いと使う・使わないの判断
数字が0から増えていくカウンター。見本とコード

コメントを残す

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です

CAPTCHA