Perspective Carousel
3D 透視輪播(coverflow 風格),支援拖曳翻頁、循環、自動輪播與受控/非受控雙模式。
把這個效果裝進你的專案
在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。
npx webberui@latest add perspective-carouselPerspective Carousel 是用 React、Motion 與 CSS 3D transform 做的 coverflow 3D 輪播(3D carousel)元件:中央投影片正面朝向使用者,兩側鄰片依 rotate、depth 旋轉並往後推,可拖曳、甩動或點擊側邊投影片切換。
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
<PerspectiveCarousel/>
完整範例包含預覽的排版與資料。先安裝元件,再把範例檔貼入你的專案。
適用情境
- 作品集或案例頁,用封面式排列展示專案縮圖。
- 電商首頁的新品、主打商品輪播,搭配
autoplay定時切換。 - 音樂、影片、書籍等以封面為主的內容瀏覽列。
- 活動頁的講者或票種卡片,需要同時看到前後幾張。
- 不適合:一次要瀏覽大量圖片並放大檢視,改用 Lightbox Gallery;只要平面捲動的封面列,可以看 Cover Carousel。
安裝
npx webberui@latest add https://webberui.com/r/perspective-carousel.json或在 components.json 設定 registries 後,改用 @webberui/perspective-carousel 安裝。
使用
import { PerspectiveCarousel } from "@/components/ui/perspective-carousel";
const items = [
{ id: "a", content: <img src="/1.jpg" alt="" className="h-full w-full object-cover" /> },
{ id: "b", content: <img src="/2.jpg" alt="" className="h-full w-full object-cover" /> },
{ id: "c", content: <img src="/3.jpg" alt="" className="h-full w-full object-cover" /> },
];
<PerspectiveCarousel items={items} loop />受控模式
傳入 index 與 onIndexChange 即進入受控模式,可與外部狀態(如指示點、縮圖列)同步:
const [active, setActive] = React.useState(0);
<PerspectiveCarousel items={items} index={active} onIndexChange={setActive} loop />程式化操作
透過 ref 取得 next / prev / goTo:
const ref = React.useRef<PerspectiveCarouselHandle>(null);
<PerspectiveCarousel ref={ref} items={items} />
<button onClick={() => ref.current?.next()}>下一張</button>範例
商品主打輪播
開啟 loop 與 autoplay,每 5 秒換一張;指標懸停或焦點進入時會自動暫停。
import { PerspectiveCarousel } from "@/components/ui/perspective-carousel";
const products = [
{ id: "p1", name: "無線降噪耳機", image: "/products/headphones.jpg" },
{ id: "p2", name: "機械式鍵盤", image: "/products/keyboard.jpg" },
{ id: "p3", name: "27 吋顯示器", image: "/products/monitor.jpg" },
{ id: "p4", name: "人體工學椅", image: "/products/chair.jpg" },
];
export function FeaturedProducts() {
return (
<PerspectiveCarousel
ariaLabel="主打商品"
loop
autoplay
autoplayInterval={5000}
items={products.map((p) => ({
id: p.id,
content: (
<figure className="relative h-full w-full">
<img src={p.image} alt={p.name} className="h-full w-full object-cover" />
<figcaption className="absolute inset-x-0 bottom-0 bg-black/60 p-3 text-sm text-white">
{p.name}
</figcaption>
</figure>
),
}))}
/>
);
}較平緩的透視
降低 rotate 與 depth、只露出一張鄰片,適合寬度有限的版面。
<PerspectiveCarousel
items={items}
itemWidth={200}
itemHeight={260}
visible={1}
rotate={25}
depth={60}
perspective={1600}
/>搭配指示點
用受控模式同步外部的指示點,並隱藏內建箭頭。
const [active, setActive] = React.useState(0);
<PerspectiveCarousel
items={items}
index={active}
onIndexChange={setActive}
showArrows={false}
/>
<div className="mt-4 flex justify-center gap-2">
{items.map((item, i) => (
<button
key={item.id}
aria-label={`前往第 ${i + 1} 張`}
onClick={() => setActive(i)}
className={i === active ? "size-2 rounded-full bg-neutral-900" : "size-2 rounded-full bg-neutral-300"}
/>
))}
</div>Props
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
items | PerspectiveCarouselItem[] | — | 投影片陣列,每項含 id 與 content |
index | number | — | 受控目前索引(0 起算),提供時由外部掌控 |
defaultIndex | number | 0 | 非受控初始索引 |
onIndexChange | (index: number) => void | — | 索引變動時觸發 |
loop | boolean | false | 是否循環(頭尾相接的環狀 3D 排列) |
itemWidth | number | 240 | 投影片寬度(px) |
itemHeight | number | 320 | 投影片高度(px) |
visible | number | 2 | 每側可見的鄰片數量 |
perspective | number | 1200 | CSS 透視距離(px),越小透視越強 |
rotate | number | 45 | 鄰片繞 Y 軸旋轉角度(deg) |
depth | number | 120 | 每階往後推的 Z 位移(px) |
spread | number | itemWidth * 0.6 | 每階水平間距(px) |
scaleStep | number | 0.12 | 每階縮小比例 |
autoplay | boolean | false | 自動輪播(減少動態時停用) |
autoplayInterval | number | 3500 | 自動輪播間隔(ms) |
showArrows | boolean | true | 是否顯示左右箭頭 |
velocityThreshold | number | 320 | 觸發翻頁的甩動速度閾值(px/s) |
ariaLabel | string | "3D 透視輪播" | 輪播區的無障礙標籤 |
型別
interface PerspectiveCarouselItem {
id: string | number;
content: React.ReactNode;
}
interface PerspectiveCarouselHandle {
next: () => void;
prev: () => void;
goTo: (index: number) => void;
}細節
- 純 CSS 3D transform(
perspective+rotateY+translateZ)打造 coverflow 透視,未依賴任何 WebGL 套件。 - 所有投影片共用一個浮點位置 motion value,中心到兩側的位移、旋轉、縮放、透明度與壓暗皆由該值連續映射,拖曳過程即時跟手。
- 拖曳放開時依落點四捨五入翻頁,甩動速度超過
velocityThreshold則往甩動方向再進一張。 loop開啟時採最短環狀路徑落定,並在動畫結束後將位置正規化,長時間輪播不會累積漂移。
可及性
- 輪播容器帶
role="group"、aria-roledescription="輪播"與可聚焦的tabIndex,方向鍵可翻頁,Home / End 直達首末張。 - 目前張數以
aria-live="polite"的隱藏區域朗讀(「第 N 張,共 M 張」);非當前投影片標記aria-hidden。 - 左右箭頭為原生
button,附aria-label並在到達邊界(非循環)時disabled。 - 使用者系統開啟「減少動態效果」時,停用拖曳與自動輪播,翻頁改為即時切換不含補間動畫。
- 指標懸停或焦點進入輪播區時自動暫停自動輪播。
常見問題
需要 WebGL 或 Three.js 嗎?
不需要。3D 效果來自 CSS 的 perspective、rotateY 與 translateZ,動畫由 Motion 的 motion value 驅動,依賴只有 motion、lucide-react、clsx 與 tailwind-merge。
可以在 Next.js App Router 使用嗎?
可以。原始碼開頭已有 "use client",直接在 Server Component 頁面裡 import;items 的 content 可以放 next/image 或任何 React 節點。
怎麼調整大小與透視強度?
用 itemWidth、itemHeight 設定投影片尺寸,perspective 越小透視越強,rotate、depth、spread、scaleStep 分別控制鄰片的旋轉、後推、間距與縮小比例,visible 控制每側露出幾張。
自動輪播什麼時候會停?
指標懸停、焦點進入輪播區、只有一張投影片,或使用者開啟「減少動態效果」時,autoplay 都不會推進。開啟減少動態效果時,拖曳也會停用,翻頁改為即時切換。
鍵盤可以操作嗎?
可以。輪播容器可聚焦,左右方向鍵翻頁,Home、End 跳到第一張與最後一張;左右箭頭是原生 button,非循環模式到邊界時會 disabled。