
会社の様々なデータを数値化してHPで紹介しているサイトがたくさんありますが、その中で数字がカウントされるスクロールアニメーションを採用しているサイトがあります。
というかほぼデフォで実装しているところの方が多いくらいです。
ということで、今回作成してみました。
JavaScriptを記述する
まずはJavaScriptを記述してみます。
JavaScript
const counters = document.querySelectorAll(".counter");
counters.forEach(counter => {
counter.innerText = "0";
const updateCount = () => {
const target = +counter.getAttribute("data-target");
const current = +counter.innerText;
const increment = target / 100;
if (current < target) {
counter.innerText = `${Math.ceil(current + increment)}`;
setTimeout(updateCount, 20);
} else {
counter.innerText = target;
}
};
updateCount();
});
targetはカウントアップのdata属性から取得した最終値です。
incrementは100ステップ程度で到達するように、1回あたりの増分を設定しています。
setTimeoutは20msごとに数字を更新し、アニメーションに見せています。
HTMLは以下になります。
HTML
<div class="counter" data-target="100">0</div>
ベースはこれでできます。
以下からは応用です。
小数点をカウントできるようにする
ベースだけでは小数点を設定した時不自然なカウントになります。
例えば
<div class="counter" data-target="19.4">0</div>
とした場合、0から19まで整数でカウントした後、いきなり小数点が追加されます。
これを小数点を含んだアニメーションにします。
JavaScript
const counters = document.querySelectorAll(".counter");
const startCounting = entry => {
const counter = entry.target;
const targetStr = counter.getAttribute("data-target");
const target = parseFloat(targetStr);
let current = 0;
const steps = 100; // アニメーションのステップ数
const increment = target / steps;
// data-targetの小数点以下の桁数を自動で判定
const decimalPlaces = (targetStr.split(".")[1] || "").length;
const update = () => {
if (current < target) {
current += increment;
counter.innerText = current.toFixed(decimalPlaces);
setTimeout(update, 20);
} else {
counter.innerText = target.toFixed(decimalPlaces);
}
};
update();
};
const observer = new IntersectionObserver(
entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
startCounting(entry);
observer.unobserve(entry.target);
}
});
},
{
threshold: 0.6
}
);
counters.forEach(counter => observer.observe(counter));
parseFloat() でターゲット値を小数も含めて取得します。
decimalPlaces で小数点の桁数を自動判定します。
toFixed(decimalPlaces) を使って自然に小数を維持します。
HTMLは以下になります。
HTML
<div class="counter" data-target="19.4">0</div> <div class="counter" data-target="19.45">0</div> <div class="counter" data-target="123.4567">0</div> <div class="counter" data-target="100">0</div>
「小数点第1位まで」などの固定も可能ですが、上記は「小数点の桁数を自動で判定」し制限を設けない仕様にしています。
カンマ区切りの数値にする
<div class="counter" data-target="1000">0</div>
とした場合、1000とカウントされます。
これをカンマを自動で入れるスクリプトにしてみます。
JavaScript
const counters = document.querySelectorAll(".counter");
const startCounting = entry => {
const counter = entry.target;
const targetStr = counter.getAttribute("data-target").replace(/,/g, ""); // カンマを除去
const target = parseFloat(targetStr);
let current = 0;
const steps = 100;
const increment = target / steps;
// 小数点以下の桁数を判定
const decimalPlaces = (targetStr.split(".")[1] || "").length;
const formatNumber = (value) => {
if (decimalPlaces > 0) {
// 小数がある場合 → 指定の桁数まで表示
return Number(value.toFixed(decimalPlaces)).toLocaleString();
} else {
// 整数の場合 → カンマ区切りのみ
return Math.round(value).toLocaleString();
}
};
const update = () => {
if (current < target) {
current += increment;
counter.innerText = formatNumber(current);
setTimeout(update, 20);
} else {
counter.innerText = formatNumber(target);
}
};
update();
};
const observer = new IntersectionObserver(
entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
startCounting(entry);
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.6 }
);
counters.forEach(counter => observer.observe(counter));
以下の部分で判定しています。
const formatNumber = (value) => {
if (decimalPlaces > 0) {
// 小数がある場合 → 指定の桁数まで表示
return Number(value.toFixed(decimalPlaces)).toLocaleString();
} else {
// 整数の場合 → カンマ区切りのみ
return Math.round(value).toLocaleString();
}
};
これで1000が1,000になります。
HTMLは以下になります。
HTML
<div class="counter" data-target="19.4">0</div> <div class="counter" data-target="19.45">0</div> <div class="counter" data-target="123.4567">0</div> <div class="counter" data-target="100">0</div> <div class="counter" data-target="1000">0</div>
以上です。
まとめ
イージングやカウント時間の調整も色々できるので何かと応用が効きそうなので、今後研究したいと思います。
またスクロールで数字が増えるカウントアップアニメーション「jquery-numerator.js」というのがあります。
本記事を書く最中に知ったのですが、こちらもトライしてみます。