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;処理の流れ
- バーの移動量を取得
- バー移動割合を計算
- その割合でスクロール
こうすることで、バー操作とコンテンツスクロールが連動します。
重要ポイント④ スマホのタッチ操作に対応
スマホでは 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を作る際は、ぜひ参考にしてみてください。
