Infinite Drag Canvas
無限拖曳平移的作品畫布,把卡片散佈於無邊界空間,可拖曳(含慣性)、鍵盤平移與一鍵回中。
把這個效果裝進你的專案
在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。
npx webberui@latest add "https://webberui.com/r/infinite-drag-canvas.json?t=<安裝 token>"Infinite Drag Canvas 是用 React、Tailwind CSS 與 Motion 做的無限畫布(infinite canvas)元件:把卡片放在以畫布中心為原點的世界座標上,使用者可以拖曳平移、放開後慣性滑行,背景點陣跟著移動,看起來沒有邊界。
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
<InfiniteDragCanvas/>
完整範例包含預覽的排版與資料。先安裝元件,再把範例檔貼入你的專案。
適用情境
- 設計師或工作室的作品集首頁,把作品散放在一大片可拖曳瀏覽的空間。
- 情緒板、靈感牆或活動照片牆,內容數量多、不想用固定網格排列。
- 產品發表或品牌活動頁的互動展示區,讓訪客自己拖曳探索。
- 白板式的概念圖、流程草圖展示(僅瀏覽,元件不提供縮放與編輯)。
安裝
npx webberui@latest add "https://webberui.com/r/infinite-drag-canvas.json?t=<安裝 token>"或在 components.json 設定 registries 後,改用 @webberui/infinite-drag-canvas 安裝。
使用
import {
InfiniteDragCanvas,
CanvasItem,
} from "@/components/ui/infinite-drag-canvas";
<InfiniteDragCanvas className="h-[400px]">
<CanvasItem x={-120} y={-40}>
<div className="rounded-xl border bg-white p-4 shadow">Aurora</div>
</CanvasItem>
<CanvasItem x={140} y={80}>
<div className="rounded-xl border bg-white p-4 shadow">Drift</div>
</CanvasItem>
</InfiniteDragCanvas>;畫布中心即世界座標原點 (0, 0);CanvasItem 以自身中心對齊所給的 x / y,並隨畫布一起平移。
範例
用資料散佈作品卡
把每件作品的座標存在資料裡,再對應成 CanvasItem。拖曳進行中卡片會停用指標事件,卡片內的連結不會在放開滑鼠時被誤觸。
import {
InfiniteDragCanvas,
CanvasItem,
} from "@/components/ui/infinite-drag-canvas";
const works = [
{ id: "brand", title: "品牌識別改版", href: "/work/brand", x: -260, y: -120 },
{ id: "app", title: "記帳 App 介面", href: "/work/app", x: 40, y: -180 },
{ id: "site", title: "電商官網", href: "/work/site", x: 220, y: 60 },
{ id: "poster", title: "展覽主視覺", href: "/work/poster", x: -140, y: 160 },
];
export function PortfolioCanvas() {
return (
<InfiniteDragCanvas className="h-[480px]">
{works.map((w) => (
<CanvasItem key={w.id} x={w.x} y={w.y}>
<a
href={w.href}
className="block w-48 rounded-xl border bg-white p-4 text-sm font-medium shadow-sm"
>
{w.title}
</a>
</CanvasItem>
))}
</InfiniteDragCanvas>
);
}受控模式:跳到指定作品
傳入 offset 與 onOffsetChange 由外部管理平移位置。要讓座標 (x, y) 的物件位在畫布中央,把 offset 設成 { x: -x, y: -y };受控更新會直接跳到該位置。
"use client";
import * as React from "react";
import {
InfiniteDragCanvas,
CanvasItem,
type CanvasOffset,
} from "@/components/ui/infinite-drag-canvas";
export function JumpToWork({ works }: { works: { id: string; title: string; x: number; y: number }[] }) {
const [offset, setOffset] = React.useState<CanvasOffset>({ x: 0, y: 0 });
return (
<div className="space-y-3">
<div className="flex gap-2">
{works.map((w) => (
<button key={w.id} onClick={() => setOffset({ x: -w.x, y: -w.y })}>
{w.title}
</button>
))}
</div>
<InfiniteDragCanvas offset={offset} onOffsetChange={setOffset}>
{works.map((w) => (
<CanvasItem key={w.id} x={w.x} y={w.y}>
<div className="rounded-xl border bg-white p-4 shadow-sm">{w.title}</div>
</CanvasItem>
))}
</InfiniteDragCanvas>
</div>
);
}調整背景與操作手感
gridSize 調整點陣間距、showGrid={false} 關閉點陣;momentum={false} 讓放開後立即停下;keyboardStep 控制方向鍵每次平移的距離;showRecenter={false} 可隱藏右下角的回中按鈕。
<InfiniteDragCanvas
className="h-[560px] bg-white dark:bg-neutral-900"
gridSize={24}
momentum={false}
keyboardStep={120}
showRecenter={false}
>
{/* ... */}
</InfiniteDragCanvas>Props
InfiniteDragCanvas
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
children | ReactNode | — | 放入 CanvasItem 子元件 |
offset | { x: number; y: number } | — | 受控平移位置,由外部狀態驅動 |
defaultOffset | { x: number; y: number } | { x: 0, y: 0 } | 非受控初始平移位置 |
onOffsetChange | (offset) => void | — | 平移變化時回報(拖曳/慣性/回中/鍵盤,座標取整) |
momentum | boolean | true | 放開後是否帶慣性滑行 |
showGrid | boolean | true | 是否顯示跟隨平移的點陣背景 |
gridSize | number | 40 | 點陣背景間距(px) |
showRecenter | boolean | true | 是否顯示內建「回到中心」按鈕 |
keyboardStep | number | 64 | 聚焦後方向鍵每次平移距離(px) |
className | string | — | 附加樣式(可用 h-* 覆寫畫布高度) |
CanvasItem
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
x | number | — | 世界座標 X(px),以畫布中心為原點 |
y | number | — | 世界座標 Y(px),以畫布中心為原點 |
children | ReactNode | — | 卡片內容 |
className | string | — | 附加樣式 |
細節
- 拖曳、觸控與慣性滑行由 Motion 內建
drag處理,無拖曳邊界,因此空間可無限延伸。 - 點陣背景以
gridSize取模平移,只需覆蓋視窗+一格即可製造無限重複的錯覺;點色使用currentColor,深淺色自動切換。 offset/defaultOffset支援受控與非受控雙模式:傳入offset時由外部狀態同步平移位置,未傳入時內部自理,並透過onOffsetChange回報。- 內建「回到中心」按鈕以 spring 平滑滑回
defaultOffset(或原點)。
可及性
- 畫布為
role="group"且可聚焦(tabIndex=0),帶說明用的aria-label。 - 聚焦後可用方向鍵平移、
Home鍵回到中心,方向鍵會阻止頁面捲動。 - 使用者系統開啟「減少動態效果」時,關閉慣性滑行,回中與鍵盤平移改為即時跳位(不影響 DOM 結構)。
- 拖曳進行中,
CanvasItem會停用指標事件,避免誤觸內部連結或選取文字。
常見問題
可以在 Next.js App Router 使用嗎?
可以。元件檔案開頭已有 "use client",在 Server Component 頁面直接 import 即可;CanvasItem 裡放的內容也可以由伺服器端產生。只有像受控模式這種要用 useState 的包裝層才需要是 Client Component。
畫布高度怎麼改?
預設高度是 h-80,寬度是 w-full。在 className 傳入 h-[480px] 或 h-screen 之類的高度 class 就會覆寫(樣式透過 cn 合併)。
放很多卡片會不會變慢?
平移是由 Motion 的 motion value 改變一層容器的 transform,非受控模式下拖曳過程不需要逐格更新 React 狀態。受控模式則會在每次 onOffsetChange 更新外部狀態。另外元件沒有做視窗外的卡片剔除,所有 CanvasItem 都會留在 DOM 裡,數量很多時建議控制圖片大小。
支援縮放嗎?
不支援。元件提供拖曳平移、慣性滑行、方向鍵平移與 Home 回到中心,沒有滾輪或雙指縮放。
需要哪些依賴?
依賴是 motion、lucide-react(回中按鈕的圖示)、clsx 與 tailwind-merge。不用 CLI 的話,照安裝區塊「手動」分頁的步驟安裝依賴並複製原始碼即可。