MENU

【JavaScript】自作でスクロールバーを実装する方法

Webサイトでオリジナルデザインの横スクロールバーを作るとき、

・ブラウザ標準のスクロールバーではデザインを自由に調整できない
・プラグインを使用すると後々に不具合が起きやすい

といった問題があります。

そのような場合、JavaScriptでスクロールバーを自作するのがおすすめです。

自作スクロールバーを実装すると、例えば次のようなUIが作れます。

  • デザインに合わせたスクロールバー
  • 左右ボタン付きのスクロールUI
  • スクロールバーのドラッグ操作
  • スマホのタッチ操作対応

この記事では、JavaScriptでスクロールバーを自作する方法を解説します。
また、コードの中でも重要なポイントを中心に解説していきます。

目次

実装イメージ

今回作るスクロールバーは次のような構造です。

  • 横スクロールできるコンテンツ
  • カスタムスクロールバー
  • 左右のスクロールボタン
  • スクロールバーのドラッグ操作

HTML

まずはHTMLです。

<div class="scroll-wrap">

    <div class="scroll-content scroll-sync">
        <a href=""></a>
        <a href=""></a>
        <a href=""></a>
    </div>

    <div class="scroll-bar-wrap scroll-sync">

        <div class="scroll-bar-left-btn"></div>

        <div class="scroll-bar-inner">
            <div class="scroll-bar"></div>
        </div>

        <div class="scroll-bar-right-btn"></div>

    </div>

</div>

各要素の役割

要素役割
scroll-wrapスクロールUI全体
scroll-content横スクロールするコンテンツ
scroll-bar-wrapスクロールバー全体
scroll-bar-innerスクロールバーのトラック
scroll-bar実際に動くバー
scroll-bar-left-btn左スクロールボタン
scroll-bar-right-btn右スクロールボタン

scroll-bar-inner の中に scroll-bar を配置することで、
スクロールバーの移動範囲を制御できる構造になっています。

JavaScript

次にJavaScriptです。

document.querySelectorAll('.scroll-wrap').forEach((wrap) => {

    const content = wrap.querySelector('.scroll-content');
    const bar = wrap.querySelector('.scroll-bar');
    const barInner = wrap.querySelector('.scroll-bar-inner');
    const leftBtn = wrap.querySelector('.scroll-bar-left-btn');
    const rightBtn = wrap.querySelector('.scroll-bar-right-btn');

    let isDragging = false;
    let startX = 0;
    let startLeft = 0;

    function getClientX(e) {
        return e.touches ? e.touches[0].clientX : e.clientX;
    }

    function updateScrollBar() {

        const visibleWidth = content.clientWidth;
        const totalWidth = content.scrollWidth;
        const scrollLeft = content.scrollLeft;

        const maxScroll = totalWidth - visibleWidth;

        const barInnerWidth = barInner.clientWidth;

        const barWidthRatio = visibleWidth / totalWidth;
        const barWidth = barInnerWidth * barWidthRatio;

        bar.style.width = barWidth + 'px';

        const maxBarMove = barInnerWidth - barWidth;

        if (maxScroll <= 0) {
            bar.style.left = '0px';
            return;
        }

        const barLeft = (scrollLeft / maxScroll) * maxBarMove;

        bar.style.left = barLeft + 'px';
    }

    content.addEventListener('scroll', updateScrollBar);
    window.addEventListener('resize', updateScrollBar);

    updateScrollBar();

    function startDrag(e) {

        isDragging = true;
        startX = getClientX(e);
        startLeft = bar.offsetLeft;

        document.body.style.userSelect = 'none';
    }

    function dragMove(e) {

        if (!isDragging) return;

        const visibleWidth = content.clientWidth;
        const totalWidth = content.scrollWidth;
        const barInnerWidth = barInner.clientWidth;

        const barWidth = bar.offsetWidth;

        const maxBarMove = barInnerWidth - barWidth;
        const maxScroll = totalWidth - visibleWidth;

        const moveX = getClientX(e) - startX;

        let newLeft = startLeft + moveX;

        newLeft = Math.max(0, Math.min(newLeft, maxBarMove));

        bar.style.left = newLeft + 'px';

        const scrollRatio = newLeft / maxBarMove;

        content.scrollLeft = scrollRatio * maxScroll;

        if (e.cancelable) e.preventDefault();
    }

    function endDrag() {

        isDragging = false;
        document.body.style.userSelect = '';
    }

    bar.addEventListener('mousedown', startDrag);
    document.addEventListener('mousemove', dragMove);
    document.addEventListener('mouseup', endDrag);

    bar.addEventListener('touchstart', startDrag, { passive:false });
    document.addEventListener('touchmove', dragMove, { passive:false });
    document.addEventListener('touchend', endDrag);

    const scrollStep = () => barInner.clientWidth * 0.08;

    if (leftBtn) {
        leftBtn.addEventListener('click', () => {
            content.scrollBy({
                left: -scrollStep(),
                behavior: 'smooth'
            });
        });
    }

    if (rightBtn) {
        rightBtn.addEventListener('click', () => {
            content.scrollBy({
                left: scrollStep(),
                behavior: 'smooth'
            });
        });
    }

});

重要ポイント① スクロールバーの幅を計算する

スクロールバーの幅は次の式で求めています。

表示幅 / コンテンツ全体幅

コードでは次の部分です。

const barWidthRatio = visibleWidth / totalWidth;
const barWidth = barInnerWidth * barWidthRatio;

つまり

意味
visibleWidth画面に表示されている幅
totalWidthコンテンツ全体の幅

この比率から、スクロールバーの幅を決めています。

重要ポイント② スクロール量からバー位置を計算する

スクロール位置に応じて、バーの位置も更新します。

const barLeft = (scrollLeft / maxScroll) * maxBarMove;

計算式は次の通りです。

スクロール割合 × バーの移動範囲

これによって
コンテンツスクロールとバーの動きが完全に同期します。

重要ポイント③ バーをドラッグしてスクロールする

スクロールバーをドラッグすると、逆にコンテンツをスクロールさせます。

const scrollRatio = newLeft / maxBarMove;
content.scrollLeft = scrollRatio * maxScroll;

処理の流れ

  1. バーの移動量を取得
  2. バー移動割合を計算
  3. その割合でスクロール

こうすることで、バー操作とコンテンツスクロールが連動します。

重要ポイント④ スマホのタッチ操作に対応

スマホでは touchイベント を使います。

function getClientX(e) {
    return e.touches ? e.touches[0].clientX : e.clientX;
}

この関数を使うことで

  • PC → mouseイベント
  • スマホ → touchイベント

両方に対応できます。

重要ポイント⑤ 複数スクロールUIに対応

このコードでは次の処理をしています。

document.querySelectorAll('.scroll-wrap')

これにより

  • 同じスクロールUIを
  • ページ内に複数設置可能

になります。

まとめ

この記事では、JavaScriptで自作スクロールバーを実装する方法を解説しました。

ポイントをまとめると次の通りです。

  • スクロール割合からバーの幅を計算
  • スクロール量からバー位置を計算
  • ドラッグ操作でスクロールを制御
  • スマホのタッチ操作にも対応
  • 複数スクロールUIにも対応可能

カスタムスクロールバーを実装すると、サイトのUIをより自由にデザインできます。

横スクロールUIを作る際は、ぜひ参考にしてみてください。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

Muta Blog運営者はデザインも開発もやるWebエンジニアです。
本ブログでは現場で培ったWebの知識やノウハウを発信しています。
Web制作歴5年/現在ReactやRubyを中心に幅広く学習中/

目次