横スクロール用のカスタムスクロールバーを簡単に実装できるVanilla JSライブラリ「HorizontalScroll」を公開しました

横スクロール用のカスタムスクロールバーを簡単に実装できるVanilla JSライブラリ「HorizontalScroll」を公開しました

横に長いテーブルをスマートフォンで表示するとき、横スクロール自体はCSSのoverflow-x: autoで簡単に実装できます。

ただ、ブラウザ標準のスクロールバーは環境によって見た目が異なり、デザインに合わせづらいことがあります。また、スクロールバーが細かったり、常時表示されなかったりするため、横にスクロールできることがユーザーに伝わりにくい場合もあります。

そこで、横に長いコンテンツへドラッグ可能なカスタムスクロールバーを追加できるVanilla JSライブラリ、@neruco/horizontal-scrollを作成しました。

https://www.npmjs.com/package/@neruco/horizontal-scroll

npm install @neruco/horizontal-scroll

HorizontalScrollは、テーブルだけでなく、カード一覧、画像一覧、コードブロックなど、横方向にはみ出すさまざまなコンテンツに利用できます。外部ライブラリに依存せず、同じページ内への複数設置にも対応しています。

サンプル

See the Pen HorizontalScroll by あお@neruco (@coder_ao) on CodePen.

HorizontalScrollの特徴

主な特徴は次のとおりです。

  • Vanilla JavaScriptで実装
  • 外部ライブラリへの依存なし
  • ページ内への複数設置に対応
  • スクロールバーのつまみをドラッグ可能
  • スクロールバートラックのクリックに対応
  • スマートフォンではコンテンツを直接スワイプ可能
  • テーブル以外の横長コンテンツにも使用可能
  • ESM、CommonJS、IIFE形式に対応
  • TypeScriptの型定義を同梱
  • ResizeObserverによるサイズ変更の自動検知
  • update()destroy()を用意

JavaScript用のフッククラスには、.js-horizontalScrollのようなjs-接頭辞とキャメルケースを採用しています。

基本的なHTML

基本構造は次のとおりです。

<div class="js-horizontalScroll">
  <div class="js-horizontalScroll__viewport">
    <div>
      横に長いコンテンツ
    </div>
  </div>

  <div
    class="js-horizontalScroll__scrollbar"
    aria-hidden="true"
  >
    <div class="js-horizontalScroll__thumb"></div>
  </div>
</div>

それぞれの要素には、次の役割があります。

  • .js-horizontalScroll:コンポーネント全体
  • .js-horizontalScroll__viewport:実際に横スクロールする領域
  • .js-horizontalScroll__scrollbar:カスタムスクロールバーのトラック
  • .js-horizontalScroll__thumb:ドラッグするつまみ

横に長くなるコンテンツは、.js-horizontalScroll__viewportの中に配置します。

npmからインストールする

npmを利用しているプロジェクトでは、次のコマンドでインストールできます。

npm install @neruco/horizontal-scroll

JavaScriptとCSSを読み込み、インスタンスを作成します。

import HorizontalScroll from "@neruco/horizontal-scroll";
import "@neruco/horizontal-scroll/style.css";

const horizontalScroll = new HorizontalScroll(
  ".js-horizontalScroll"
);

ページ内に複数の.js-horizontalScrollがある場合も、一度の初期化ですべての要素へ適用されます。それぞれのスクロール領域は独立して動作します。

テーブルに使用する

横に長い比較表などには、次のように使用できます。

<div class="js-horizontalScroll">
  <div class="js-horizontalScroll__viewport">
    <table class="comparisonTable">
      <thead>
        <tr>
          <th scope="col">項目</th>
          <th scope="col">商品A</th>
          <th scope="col">商品B</th>
          <th scope="col">商品C</th>
          <th scope="col">商品D</th>
        </tr>
      </thead>

      <tbody>
        <tr>
          <th scope="row">価格</th>
          <td>¥1,200</td>
          <td>¥1,500</td>
          <td>¥980</td>
          <td>¥2,100</td>
        </tr>

        <tr>
          <th scope="row">重量</th>
          <td>120g</td>
          <td>150g</td>
          <td>100g</td>
          <td>180g</td>
        </tr>

        <tr>
          <th scope="row">保証期間</th>
          <td>1年</td>
          <td>2年</td>
          <td>6か月</td>
          <td>3年</td>
        </tr>
      </tbody>
    </table>
  </div>

  <div
    class="js-horizontalScroll__scrollbar"
    aria-hidden="true"
  >
    <div class="js-horizontalScroll__thumb"></div>
  </div>
</div>

テーブル自体には、横スクロールが発生する幅を指定します。

.comparisonTable {
  width: 900px;
  border-collapse: collapse;
}

.comparisonTable th,
.comparisonTable td {
  padding: 12px;
  border: 1px solid #ccc;
  white-space: nowrap;
}

コンテンツの幅が表示領域より小さく、横スクロールが不要な場合は、カスタムスクロールバーも非表示になります。

カード一覧に使用する

HorizontalScrollはテーブル専用ではありません。

横並びのカード一覧にも利用できます。

<div class="js-horizontalScroll">
  <div class="js-horizontalScroll__viewport">
    <div class="cardList">
      <article class="cardList__item">
        カード1
      </article>

      <article class="cardList__item">
        カード2
      </article>

      <article class="cardList__item">
        カード3
      </article>

      <article class="cardList__item">
        カード4
      </article>
    </div>
  </div>

  <div
    class="js-horizontalScroll__scrollbar"
    aria-hidden="true"
  >
    <div class="js-horizontalScroll__thumb"></div>
  </div>
</div>
.cardList {
  display: flex;
  gap: 16px;
  width: max-content;
}

.cardList__item {
  width: 260px;
  flex-shrink: 0;
  padding: 24px;
  border: 1px solid #ccc;
}

このほかにも、次のようなコンテンツに使用できます。

  • 画像ギャラリー
  • 横並びのナビゲーション
  • 料金プランの比較表
  • スケジュール表
  • コードブロック
  • 横長の図やフローチャート

scriptタグで読み込む

IIFE版も用意しているため、npmを使用しない静的HTMLやWordPressテーマでも利用できます。IIFE版では、HorizontalScrollがブラウザのグローバル変数として公開されます。

CSSとIIFE版のJavaScriptを読み込みます。

<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/@neruco/horizontal-scroll@1.0.2/dist/horizontal-scroll.css"
>

<script src="https://cdn.jsdelivr.net/npm/@neruco/horizontal-scroll@1.0.2/dist/horizontal-scroll.iife.js"></script>

読み込み後に初期化します。

<script>
  const horizontalScroll = new HorizontalScroll(
    ".js-horizontalScroll"
  );
</script>

WordPressテーマで使用する場合は、ビルド後のファイルをテーマ内へ配置し、wp_enqueue_style()wp_enqueue_script()で読み込めます。

<?php
function theme_enqueue_horizontal_scroll() {
	wp_enqueue_style(
		'horizontal-scroll',
		get_template_directory_uri() . '/assets/css/horizontal-scroll.css',
		array(),
		'1.0.2'
	);

	wp_enqueue_script(
		'horizontal-scroll',
		get_template_directory_uri() . '/assets/js/horizontal-scroll.iife.js',
		array(),
		'1.0.2',
		true
	);
}
add_action( 'wp_enqueue_scripts', 'theme_enqueue_horizontal_scroll' );

初期化用のJavaScriptは、IIFE版より後に実行します。

document.addEventListener("DOMContentLoaded", () => {
  const horizontalScroll = new HorizontalScroll(
    ".js-horizontalScroll"
  );
});

オプションを指定する

第2引数でオプションを指定できます。

const horizontalScroll = new HorizontalScroll(
  ".js-horizontalScroll",
  {
    minThumbWidth: 48,
    smoothTrackClick: false,
  }
);

minThumbWidth

つまみの最小幅をピクセル単位で指定します。

new HorizontalScroll(".js-horizontalScroll", {
  minThumbWidth: 48,
});

初期値は40です。

コンテンツが非常に横長い場合でも、つまみが小さくなりすぎるのを防げます。

smoothTrackClick

スクロールバーのトラックをクリックしたとき、スムーススクロールさせるか指定します。

new HorizontalScroll(".js-horizontalScroll", {
  smoothTrackClick: false,
});

初期値はtrueです。

セレクターを変更する

プロジェクト独自のクラス名にも変更できます。

new HorizontalScroll(".js-cardScroll", {
  viewportSelector: ".js-cardScroll__viewport",
  scrollbarSelector: ".js-cardScroll__scrollbar",
  thumbSelector: ".js-cardScroll__thumb",
});

使用できるオプションには、各要素のセレクター、状態クラス、つまみの最小幅、トラッククリック時の挙動があります。

動的に内容を変更した場合

横スクロール領域の内容をJavaScriptで変更した場合は、update()でスクロールバーを再計算できます。

horizontalScroll.update();

通常のサイズ変更はResizeObserverで監視されますが、DOMを書き換えた直後など、任意のタイミングで再計算したい場合に利用できます。

インスタンスを破棄する

登録されたイベントや監視を解除する場合は、destroy()を実行します。

horizontalScroll.destroy();

destroy()を実行すると、イベントリスナー、ResizeObserver、状態クラス、つまみに設定されたインラインスタイルなどが解除されます。

CSSをカスタマイズする

同梱CSSをそのまま利用できますが、スクロールバーの色や太さはプロジェクトに合わせて変更できます。

.js-horizontalScroll__scrollbar {
  height: 10px;
  margin-top: 16px;
  background-color: #e5e5e5;
  border-radius: 9999px;
}

.js-horizontalScroll__thumb {
  background-color: #666;
  border-radius: inherit;
}

JavaScript側では、スクロールバーとつまみの幅・位置を制御します。

色、余白、角丸、太さなどの見た目はCSS側で自由に調整できます。

まとめ

@neruco/horizontal-scrollは、横に長いコンテンツへカスタムスクロールバーを追加するための軽量なVanilla JSライブラリです。

テーブル専用ではないため、比較表、カード一覧、画像一覧など、さまざまなコンテンツに利用できます。

npmを使うプロジェクトではES Modules版、WordPressや静的HTMLではIIFE版を利用できます。

npm install @neruco/horizontal-scroll

今後も実案件で使用しながら、使いやすい形へ改善していく予定です。