JavaScriptで数値カウントアップUIを作る方法【Number Animation入門】

Number Animationのアイキャッチ画像

1. はじめに

「0 から 1000 まで数字が増えていく表示」を見たことがあると思います。
たとえば、以下のようなUIです。

  • スコアが加算される表示
  • 売上やPVの集計表示
  • 経験値や報酬の獲得演出
  • ダッシュボードの数値アニメーション

こうした 数値が変化するUI は、ただ値を一気に切り替えるよりも、少しアニメーションさせた方が変化を認識しやすくなります。

今回は、JavaScriptで 数値カウントアップUI(Number Animation) を作る方法を整理します。
requestAnimationFrame を使った基本実装を中心に、duration・easing・表示更新の考え方まで、できるだけシンプルに見ていきます。

2. 数値アニメーションは何のために使うのか

数値カウントアップUIの目的は、単に見た目を派手にすることではありません。
大事なのは、「変化が起きた」ことをユーザーに伝えること です。

たとえば、1000 という数字が急に 1280 に切り替わると、増えたこと自体は分かっても、

  • どのくらい増えたのか
  • 今まさに加算されたのか
  • 演出的に強調したい変化なのか

が伝わりにくいことがあります。

一方、数値が少しずつ増えていくと、

  • 増加した事実が視覚的に分かりやすい
  • 「成果が積み上がった」感覚を出しやすい
  • UI全体に手触りが出る

というメリットがあります。

つまり、数値アニメーションは 装飾 というより フィードバック設計 の一部です。

3. 実装の考え方は意外とシンプル

基本の考え方は次の3つです。

  1. 開始値と終了値を決める
  2. 何ミリ秒で終えるかを決める
  3. 進捗に応じて表示する数値を計算する

たとえば、

  • start = 0
  • end = 1000
  • duration = 1200ms

なら、0ms時点では 0、600ms時点では途中の値、1200ms時点では 1000 を表示します。

この「途中の値」を毎フレーム更新するために、requestAnimationFrame を使います。

4. まずは最小構成で考える

数値アニメーションの基本は、進捗率 t を 0〜1 で求めることです。

t = 経過時間 / duration

ただし、duration を超えた後は 1 を超えないようにします。

t = Math.min(elapsed / duration, 1)

そして、開始値と終了値の間を補間します。

value = start + (end - start) * t

これで、時間に応じて数値がなめらかに変化します。

たとえば start が 0、end が 1000 なら、

  • t = 0 → 0
  • t = 0.5 → 500
  • t = 1 → 1000

になります。

この考え方は、以前の Interpolation の記事ともつながっています。
数値カウントアップも本質的には 値の補間 です。

5. requestAnimationFrame を使った基本実装

まずは最小のサンプルです。

const output = document.getElementById('count');

function animateCount(start, end, duration) {
  const startTime = performance.now();

  function update(now) {
    const elapsed = now - startTime;
    const t = Math.min(elapsed / duration, 1);
    const value = start + (end - start) * t;

    output.textContent = Math.floor(value);

    if (t < 1) {
      requestAnimationFrame(update);
    }
  }

  requestAnimationFrame(update);
}

animateCount(0, 1000, 1200);

このコードでは、0 から 1000 までを 1200ms かけて増やしています。

ポイントは以下です。

  • performance.now() で開始時刻を取る
  • 毎フレーム elapsed を計算する
  • t を 0〜1 に収める
  • 補間した値を表示する
  • 終わっていなければ次フレームを予約する

かなりシンプルですが、これで数値アニメーションの土台はできています。

6. なぜ setInterval より requestAnimationFrame がよいのか

数値更新だけなら setInterval でも動きます。
ただ、UIアニメーションとして考えるなら requestAnimationFrame の方が扱いやすいです。

理由は、ブラウザの描画タイミングに合わせて実行されるからです。

つまり、

  • 画面更新と揃えやすい
  • 無駄な更新が減りやすい
  • 他のアニメーションと合わせやすい

という利点があります。

Idle Interface のこれまでの記事でも何度か扱ってきましたが、UIアニメーションを設計するなら、まず requestAnimationFrame ベースで考えるのが自然です。

7. Math.floor だけでよいのか

最小実装では Math.floor(value) を使いました。
これで整数表示としては十分です。

ただし、用途によって表示方法は変わります。

たとえば、

  • 小数第1位まで見せたい
  • 3桁区切りにしたい
  • 通貨表示したい
  • 最後だけピタッと終了値に合わせたい

といったケースがあります。

たとえば 3桁区切りなら、次のようにできます。

output.textContent = Math.floor(value).toLocaleString();

12000 なら 12,000 のように表示されるので、ダッシュボードや集計表示ではかなり見やすくなります。

8. easing を入れると「それっぽさ」が増す

ここまでの補間は等速です。
つまり、最初から最後まで同じペースで増えます。

でも実際のUIでは、

  • 最初は速く
  • 最後はゆっくり止まる

ような動きの方が気持ちよく見えることが多いです。

そこで使うのが easing です。

たとえば easeOutCubic は次のように書けます。

function easeOutCubic(t) {
  return 1 - Math.pow(1 - t, 3);
}

そして、補間に使う t をそのまま使う代わりに、easing を通した値に置き換えます。

const eased = easeOutCubic(t);
const value = start + (end - start) * eased;

こうすると、序盤は勢いよく増えて、終盤はゆっくり収束する動きになります。

数値アニメーションでは、この「終わり方」がかなり大事です。
最後に少し減速するだけで、UIの印象がかなりよくなります。

9. 実用では「再利用しやすい形」にしておく

実案件や複数箇所で使うことを考えるなら、関数として切り出しておくと便利です。

function animateNumber({
  element,
  start = 0,
  end = 100,
  duration = 1000,
  easing = t => t,
  format = v => Math.floor(v).toString()
}) {
  const startTime = performance.now();

  function update(now) {
    const elapsed = now - startTime;
    const t = Math.min(elapsed / duration, 1);
    const eased = easing(t);
    const value = start + (end - start) * eased;

    element.textContent = format(value);

    if (t < 1) {
      requestAnimationFrame(update);
    }
  }

  requestAnimationFrame(update);
}

使う側はこんな形です。

animateNumber({
  element: document.getElementById('count'),
  start: 0,
  end: 12000,
  duration: 1400,
  easing: easeOutCubic,
  format: v => Math.floor(v).toLocaleString()
});

この形にしておくと、

  • duration を変える
  • easing を差し替える
  • 表示形式を変える

といった調整がしやすくなります。

10. 数値アニメーションでよくある注意点

10-1. 終了値にぴったり合わない

途中計算の都合で、最後に 999 のまま終わるような実装にしてしまうことがあります。
これを避けるため、終了時には最終値を明示的に入れておくと安心です。

if (t >= 1) {
  element.textContent = format(end);
  return;
}

10-2. 短すぎると読めない

たとえば 0 → 100000 を 300ms で動かすと、変化が速すぎて認識しづらくなります。
逆に長すぎても待たされる感じが出ます。

まずは 800ms〜1500ms くらいから試すと調整しやすいです。

10-3. 何度も連打すると重複しやすい

ボタンを押すたびに新しいアニメーションを始める場合、前のアニメーションと重なって意図しない表示になることがあります。
実運用では「前のアニメーションを止める」「現在値から再開する」といった制御も考えた方がよいです。

10-4. 数字だけ動かしても伝わらないことがある

数値だけでは変化が弱い場合、

  • 色の変化
  • スケールの変化
  • アイコンやラベルの補助
  • 差分表示(+120 など)

を組み合わせると、より伝わりやすくなります。

11. どんな場面で使うと効果的か

数値アニメーションは、次のような場面で特に相性がよいです。

  • 売上やPVなどの集計表示
  • ゲームのスコア加算
  • 経験値やコイン獲得
  • プログレスバー横の数値
  • ダッシュボードのKPI表示
  • レベルアップや報酬獲得の演出

逆に、常に高速で更新される値に毎回大きなアニメーションを付けると、かえって読みにくくなることもあります。
大事なのは、「変化を見せたい場面」に絞って使うことです。

12. 今回のポイント

今回の数値カウントアップUIは、見た目こそシンプルですが、アニメーション設計の基礎が詰まっています。

  • requestAnimationFrame で毎フレーム更新する
  • 経過時間から進捗率を作る
  • 開始値と終了値を補間する
  • easing で気持ちよい終わり方を作る
  • format で表示を整える

つまり、数値アニメーションは単体の小技というより、UI変化をどう見せるかの基本練習 としてかなり良い題材です。

これまで扱ってきた deltaTimeeasingInterpolation の考え方も、こうした実用UIに落とし込むとぐっと身近になります。

13. 実験:Number Animation Playground: Linear vs Ease-Out

以下の実験では、数値カウントアップUIの動きを実際に確認できます。
開始値・終了値・duration を変えながら、linear と ease-out の違いを見比べてみてください。

数値アニメーションは一見シンプルですが、どの速さで増えるかどのように止まるか によって印象がかなり変わります。
特に ease-out は終盤で少し減速するため、ただ数値が変わるだけでなく「自然に収まる」感じを作りやすいのが特徴です。

観察ポイント

  • linear は一定速度で増える
  • ease-out は最初が速く、最後がゆっくり止まる
  • duration が短すぎると変化を認識しづらい
  • duration が長すぎると反応が鈍く見える
  • 数値だけでなく、進捗バーも合わせると変化がより伝わりやすい

今回のデモでは、数値アニメーションを単体で見ています。
実際のUIでは、これに色変化・スケール変化・アイコン・ラベルなどを組み合わせることで、より分かりやすいフィードバックにできます。

14. まとめ

JavaScriptで数値カウントアップUIを作るときは、まずは難しく考えすぎず、

  • 開始値
  • 終了値
  • duration
  • 補間
  • easing

の5つに分けて考えると整理しやすくなります。

数値アニメーションは、派手な演出というより、ユーザーに変化を伝えるためのUIです。
だからこそ、速さや止まり方、表示形式を丁寧に整えると、全体の印象もかなり良くなります。

次に progress bar や reward popup のようなUIを作るときにも、この考え方はそのまま応用できます。
小さなUIパーツですが、アニメーション設計の練習題材としてはとても優秀です。

コメント

コメントを残す

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

CAPTCHA