【js】スクロールで数字をスロット風にカウントアップ

Webサイトの数値コンテンツに、スクロールに合わせて数字が切り替わる「スロット風」のアニメーションを実装しました。今回は、外部ライブラリを使わず、JavaScriptとCSSだけで数字を縦方向に動かすシンプルな実装です。

目次

完成イメージ

まずは以下のデモをご覧ください。

See the Pen
Untitled
by 岸上友希那 (@dnrfjuif-the-looper)
on CodePen.

実装方法

実際に使用するコード

<div class="number">
    <span class="value">1992</span>
    <span class="unit">年</span>
</div>
/* 数字 */
.number {
    display: flex;
    align-items: baseline;
    color: #0068b7;
}

.value {
    display: inline-flex;
    font-size: 100px;
    font-weight: 700;
    line-height: 1;
    letter-spacing: 0;
}

.unit {
    margin-left: 8px;
    font-size: 40px;
    font-weight: 700;
    line-height: 1;
}


/* スロット */
.digit {
    display: inline-block;
    width: 0.62em;
    height: 1em;
    overflow: hidden;
    line-height: 1;
    text-align: center;
}

.reel {
    display: block;
    transform: translate3d(0, 0, 0);
    will-change: transform;
}

.digit-num {
    display: block;
    height: 1em;
    line-height: 1;
    font-size: 1em;
    font-weight: inherit;
}


/* アニメーション */
.value.play .reel {
    transform: translate3d(0, var(--offset), 0);
    transition:
        transform 0.8s cubic-bezier(0.12, 0.75, 0.15, 1);
    transition-delay: calc(var(--index) * 0.04s);
}
document.addEventListener('DOMContentLoaded', function () {

    const value = document.querySelector('.value');

    if (!value) {
        return;
    }

    const target = value.textContent.trim();
    const SPINS = 1;

    /*
     * 元の数字を空にする
     */
    value.textContent = '';


    /*
     * 1桁分のリールを作る
     */
    function createReel(targetDigit) {

        const reel = document.createElement('span');

        reel.className = 'reel';


        /*
         * 0〜9を1周
         */
        for (let spin = 0; spin < SPINS; spin++) {

            for (let i = 0; i <= 9; i++) {

                const num = document.createElement('span');

                num.className = 'digit-num';
                num.textContent = i;

                reel.appendChild(num);
            }
        }


        /*
         * 目的の数字まで追加
         */
        for (let i = 0; i <= targetDigit; i++) {

            const num = document.createElement('span');

            num.className = 'digit-num';
            num.textContent = i;

            reel.appendChild(num);
        }


        reel.dataset.target = targetDigit;

        return reel;
    }


    /*
     * 数字を1桁ずつスロット化
     */
    [...target].forEach(function (char, index) {

        const targetDigit = Number(char);

        const digit = document.createElement('span');

        digit.className = 'digit';


        const reel = createReel(targetDigit);

        reel.style.setProperty('--index', index);

        digit.appendChild(reel);
        value.appendChild(digit);

    });


    /*
     * 移動量を計算
     */
    function prepare() {

        const reels = value.querySelectorAll('.reel');

        reels.forEach(function (reel) {

            const digit =
                reel.querySelector('.digit-num');

            const height =
                digit.getBoundingClientRect().height;

            const targetDigit =
                Number(reel.dataset.target);

            const steps =
                SPINS * 10 + targetDigit;

            reel.style.setProperty(
                '--offset',
                '-' + (steps * height) + 'px'
            );

        });
    }


    /*
     * スクロールで表示されたら開始
     */
    const observer = new IntersectionObserver(function (entries) {

        entries.forEach(function (entry) {

            if (!entry.isIntersecting) {
                return;
            }

            prepare();

            requestAnimationFrame(function () {
                value.classList.add('play');
            });

            observer.unobserve(entry.target);

        });

    }, {
        threshold: 0.25
    });


    observer.observe(value);

});

コードの解説

1. 数字を縦に並べる

buildReel()では、0〜9の数字を縦に並べ、さらに最終的な数字まで追加しています。例えば「2」の場合、リールを上方向に移動させることで、最後に「2」が表示領域に収まります。

2. 桁ごとの移動距離を計算する

prepareOffsets()では、数字1つ分の高さをgetBoundingClientRect()で取得し、目的の数字までの移動距離を計算します。フォントサイズに応じて高さを取得するため、固定のピクセル値だけで移動距離を指定する必要がありません。

3. スクロールをきっかけに再生する

IntersectionObserverで数字が画面内に入ったことを検知し、is-slot-inviewクラスを追加します。移動処理そのものはCSSのtransitionに任せることで、JavaScript側では開始タイミングと移動距離の制御に集中できます。また、transition-delayを桁ごとに少しずつずらすことで、各桁が順番に停止するような動きを演出しています。


数字に動きを加えることで、シンプルな数値コンテンツにも目を引く演出を加えることができます。今回のようなスロット風アニメーションは、数字を使った実績紹介や会社情報などにも取り入れやすい表現です。
サイトの雰囲気に合わせて動き方や速度を調整しながら、ぜひ試してみてください。

  • URLをコピーしました!
目次