卡片與網格

Infinite Drag Canvas

無限拖曳平移的作品畫布,把卡片散佈於無邊界空間,可拖曳(含慣性)、鍵盤平移與一鍵回中。

這是 WebberUI Pro 元件

下方的安裝指令需要授權才能執行,未授權時會回 401。

怎麼安裝 Pro 元件 →查看方案 →

把這個效果裝進你的專案

在專案資料夾執行指令,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、程式碼片段同步更新——直接試出你要的樣子再複製。

40
64
編輯中的呼叫預設屬性
<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型別預設值說明
childrenReactNode—放入 CanvasItem 子元件
offset{ x: number; y: number }—受控平移位置,由外部狀態驅動
defaultOffset{ x: number; y: number }{ x: 0, y: 0 }非受控初始平移位置
onOffsetChange(offset) => void—平移變化時回報(拖曳/慣性/回中/鍵盤,座標取整)
momentumbooleantrue放開後是否帶慣性滑行
showGridbooleantrue是否顯示跟隨平移的點陣背景
gridSizenumber40點陣背景間距(px)
showRecenterbooleantrue是否顯示內建「回到中心」按鈕
keyboardStepnumber64聚焦後方向鍵每次平移距離(px)
classNamestring—附加樣式(可用 h-* 覆寫畫布高度)

CanvasItem

Prop型別預設值說明
xnumber—世界座標 X(px),以畫布中心為原點
ynumber—世界座標 Y(px),以畫布中心為原點
childrenReactNode—卡片內容
classNamestring—附加樣式

細節

  • 拖曳、觸控與慣性滑行由 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 的話,照安裝區塊「手動」分頁的步驟安裝依賴並複製原始碼即可。