SimpleBarはスクロールバーをカスタマイズするライブラリです。スクロールバーを独自につくるのではなく、CSSのスタイルを割り当てるので、おかしな挙動は起こらず、ネイティブなスクロールのパフォーマンスが保たれます。あくまで、スクロールバーの見栄えを変えるだけです。
デザインはCSSで定める
SimpleBarは純粋なCSSでスクロールバーのスタイルを定めます。CSSで与えられるスタイルでさえあれば、自由にカスタマイズできるということです。また、macOSとWindowsで同じ見た目になるのも大きな魅力といえます。
軽量なライブラリ
6KBのとても軽いライブラリです。JavaScriptはスクロールの動きそのものには触れません。ネイティブな動きとパフォーマンスが得られます。
モダンブラウザをサポート
ChromeとFirefox、Safariなどのモダンブラウザに加え、Internet Explorer 11をサポートします。
ライブラリの概要はドキュメントとデモページでお確かめください。本稿と同じタイトルの「JavaScript + SimpleBar: スクロールバーのスタイルをカスタマイズする」でつくったつぎの作例は、標準のJavaScriptコードでSimpleBarのスタイルを割り当てました。「Left Column」にマウスポインタを重ねると、グラデーションのスクロールバーが現れます。今回のお題は、React用のSimplebarReactで同じサンプルをつくることです。
See the Pen JavaScript + SimpleBar: Customizing scrollbar style by Fumio Nonaka (@FumioNonaka) on CodePen.
本稿の作例は、Create React Appのひな形アプリケーションをもとにつくります。ひな形のつくり方については「Reactアプリケーションのひな形をつくる」をお読みください。
インストール
まず、SimpleBarをインストールします(「Installation」)。SimplebarReactもSimpleBarのCSSを使うからです。npmまたはyarnでインストールしてください。
npm install simplebar --save
yarn add simplebar
つぎに、SimplebarReactのインストールです。やはり、npmまたはyarnで行います(「Installation」)。
npm install simplebar-react --save
yarn add simplebar-react
基本となるページの組み立て
ページを構成する要素は大きく3つ、ヘッダと左カラム、そしてメインコンテンツです(図001)。また、Bootstrap 4.5を用いました。ただし、本稿ではCSSの説明は基本的に省き、SimpleBarの扱いに関わる定めだけ解説することにします。確かめたい方は、最後に掲げるCodeSandboxのサンプルまたはGithubのソースをご覧ください。
図001■ヘッダと左カラムにメインコンテンツで組み立てられたページ
以下のコードは、アプリケーション(src/App.js
)に、それぞれヘッダ(src/components/Header.js
)と左カラム(src/components/LeftColumn.js
)およびメインコンテンツ(src/components/MainContents.js
)を静的にレイアウトしたモジュールの中身です。src/App.js
がsimplebar/dist/simplebar.min.css
を読み込んでいます。
SimplebarReactのJavaScriptライブラリをimport
するのは、スクロールバーをカスタマイズする左カラムのモジュール(src/components/LeftColumn.js
)です。なお、リストの連番項目は、メソッドArray.from()
とArray.prototype.map()
でつくりました。興味のある方は「ECMAScript 6のArrayに関わる構文を試す」をお読みください。
import React from "react";
import "bootstrap/dist/css/bootstrap.min.css";
import "simplebar/dist/simplebar.min.css";
import "./App.css";
import Header from "./components/Header";
import MainContents from "./components/MainContents";
import LeftColumn from "./components/LeftColumn";
function App() {
return (
<div className="App">
<Header />
<div className="container-fluid d-flex px-0">
<LeftColumn />
<MainContents />
</div>
</div>
);
}
export default App;
import React from "react";
const Header = () => {
return (
<header id="header" className="text-white bg-primary w-100 p-2 d-flex">
<h1>Header</h1>
</header>
);
};
export default Header;
import React from "react";
const LeftColumn = () => {
return (
<div id="left-column" className="bg-light p-2">
<h3>Left column</h3>
<ul id="list" className="pl-4">
{Array.from(new Array(20), (_, index) => (
<li key={index}>item {String(index + 1).padStart(2, 0)}</li>
))}
</ul>
</div>
);
};
export default LeftColumn;
import React from "react";
const MainContents = () => {
return (
<main className="px-4 py-2">
<h2>Main contents</h2>
<p>
<!-- [中略] -->
</p>
</main>
);
};
export default MainContents;
ヘッダを上部に固定する
まずは、ヘッダをページ上部に固定するCSSの設定です(src/App.css
)。位置はposition
プロパティにfixed
を与えて固定します。具体的な置き場所は上部なのでtop: 0
です。すると、<body>
要素の領域に含まれなくなるので、そのままではページの上部がかぶって隠れてしまいます(図003)。
#header {
position: fixed;
top: 0;
}
図002■ページ上部をヘッダが覆ってしまう
<body>
要素のpadding
またはmargin
は、ヘッダの高さ分下げなければならないのです。もっとも、高さはウィンドウ幅やレスポンシブの設定によって変わるかもしれません。動的に定めるべきでしょう。
要素の高さはelement.clientHeight
で得られます。要素を参照するフックはuseRef
です(src/App.js
)。参照(header
)は、プロパティでヘッダのコンポーネント(src/components/Header.js
)に渡します。ただし、element.clientHeight
は、読み取り専用プロパティであることにご注意ください。高さの設定には、要素(<div>
)のstyle
属性を用います。
// import React from "react";
import React, { useEffect, useRef, useState } from "react";
function App() {
const [headerHeight, setHeaderHeight] = useState(0);
const header = useRef(null);
useEffect(() => {
const _header = header.current;
const setLayout = () => {
setHeaderHeight(_header.clientHeight);
}
window.addEventListener("resize", setLayout);
setLayout();
}, []);
return (
// <div className="App">
<div className="App" style={{ paddingTop: headerHeight }}>
{/* <Header /> */}
<Header headerRef={header} />
</div>
);
}
// const Header = () => {
const Header = ({ headerRef }) => {
return (
<header
id="header"
ref={headerRef}
>
<h1>Header</h1>
</header>
);
};
SimpleBarを組み込む
SimpleBarを使う要素には、overflow
プロパティにauto
を与えてください(src/App.css
)。そのうえで、SimpleBarを設定する要素は<SimpleBar>
に置き替えます(src/components/LeftColumn.js
)。
#left-column {
overflow: auto;
}
import SimpleBar from "simplebar-react";
const LeftColumn = () => {
return (
// <div id="left-column" className="bg-light p-2">
<SimpleBar id="left-column" className="bg-light p-2">
{/* </div> */}
</SimpleBar>
);
};
そして、スクロールバーを表示するには、要素に高さを定めなければなりません。
#left-column {
overflow: auto;
height: 400px; /* 高さを定める */
}
とはいえ、高さを決め打ちは避けたいところです。すでに、ヘッダの高さはとれるのですから、ブラウザウィンドウのビューポートの高さ(window.innerHeight
)から差し引けば、左カラムの高さ(leftColumnHeight
)は求まります。
function App() {
const [leftColumnHeight, setLeftColumnHeight] = useState(0);
useEffect(() => {
const setLayout = () => {
setLeftColumnHeight(window.innerHeight - _header.clientHeight);
};
}, []);
return (
<div className="App" style={{ paddingTop: headerHeight }}>
<div className="container-fluid d-flex px-0">
{/* <LeftColumn /> */}
<LeftColumn leftColumnHeight={leftColumnHeight} />
</div>
</div>
);
}
// const LeftColumn = () => {
const LeftColumn = ({ leftColumnHeight }) => {
return (
// <div id="left-column" className="bg-light p-2">
<SimpleBar
id="left-column"
className="bg-light p-2"
style={{
height: leftColumnHeight,
}}
>
{/* </div> */}
</SimpleBar>
);
};
これで、ウィンドウに合わせて左カラムの高さが定まり、スクロールバーは自動表示されるようになりました(図003)。
図003■SimpleBarのスクロールバーが自動表示される
ページ全体をスクロールしたときの不具合を直す
まだ少し、不具合が残っています。ページ全体を下にスクロールしたとき、左カラムがせり上がって、ヘッダにかぶってしまうことです(図004)。
図004■ページを下にスクロールすると左カラムがヘッダにかぶる
左カラム(src/components/LeftColumn.js
)の垂直位置は固定しなければなりません。やり方は、前述「ヘッダを上部に固定する」と同じです。ただ、CSS(src/App.css
)でなく、style
属性で定めることにしました。
function App() {
return (
<div className="App" style={{ paddingTop: headerHeight }}>
<div className="container-fluid d-flex px-0">
<LeftColumn
headerHeight={headerHeight}
/>
</div>
</div>
);
}
// const LeftColumn = ({ leftColumnHeight }) => {
const LeftColumn = ({ headerHeight, leftColumnHeight }) => {
return (
<SimpleBar
style={{
position: "fixed",
top: headerHeight,
}}
>
</SimpleBar>
);
};
もちろん、前述「ヘッダを上部に固定する」と同じように、左カラムがメインコンテンツにかぶってしまいます(図005)。
図005■メインコンテンツが左カラムに隠れてしまう
「ヘッダを上部に固定する」と同じ考え方で、メインコンテンツの左端をカラムの幅だけ右に寄せればよいはずです。けれど、つぎのコードではメインコンテンツの位置がまったく動きません。
function App() {
const [leftColumnWidth, setLeftColumnWidth] = useState(0);
const leftColumn = useRef(null);
useEffect(() => {
const _leftColumn = leftColumn.current;
const setLayout = () => {
setLeftColumnWidth(_leftColumn.clientWidth);
};
}, [leftColumn]);
return (
<div className="App" style={{ paddingTop: headerHeight }}>
<div className="container-fluid d-flex px-0">
<LeftColumn
leftColumnRef={leftColumn}
/>
{/* <MainContents /> */}
<MainContents leftColumnWidth={leftColumnWidth} />
</div>
</div>
);
}
// const MainContents = () => {
const MainContents = ({ leftColumnWidth }) => {
return (
// <main className="px-4 py-2">
<main className="px-4 py-2" style={{ marginLeft: leftColumnWidth }}>
</main>
);
};
// const LeftColumn = ({ headerHeight, leftColumnHeight }) => {
const LeftColumn = ({headerHeight, leftColumnHeight, leftColumnRef }) => {
return (
<SimpleBar
ref={leftColumnRef}
>
</SimpleBar>
);
};
SimpleBarコンポーネントをラップする
調べてみると、SimpleBar
コンポーネントのclientWidth
プロパティ値が0です。SimpleBarは、あくまでスクロールバーのスタイルを整えるためのラッパーだからでしょう。
そこで、Simplebar
コンポーネントをつぎのように<div>
要素で包み、スクロールバーに用いる以外の属性はすべてこの要素に移します。こうすることで、カラムの要素の幅(clientWidth
)が正しく得られるのです。メインコンテンツの左端は、カラムの右端に揃います。
const LeftColumn = ({ headerHeight, leftColumnHeight, leftColumnRef }) => {
return (
<div
id="left-column"
ref={leftColumnRef}
className="bg-light p-2"
style={{
position: "fixed",
top: headerHeight,
}}
>
<SimpleBar
/* id="left-column"
ref={leftColumnRef}
className="bg-light p-2" */
style={{
/* position: "fixed",
top: headerHeight, */
height: leftColumnHeight,
}}
>
</SimpleBar>
</div>
);
};
CSSでスクロールバーのスタイルを変える
SimpleBarのスクロールバーのスタイルは、CSSにより定められています。つまり、見栄えがCSSで変えられるということです。ここでは、スクロールさせるスライダのカラーを、つぎのCSSでグラデーションにしてみましょう(図006)。
.simplebar-scrollbar::before {
background: linear-gradient(darkblue, skyblue);
}
図006■メインコンテンツの位置が正しく定まってスライダはグラデーションになった
冒頭の標準JavaScriptのサンプルと同じページをSimplebarReactでつくり、CodeSandboxに掲げました。また、Githubでもソースをご覧いただけます。