SimpleBar.jsでスクロールバーを美しく!【実装レシピ&サンプル付き】

SimpleBar.jsとは?

  • ネイティブスクロールを維持しつつ、カスタム見た目のスクロールバーを実現する軽量ライブラリ
  • スクロール機能自体はブラウザに任せるため、パフォーマンスとアクセシビリティに優れます
  • 既存要素に data-simplebar を付けるだけでも動作

導入方法

1) CDNで手早くお試し

<!-- head内 -->
<link rel="stylesheet" href="https://unpkg.com/simplebar@latest/dist/simplebar.min.css">
<script defer src="https://unpkg.com/simplebar@latest/dist/simplebar.min.js"></script>

2) npmでプロジェクトに導入

npm i simplebar
# or
yarn add simplebar
// bundler 環境(例:Vite/webpack)
import 'simplebar/dist/simplebar.min.css';
import SimpleBar from 'simplebar';

基本:属性を付けるだけ(高さは必須)

<div class="card" data-simplebar>
  <h3>ニュース</h3>
  <p>スクロールテキスト…</p>
  <p>スクロールテキスト…</p>
  <p>スクロールテキスト…</p>
</div>

<style>
.card{
  width: 320px;
  height: 180px;    /* ← これがないとスクロール領域が生まれない */
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 12px;
  overflow: auto;   /* 基本は auto でOK(SimpleBarがラップしてくれる) */
}
</style>

これだけで、ネイティブ同等の操作感を残したまま、見た目が整ったスクロールバーに。

オプションを使う:自動非表示や最小サイズ

data 属性で指定(CDN派におすすめ)

<div class="log" 
     data-simplebar
     data-simplebar-auto-hide="false" 
     data-simplebar-scrollbar-min-size="20">
  <!-- 長文 -->
</div>

<style>
.log{
  width: 420px;
  height: 240px;
  padding: 10px;
  border: 1px solid #ccc;
}
</style>

JSで生成(npm or 細かく制御したい場合)

import SimpleBar from 'simplebar';

const el = document.querySelector('.log');
const sb = new SimpleBar(el, {
  autoHide: false,            // ホバー時のみ表示→falseで常時
  scrollbarMinSize: 20,       // ハンドルの最小サイズ
  scrollbarMaxSize: 48        // ハンドルの最大サイズ
});

// インスタンスにアクセス(後述の再計算などで使う)
console.log(sb);

スクロール位置の制御(「最下部へ」など)

SimpleBarは内部にスクロール用のラッパーを持ちます。
インスタンスからスクロール可能要素を取得して操作します。

// 例:最下部へ移動
function scrollToBottom(sbInstance){
  const el = sbInstance.getScrollElement();
  el.scrollTop = el.scrollHeight;
}

// 例:先頭へ
function scrollToTop(sbInstance){
  sbInstance.getScrollElement().scrollTop = 0;
}

CDN(data属性)で自動初期化した場合でも、要素からインスタンスを参照できます:

const el = document.querySelector('[data-simplebar]');
const sb = SimpleBar.instances.get(el); // 既存インスタンスを取得
scrollToBottom(sb);

動的コンテンツの追加と再計算

通常はMutationObserverで検知してくれますが、一気にDOMを書き換えた後などは手動再計算が確実です。

// 例:ログを追加してから再計算
function appendLog(sbInstance, text){
  const content = sbInstance.getContentElement(); // コンテンツ領域
  const p = document.createElement('p');
  p.textContent = text;
  content.appendChild(p);

  // レイアウトが大きく変わった際に明示的に再計算
  sbInstance.recalculate();
}

ページ全体に適用する場合のコツ

body そのものに適用するのではなく、高さ固定のラッパーを用意するのが定石です。

<body>
  <div class="page-scroll" data-simplebar>
    <!-- ページの中身全部 -->
  </div>
</body>

<style>
html, body { height: 100%; }
.page-scroll{
  height: 100%;   /* これで“ページ全体のスクロール”に */
  overflow: auto;
}
</style>

よく使うカスタマイズ(見た目の調整)

SimpleBarはCSSで見た目を変えられます。
ハンドル色・太さ・角丸などを調整してブランドに寄せましょう。

/* ハンドル(つまみ) */
.simplebar-scrollbar:before{
  content: '';
  position: absolute;
  left: 0; right: 0;
  top: 0; bottom: 0;
  opacity: 0.6;                 /* 濃さ */
  border-radius: 6px;           /* 角丸 */
  background: #999;             /* 色 */
}

/* トラック(バーのレール) */
.simplebar-track.simplebar-vertical{
  width: 10px;                  /* 太さ */
  background: transparent;      /* うっすら色を付けたいなら rgba(...) */
  margin-right: 2px;            /* 余白調整 */
}

/* ホバーで濃くする */
.simplebar-track:hover .simplebar-scrollbar:before{
  opacity: 0.95;
}

/* 横スクロール時のトラック(必要なら) */
.simplebar-track.simplebar-horizontal{
  height: 10px;
}

メモ:OSやブラウザによってはネイティブのオーバーレイスクロールバーが上に重なることがあります。SimpleBarを使う要素では overflow: auto を維持しつつ、必ず高さ(もしくはmax-height)を定義して期待どおりの領域を作りましょう。

便利スニペット集

1) 「ログが来たら自動で最下部へ」+手動スクロールを尊重

const el = document.querySelector('.chat');
const sb = SimpleBar.instances.get(el) || new SimpleBar(el);

let stickToBottom = true;

const scrollEl = sb.getScrollElement();
scrollEl.addEventListener('scroll', () => {
  const nearBottom = (scrollEl.scrollHeight - scrollEl.scrollTop - scrollEl.clientHeight) < 8;
  stickToBottom = nearBottom;
});

function pushMessage(text){
  const p = document.createElement('p');
  p.textContent = text;
  sb.getContentElement().appendChild(p);
  sb.recalculate();
  if(stickToBottom) scrollEl.scrollTop = scrollEl.scrollHeight;
}

2) 横スクロールのギャラリー

<div class="gallery" data-simplebar>
  <div class="row">
    <img src="a.jpg"><img src="b.jpg"><img src="c.jpg">
  </div>
</div>

<style>
.gallery{
  max-width: 100%;
  overflow: auto;
  white-space: nowrap;     /* 横並び */
}
.gallery .row img{
  display: inline-block;
  width: 240px;
  height: 160px;
  object-fit: cover;
  margin-right: 12px;
  border-radius: 8px;
}
.simplebar-track.simplebar-horizontal{ height: 8px; }
</style>

3) 「読み込み中はスクロール無効 → 完了後に有効」

const container = document.querySelector('.panel');
container.style.pointerEvents = 'none';     // 誤操作を防ぐ
container.style.overflow = 'hidden';        // 念のため

// データ取得など非同期処理
fetch('/api/list').then(r => r.json()).then(items => {
  const sb = SimpleBar.instances.get(container) || new SimpleBar(container);
  const content = sb.getContentElement();
  items.forEach(t => {
    const li = document.createElement('div');
    li.textContent = t.title;
    content.appendChild(li);
  });
  sb.recalculate();

  container.style.pointerEvents = '';
  container.style.overflow = 'auto';
});

Reactでの最短実装(参考)

公式が simplebar-react も提供しています。

npm i simplebar-react simplebar
import 'simplebar-react/dist/simplebar.min.css';
import SimpleBar from 'simplebar-react';

export default function Panel(){
  return (
    <SimpleBar style={{ maxHeight: 300 }} autoHide={false}>
      <div>行1</div>
      <div>行2</div>
      <div>行3</div>
    </SimpleBar>
  );
}

よくあるハマりどころ

  1. 高さが未指定
    → スクロール領域が生まれず、バーが出ない。heightmax-height を付与。
  2. 入れ子のoverflow
    → 親にも子にも overflow があると意図せず二重スクロールに。適用範囲を明確に。
  3. body 直適用
    → 非推奨。100% 高さのラッパー を用意してそこにSimpleBar。
  4. 動的更新が大量
    → 原則自動検知。大規模変更後は recalculate() を呼ぶと安定。
  5. モバイル慣性スクロール
    → iOS系はネイティブ慣性が効く。必要に応じて overscroll-behavior などでバウンス制御。

まとめ

  • SimpleBarはネイティブの強み+綺麗な見た目を両立
  • data-simplebar を付けるだけでOK、高さ指定がキモ
  • オプションやCSSで表示タイミング・太さ・色を自在にカスタム
  • 動的DOM変更時は recalculate() を覚えておくと安心