Frontend Lab
← 回到所有模式
PG07📐 Layout · 版型分割

Resizable Panels

可拖曳分割面板

難度 ★★★★★標籤 resize · split · drag3 min read

模式簡述

Resizable Panels:可拖曳調整大小的分割面板。VS Code、設計工具的標配。要記住使用者偏好。

// editor.tsx

export function Editor() {

return <Pane />;

}

Preview

Hello, Pane.

50% / 右 50%

對半|左右各 50%

Claude Code Prompt

💬 Claude Code Prompt
參考:PG07 resizable-panels
組件:ResizablePanels.tsx

Resizable Panels:可拖曳調整大小的分割面板。VS Code、設計工具的標配。要記住使用者偏好。

限制:
- 沒做最小寬度
- 沒記住寬度
- 手把太細

完整程式碼

ResizablePanels.tsx
"use client";
import { useEffect, useRef, useState } from "react";

type State = "balanced" | "left-wide" | "right-wide";

export function ResizablePanels({ state = "balanced" }: { state?: State }) {
const pct = state === "left-wide" ? 72 : state === "right-wide" ? 28 : 50;
const [split, setSplit] = useState(pct);
const ref = useRef<HTMLDivElement>(null);
const [drag, setDrag] = useState(false);
useEffect(() => setSplit(pct), [pct]);

useEffect(() => {
  function onMove(e: PointerEvent) {
    if (!drag || !ref.current) return;
    const r = ref.current.getBoundingClientRect();
    const x = ((e.clientX - r.left) / r.width) * 100;
    setSplit(Math.max(15, Math.min(85, x)));
  }
  function onUp() {
    setDrag(false);
  }
  if (drag) {
    window.addEventListener("pointermove", onMove);
    window.addEventListener("pointerup", onUp);
    return () => {
      window.removeEventListener("pointermove", onMove);
      window.removeEventListener("pointerup", onUp);
    };
  }
}, [drag]);

return (
  <div className="w-full max-w-2xl">
    <div
      ref={ref}
      className="relative flex h-56 overflow-hidden rounded-xl border border-border select-none"
    >
      <div
        className="overflow-hidden bg-zinc-800 p-3 font-mono text-[10px] text-zinc-400"
        style={{ width: `${split}%` }}
      >
        <p className="text-zinc-500">// editor.tsx</p>
        <p>export function Editor() {`{`}</p>
        <p>  return &lt;Pane /&gt;;</p>
        <p>{`}`}</p>
      </div>
      <div
        onPointerDown={() => setDrag(true)}
        className="relative w-1.5 shrink-0 cursor-ew-resize bg-border hover:bg-lab-accent/50"
      >
        <div className="absolute left-1/2 top-1/2 h-10 w-1 -translate-x-1/2 -translate-y-1/2 rounded-full bg-foreground/40" />
      </div>
      <div
        className="flex-1 bg-background p-3 font-mono text-[10px] text-muted-foreground"
        style={{ width: `${100 - split}%` }}
      >
        <p className="text-foreground">Preview</p>
        <div className="mt-2 grid h-32 place-items-center rounded-md border border-border bg-muted/30">
          <span className="text-[11px] text-foreground">Hello, Pane.</span>
        </div>
      </div>
    </div>
    <p className="mt-2 text-center font-mono text-[10px] text-muted-foreground">
{Math.round(split)}% / 右 {Math.round(100 - split)}%
    </p>
  </div>
);
}

何時用

  • ✅ 編輯器與預覽並排
  • ✅ Three column 工具型介面
  • ✅ 需要使用者自訂版面

何時別用

  • ❌ 手機端(沒空間 resize)
  • ❌ 純展示內容(沒必要 resize)
  • ❌ 極簡介面(增加複雜度)

我踩過的坑

  1. 沒做最小寬度:拖到 0 panel 消失但不知道怎麼救回。設 min-width: 200px。

  2. 沒記住寬度:每次刷新都重置。存 localStorage。

  3. 手把太細:1px 拖曳區手指找不到。要 8-12px 的 hit area + 視覺 indicator。

相關模式

  • PC03

搜尋

按 ⌘K 隨時開啟