9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

React + SimpleBar: スクロールバーのスタイルをカスタマイズする

Last updated at Posted at 2020-08-24

SimpleBarはスクロールバーをカスタマイズするライブラリです。スクロールバーを独自につくるのではなく、CSSのスタイルを割り当てるので、おかしな挙動は起こらず、ネイティブなスクロールのパフォーマンスが保たれます。あくまで、スクロールバーの見栄えを変えるだけです。

SimpleBar logo

デザインは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■ヘッダと左カラムにメインコンテンツで組み立てられたページ

2007001_002.png

以下のコードは、アプリケーション(src/App.js)に、それぞれヘッダ(src/components/Header.js)と左カラム(src/components/LeftColumn.js)およびメインコンテンツ(src/components/MainContents.js)を静的にレイアウトしたモジュールの中身です。src/App.jssimplebar/dist/simplebar.min.cssを読み込んでいます。

SimplebarReactのJavaScriptライブラリをimportするのは、スクロールバーをカスタマイズする左カラムのモジュール(src/components/LeftColumn.js)です。なお、リストの連番項目は、メソッドArray.from()Array.prototype.map()でつくりました。興味のある方は「ECMAScript 6のArrayに関わる構文を試す」をお読みください。

src/App.js
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;
src/components/Header.js
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;
src/components/LeftColumn.js
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;
src/components/MainContents.js
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)。

src/App.css
#header {
  position: fixed;
  top: 0;
}

図002■ページ上部をヘッダが覆ってしまう

2007001_003.png

<body>要素のpaddingまたはmarginは、ヘッダの高さ分下げなければならないのです。もっとも、高さはウィンドウ幅やレスポンシブの設定によって変わるかもしれません。動的に定めるべきでしょう。

要素の高さはelement.clientHeightで得られます。要素を参照するフックはuseRefです(src/App.js)。参照(header)は、プロパティでヘッダのコンポーネント(src/components/Header.js)に渡します。ただし、element.clientHeightは、読み取り専用プロパティであることにご注意ください。高さの設定には、要素(<div>)のstyle属性を用います。

src/App.js
// 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>
  );
}
src/components/Header.js
// 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)。

src/App.css
#left-column {
  overflow: auto;
}
src/components/LeftColumn.js
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>
  );
};

そして、スクロールバーを表示するには、要素に高さを定めなければなりません。

src/App.css
#left-column {
  overflow: auto;
  height: 400px;  /* 高さを定める */
}

とはいえ、高さを決め打ちは避けたいところです。すでに、ヘッダの高さはとれるのですから、ブラウザウィンドウのビューポートの高さ(window.innerHeight)から差し引けば、左カラムの高さ(leftColumnHeight)は求まります。

src/App.js
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>
  );
}
src/components/LeftColumn.js
// 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のスクロールバーが自動表示される

2008002_001.png

ページ全体をスクロールしたときの不具合を直す

まだ少し、不具合が残っています。ページ全体を下にスクロールしたとき、左カラムがせり上がって、ヘッダにかぶってしまうことです(図004)。

図004■ページを下にスクロールすると左カラムがヘッダにかぶる

2008002_002.png

左カラム(src/components/LeftColumn.js)の垂直位置は固定しなければなりません。やり方は、前述「ヘッダを上部に固定する」と同じです。ただ、CSS(src/App.css)でなく、style属性で定めることにしました。

src/App.js
function App() {

  return (
    <div className="App" style={{ paddingTop: headerHeight }}>

      <div className="container-fluid d-flex px-0">
        <LeftColumn
          headerHeight={headerHeight}

        />

      </div>
    </div>
  );
}
src/components/LeftColumn.js
// const LeftColumn = ({ leftColumnHeight }) => {
const LeftColumn = ({ headerHeight, leftColumnHeight }) => {
  return (
    <SimpleBar

      style={{
        position: "fixed",
        top: headerHeight,

      }}
    >

    </SimpleBar>
  );
};

もちろん、前述「ヘッダを上部に固定する」と同じように、左カラムがメインコンテンツにかぶってしまいます(図005)。

図005■メインコンテンツが左カラムに隠れてしまう

2008002_003.png

「ヘッダを上部に固定する」と同じ考え方で、メインコンテンツの左端をカラムの幅だけ右に寄せればよいはずです。けれど、つぎのコードではメインコンテンツの位置がまったく動きません。

src/App.js
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>
  );
}
src/components/MainContents.js
// const MainContents = () => {
const MainContents = ({ leftColumnWidth }) => {
  return (
    // <main className="px-4 py-2">
    <main className="px-4 py-2" style={{ marginLeft: leftColumnWidth }}>

    </main>
  );
};
src/components/LeftColumn.js
// const LeftColumn = ({ headerHeight, leftColumnHeight }) => {
const LeftColumn = ({headerHeight, leftColumnHeight, leftColumnRef }) => {
  return (
    <SimpleBar

      ref={leftColumnRef}

    >

    </SimpleBar>
  );
};

SimpleBarコンポーネントをラップする

調べてみると、SimpleBarコンポーネントのclientWidthプロパティ値が0です。SimpleBarは、あくまでスクロールバーのスタイルを整えるためのラッパーだからでしょう。

そこで、Simplebarコンポーネントをつぎのように<div>要素で包み、スクロールバーに用いる以外の属性はすべてこの要素に移します。こうすることで、カラムの要素の幅(clientWidth)が正しく得られるのです。メインコンテンツの左端は、カラムの右端に揃います。

src/components/LeftColumn.js
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)。

src/App.css
.simplebar-scrollbar::before {
  background: linear-gradient(darkblue, skyblue);
}

図006■メインコンテンツの位置が正しく定まってスライダはグラデーションになった

2008002_004.png
>> CodeSandboxへ

冒頭の標準JavaScriptのサンプルと同じページをSimplebarReactでつくり、CodeSandboxに掲げました。また、Githubでもソースをご覧いただけます。

 

9
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?