輪播

Perspective Carousel

3D 透視輪播(coverflow 風格),支援拖曳翻頁、循環、自動輪播與受控/非受控雙模式。

把這個效果裝進你的專案

在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。

npx webberui@latest add perspective-carousel

還沒有專案,或安裝遇到問題?看快速開始 →

Perspective Carousel 是用 React、Motion 與 CSS 3D transform 做的 coverflow 3D 輪播(3D carousel)元件:中央投影片正面朝向使用者,兩側鄰片依 rotate、depth 旋轉並往後推,可拖曳、甩動或點擊側邊投影片切換。

Playground

即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。

1200
45
120
2
0.12
編輯中的呼叫預設屬性
<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型別預設值說明
itemsPerspectiveCarouselItem[]—投影片陣列,每項含 id 與 content
indexnumber—受控目前索引(0 起算),提供時由外部掌控
defaultIndexnumber0非受控初始索引
onIndexChange(index: number) => void—索引變動時觸發
loopbooleanfalse是否循環(頭尾相接的環狀 3D 排列)
itemWidthnumber240投影片寬度(px)
itemHeightnumber320投影片高度(px)
visiblenumber2每側可見的鄰片數量
perspectivenumber1200CSS 透視距離(px),越小透視越強
rotatenumber45鄰片繞 Y 軸旋轉角度(deg)
depthnumber120每階往後推的 Z 位移(px)
spreadnumberitemWidth * 0.6每階水平間距(px)
scaleStepnumber0.12每階縮小比例
autoplaybooleanfalse自動輪播(減少動態時停用)
autoplayIntervalnumber3500自動輪播間隔(ms)
showArrowsbooleantrue是否顯示左右箭頭
velocityThresholdnumber320觸發翻頁的甩動速度閾值(px/s)
ariaLabelstring"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。