デモ
デモを読み込めませんでした。
仕様
- 用途
- グラフ
- 動作
- DOM
- 種類
- レシピ
- 使うもの
- ScrollTrigger
- 動作確認
- three 0.186.0・gsap 3.15.0
- 公開日
しくみと調整
実績の数字は、画面に入ったところで目標の値まで勢いよく数え上がり、終わりに向けてゆっくりになって止まります。アニメーションさせているのは表示の数字ではなく、0から目標値へ変化する値ひとつです。GSAP が補間したその値をフレームごとに整数へ丸めて書き出し、桁区切りは toLocaleString に任せているので、1,280のような表記のまま数えられます。
時間は1.6秒、イージングは power2.out にしました。一定の速さで数えると、止まる瞬間まで数字が速く変わり続け、いくつで止まったのかが読み取れません。数え始める位置は画面の下端から2割ほど内側で、一度数えたら once でそれきりです。往復するたびに数え直すと、数字がいつまでも定まらないように見えます。
数字には tabular-nums を指定してください。通常の字形は数字ごとに幅が違うので、数えている間に数字の列が左右に揺れます。単位は数字の後ろに別の要素として置き、書き換えるのは数字の要素だけにしてあります。同じ要素に入れておくと、書き換えのたびに単位まで消えてしまいます。桁の少ない数字なら、数えずにそのまま表示したほうが落ち着いて見えます。
コード
<section class="stats" aria-labelledby="stats-title">
<h2 class="stats__title" id="stats-title">数字で見る実績</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;
--mute-small: #a8a6a0; /* 16px 未満の文字 */
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.06em; /* 和文の小さなラベル。英大文字の字間(0.14em)は和文にかけない */
color: var(--mute-small);
}
.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),
});
});
},
});
});