画面幅をドラッグで動かす「react-resizable-panels」

画面幅をドラッグで動かす「react-resizable-panels」

投稿日: 2025年03月25日

Tips
要約
  • JS Gymのエディタで、エディタの幅を変更できる機能を実装した。
  • react-resizable-panelsライブラリを使用し、パネルのサイズをドラッグで調整可能にした。
  • サイズをlocalStorageに保存するカスタムフックを作成し、再読み込み後も幅が保持されるようにした。

JS Gymの開発で
「エディタの幅を変えられるようにしてほしい」
というフィードバックを複数いただいていまして、実装しました。

ドラッグ&ドロップ機能の一番有名なライブラリとしてReact DnDがありますが、
結構実装が面倒で「使うほどかな?」と思っていたところ

「react-resizable-panels」というちょうど良いライブラリを見つけたので使いました。

公式サイトもわかりやすく、サンプルもあります。
https://react-resizable-panels.vercel.app/

1. react-resizable-panelsの概要

  • パネルをドラッグして画面のサイズ変更できる

  • 横方向・縦方向、どちらにも対応

  • 初期サイズ指定・最小サイズも指定可能

2. インストール

npm install react-resizable-panels

3. 基本の使い方(左右2分割)

import React from "react";
import {
  PanelGroup,
  Panel,
  PanelResizeHandle,
} from "react-resizable-panels";

export default function App() {
  return (
    <div className="h-screen">
      <PanelGroup direction="horizontal">
        <Panel defaultSize={50}>
          <div className="h-full p-4 bg-blue-100">
            <p>左側の画面</p>
          </div>
        </Panel>

        <PanelResizeHandle>
          <div className="w-2 cursor-col-resize bg-gray-300" />
        </PanelResizeHandle>

        <Panel defaultSize={50}>
          <div className="h-full p-4 bg-green-100">
            <p>右側の画面</p>
          </div>
        </Panel>
      </PanelGroup>
    </div>
  );
}
画面幅をドラッグ&ドロップで変える「react-resizable-panels」|ShiftBブログ

ポイント

  • PanelGroupdirection"horizontal" を指定すると左右に分割される

  • PanelResizeHandle が中央のドラッグバー

  • PaneldefaultSize は初期サイズ(%)

4. localStorageでサイズを保存する

調整後に画面を再読み込みしても、幅がキープされるようにしたかったですが、
DBに保存するほどでも無いと思い、localStorageに保存するようにしました。

まず、ローカルストレージを管理するカスタムフックを作ります。

localStorageを使うためのカスタムフック:useLocalStorage.ts

import { useState, useEffect } from "react";

export function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T) => void] {
  const readValue = () => {
    if (typeof window === "undefined") return initialValue;
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  };

  const [storedValue, setStoredValue] = useState<T>(readValue);

  const setValue = (value: T) => {
    setStoredValue(value);
    if (typeof window !== "undefined") {
      localStorage.setItem(key, JSON.stringify(value));
    }
  };

  useEffect(() => {
    const onStorage = (e: StorageEvent) => {
      if (e.key === key && e.newValue) {
        setStoredValue(JSON.parse(e.newValue));
      }
    };
    window.addEventListener("storage", onStorage);
    return () => window.removeEventListener("storage", onStorage);
  }, [key]);

  return [storedValue, setValue];
}

保存機能付きのパネル:App.tsx

export default function App() {
  const [sizes, setSizes] = useLocalStorage<number[]>("panel-sizes", [50, 50]);

  return (
    <div className="h-screen">
      <PanelGroup
        direction="horizontal"
        onLayout={(newSizes) => setSizes(newSizes)}
      >
        <Panel defaultSize={sizes[0]}>
          <div className="h-full p-4 bg-blue-100">
            <p>左側の画面</p>
          </div>
        </Panel>

        <PanelResizeHandle>
          <div className="w-2 cursor-col-resize bg-gray-300" />
        </PanelResizeHandle>

        <Panel defaultSize={sizes[1]}>
          <div className="h-full p-4 bg-green-100">
            <p>右側の画面</p>
          </div>
        </Panel>
      </PanelGroup>
    </div>
  );
}

まとめ

サイドバーの幅調整とか、いろんなところにサクッと導入できそうな感じなので、
おすすめのライブラリです。

シェア!

Threads
icon
ぶべ
Webの修行中 / 個人開発奮闘中 / ベンチプレス110kg / Reactの先生
Loading...
記事一覧に戻る
Threads
0