デモ
デモを読み込めませんでした。
仕様
- 用途
- グラフ
- 動作
- DOM
- 種類
- レシピ
- 使うもの
- ScrollTrigger
- 動作確認
- three 0.186.0・gsap 3.15.0
- 公開日
しくみと調整
数字そのものではなく、0 から目標値へ動く値をひとつ用意して補間し、その値を毎コマ丸めて書き出します。桁区切りは toLocaleString に任せるので、1,280 のような表記のまま数えられます。単位は数字の後ろの別の要素に置き、数字の要素だけを書き換えます。
1.6 秒の power2.out で数えます。終わりに向けてゆっくりになるので、最後の数桁まで読み取れます。まっすぐ数えると、止まる瞬間まで数字が速いままで、いくつで止まったのかが分かりません。
数字には tabular-nums を指定してください。ふつうの字形では 1 と 8 で幅が違うので、数えているあいだ数字の列が左右に揺れます。単位を数字と同じ要素に入れたままにすると、書き換えのたびに単位も消えてしまいます。
数え始める線は画面の下から 2 割ほど内側に置き、一度数えたらそれきりにします(once)。上下に往復するたびに数え直すと、数字がいつまでも定まらないように見えます。
実績・利用者数・料金の節など、数字そのものを主役にしたい所に向いています。桁が多いほど効くので、2 桁ほどの小さな数字は、数えずに出したほうが落ち着きます。
コード
<section class="stats" aria-labelledby="stats-title">
<h2 class="stats__title" id="stats-title">Numbers</h2>
<dl class="stats__list">
<!-- 説明(dt)→ 数字(dd)の順に書き、見た目の上下だけ CSS で入れ替える -->
<div class="stat">
<dt class="stat__label">これまでの制作実績</dt>
<dd class="stat__value">
<!-- 目標値は data 属性に。中身は書き換えるので、最初は 0 を置いておく -->
<span class="stat__number" data-count-to="1280">0</span><span class="stat__unit">件</span>
</dd>
</div>
<div class="stat">
<dt class="stat__label">お客さまの継続率</dt>
<dd class="stat__value">
<span class="stat__number" data-count-to="98">0</span><span class="stat__unit">%</span>
</dd>
</div>
<div class="stat">
<dt class="stat__label">ご利用いただいている国</dt>
<dd class="stat__value">
<span class="stat__number" data-count-to="36">0</span><span class="stat__unit">か国</span>
</dd>
</div>
</dl>
</section>
.stats {
--ink: #eeece6;
--mute: #898989;
padding: 18vh 6vw;
background-color: #111111;
color: var(--ink);
text-align: center;
}
.stats__title {
margin: 0 0 48px;
font-size: 13px;
font-weight: 700;
line-height: 1;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--mute);
}
.stats__list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 24px;
margin: 0;
}
/* 書く順は dt → dd のまま、見た目だけ数字を上にする */
.stat {
display: flex;
flex-direction: column-reverse;
}
.stat__value {
margin: 0;
/* 1 と 8 で幅が変わらない数字。字形が変わると、数えるあいだ列が左右に揺れる */
font-variant-numeric: tabular-nums;
font-size: clamp(44px, 5.6vw, 72px);
font-weight: 800;
line-height: 1;
letter-spacing: -0.02em;
white-space: nowrap;
}
/* 単位は別の要素。数字だけを書き換えるので、単位は消えない */
.stat__unit {
margin-left: 4px;
font-size: 24px;
font-weight: 700;
letter-spacing: 0.04em;
}
.stat__label {
margin-top: 16px;
font-size: 18px;
line-height: 1.8;
letter-spacing: 0.04em;
color: var(--mute);
}
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
// 数えている値を、毎コマ丸めて書き出す。桁区切りは toLocaleString に任せる
const write = (el, value) => (el.textContent = Math.round(value).toLocaleString("ja-JP"));
document.querySelectorAll(".stats").forEach((stats) => {
const list = stats.querySelector(".stats__list");
const counters = gsap.utils
.toArray(".stat__number", stats)
.map((el) => ({ el, target: Number(el.dataset.countTo) }));
// 動きを減らす設定では、数えずに最後の値を出す
if (reduceMotion) {
counters.forEach(({ el, target }) => write(el, target));
return;
}
gsap.set(list, { y: 32, opacity: 0 });
ScrollTrigger.create({
trigger: stats,
start: "top 80%", // 画面の下から 2 割ほど内側に入ったら数え始める
once: true, // 一度数えたらそれきり。上下に往復するたびに数え直さない
onEnter: () => {
gsap.to(list, { y: 0, opacity: 1, duration: 0.6, ease: "power2.out" });
counters.forEach(({ el, target }) => {
const state = { value: 0 };
gsap.to(state, {
value: target,
duration: 1.6,
ease: "power2.out", // 終わりに向けてゆっくりになるので、最後の数桁まで読み取れる
onUpdate: () => write(el, state.value),
});
});
},
});
});