本文へスキップ
HELLO!
HIGASHIYUKI®.COM
0   /   100
グラフ

棒グラフが順に伸びて値のラベルが出る

デモ

仕様

用途
グラフ
動作
DOM
種類
レシピ
動作確認
three 0.186.0・gsap 3.15.0
公開日

しくみと調整

左の月から順に、棒が下から伸びます。棒の上の数字も 0 から同じ速さで数え上がるので、伸びきる瞬間に高さと数字が同時にそろいます。いちばん高い月だけ差し色にして、どこが山かをひと目で分かるようにしています。

1 本が伸びるのは 0.9 秒の power3.out、隣の棒とは 0.08 秒ずらします。この幅を広げると 8 本ぶんの遅れが積み上がり、最後の月がいつまでも出てきません。数字は棒より 0.15 秒遅れて、8px 下から浮き上がります。棒と同時に出すと、まだ伸びている先端に数字が付いていけません。

グラフは、絵だけでは読み上げに何も届きません。月と値はそのまま文字としてページに書き、棒の高さはその値(--value)から CSS で出します。数字を持つ場所が 1 つになるので、あとから値を直しても棒とずれません。

動かすのは、画面に入った 1 回だけにします。スクロールのたびに数え直すと、読み手が値を追えません。伸ばし終わったら、動きのために書き込んだ高さは消して CSS に返します。残したままだと、画面の幅が変わっても棒が最初の高さのままになります。動きを減らす設定では、伸びきった状態で置きます。

目盛りの上端・棒の幅・色は、まとめて CSS の値で変えられます。上端を実際の最大値ぴったりにすると、いちばん高い棒が天井に貼り付くので、少し余裕を取ります。売上や実績のように、月ごとの差を見せたいセクションに向いています。

コード

HTML
<figure class="chart">
  <figcaption class="chart__caption">
    <span class="chart__title">Revenue by month</span>
    <span class="chart__note">2026年1月〜8月・単位:万円</span>
  </figcaption>

  <!-- 月と値は文字として置く。棒の高さは --value から CSS が出す -->
  <ol class="chart__bars">
    <li class="chart__item" style="--value: 380">
      <span class="chart__month">1月</span><span class="chart__value">380</span><span class="chart__bar"></span>
    </li>
    <li class="chart__item" style="--value: 520">
      <span class="chart__month">2月</span><span class="chart__value">520</span><span class="chart__bar"></span>
    </li>
    <li class="chart__item" style="--value: 450">
      <span class="chart__month">3月</span><span class="chart__value">450</span><span class="chart__bar"></span>
    </li>
    <li class="chart__item" style="--value: 610">
      <span class="chart__month">4月</span><span class="chart__value">610</span><span class="chart__bar"></span>
    </li>
    <li class="chart__item" style="--value: 570">
      <span class="chart__month">5月</span><span class="chart__value">570</span><span class="chart__bar"></span>
    </li>
    <li class="chart__item" style="--value: 740">
      <span class="chart__month">6月</span><span class="chart__value">740</span><span class="chart__bar"></span>
    </li>
    <li class="chart__item" style="--value: 680">
      <span class="chart__month">7月</span><span class="chart__value">680</span><span class="chart__bar"></span>
    </li>
    <li class="chart__item chart__item--best" style="--value: 880">
      <span class="chart__month">8月</span><span class="chart__value">880</span><span class="chart__bar"></span>
    </li>
  </ol>
</figure>
CSS
.chart {
  --max: 1000;     /* 目盛りの上端。棒の高さはこれに対する割合 */
  --plot: 320px;   /* 上端まで伸びたときの高さ */
  --width: 52px;   /* 棒の幅 */
  --bar: #22375a;  /* 棒の色 */
  --best: #ff6b35; /* いちばん高い月の色 */
  --ink: #262626;  /* 文字の色 */
  --mute: #6b6b6b; /* 月と注記の色 */
  --line: rgba(38, 38, 38, 0.12); /* 目盛りの線の色 */
  --surface: #ffffff;
  margin: 0;
  padding: 32px;
  background: var(--surface);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.chart__caption {
  display: grid;
  gap: 6px;
  margin-bottom: 40px;
}

.chart__title {
  font-size: 24px;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.chart__note {
  font-size: 14px;
  letter-spacing: 0.04em;
  color: var(--mute);
}

/* 列の間は空けない。月のラベルの上の罫線が、そのまま 1 本の目盛りになる */
.chart__bars {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  align-items: end;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* 読み上げは「月 → 値」の順。見た目は order で 値 → 棒 → 月 に並べ替える */
.chart__item {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.chart__value {
  order: 1;
  margin-bottom: 8px;
  font-size: 15px;
  font-weight: 700;
}

.chart__bar {
  order: 2;
  flex: none;
  width: min(var(--width), 76%);
  height: calc(var(--value) / var(--max) * var(--plot));
  border-radius: 4px 4px 0 0;
  background: var(--bar);
}

.chart__month {
  order: 3;
  align-self: stretch;
  padding-top: 10px;
  border-top: 2px solid var(--line);
  font-size: 14px;
  letter-spacing: 0.04em;
  text-align: center;
  color: var(--mute);
}

/* いちばん高い月だけ差し色にする */
.chart__item--best {
  --bar: var(--best);
  color: var(--best);
}
JavaScript
import { gsap } from "gsap";

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

document.querySelectorAll(".chart").forEach((chart) => {
  const items = [...chart.querySelectorAll(".chart__item")];
  const bars = items.map((item) => item.querySelector(".chart__bar"));
  const labels = items.map((item) => item.querySelector(".chart__value"));
  // 値は CSS 変数に書いたものを正とする(棒の高さと数字が別々にならないように)
  const values = items.map((item) => Number(item.style.getPropertyValue("--value")));
  const counts = values.map(() => ({ v: 0 }));

  const count = () => {
    labels.forEach((label, i) => {
      label.textContent = Math.round(counts[i].v).toLocaleString("ja-JP");
    });
  };

  const grow = gsap
    .timeline({ paused: true, onUpdate: count })
    // 伸びきった高さは CSS が持っている。0 からその高さへ戻し、終わったら height を消して CSS に返す
    .from(bars, { height: 0, duration: 0.9, ease: "power3.out", stagger: 0.08, clearProps: "height" }, 0)
    .fromTo(counts, { v: 0 }, { v: (i) => values[i], duration: 0.9, ease: "power3.out", stagger: 0.08 }, 0)
    // 数字は棒に少し遅れて、下から浮き上がる
    .from(labels, { autoAlpha: 0, y: 8, duration: 0.35, ease: "power1.out", stagger: 0.08 }, 0.15);

  // 動きを減らす設定では、伸びきった状態で見せる
  if (reduceMotion) {
    grow.progress(1);
    return;
  }

  // 画面に入ったら 1 回だけ伸ばす
  const io = new IntersectionObserver(
    (entries, self) => {
      for (const entry of entries) {
        if (!entry.isIntersecting) continue;
        self.unobserve(entry.target);
        grow.play();
      }
    },
    { rootMargin: "0px 0px -20%" } // 下から 20% 入ったら始める
  );
  io.observe(chart);
});

インタラクションデザインについて、
お気軽にご相談ください。

ここに並べているのは、Web サイトの中で動く UI のサンプルです。新しく作るサイトに組み込むことも、いま公開中のサイトに、ボタンやヒーロー、スクロールの演出だけを足すこともできます。設計から実装までの進め方はWeb サイト制作、端末ごとの見え方や表示速度の考え方はレスポンシブウェブサイトのページにまとめています。映像として作る動きをお探しの場合はモーショングラフィックスをご覧ください。