媒體
3D 裝置舞台
純 CSS 3D 手機/平板/筆電舞台,五種編排:游標傾斜、捲動環繞、Y 軸翻轉、三台扇形陣列、捲動微距拉回;螢幕是真 DOM。
產品頁最常見的一幕:把 App 畫面裝進一台手機或筆電,讓它在頁面上動起來。這個元件的手機、平板、筆電外框全部用 CSS 畫成(圓角、邊框、動態島、側鍵、鍵盤底座),沒有任何圖片;螢幕裡放的是真 DOM,文字可選取、按鈕可點、螢幕閱讀器讀得到。五種編排可切換:游標傾斜、捲動環繞、Y 軸翻轉(背面可放第二個畫面或畫出背殼)、三台扇形陣列,以及捲動時從微距特寫拉回全景。
把這個效果裝進你的專案
在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。
npx webberui@latest add "https://webberui.com/r/device-stage-3d.json?t=<安裝 token>"Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
8
3
1400
120
編輯中的呼叫預設屬性
<DeviceStage3D/>
完整範例包含預覽的排版與資料。先安裝元件,再把範例檔貼入你的專案。
安裝
npx webberui@latest add "https://webberui.com/r/device-stage-3d.json?t=<安裝 token>"或在 components.json 設定 registries 後,改用 @webberui/device-stage-3d 安裝。
使用
import { DeviceStage3D } from "@/components/ui/device-stage-3d";
<DeviceStage3D
device="phone"
mode="flip"
screens={[<HomeScreen key="home" />, <SettingsScreen key="settings" />]}
caption={<p>點擊手機看設定頁</p>}
maxTilt={8}
/>;orbit 與 macro 由捲動驅動;舞台放在可捲動的區塊裡時,把該區塊的 ref 傳給 container:
const scrollerRef = React.useRef<HTMLDivElement>(null);
<div ref={scrollerRef} className="h-[480px] overflow-y-auto">
<div className="h-[320px]" />
<DeviceStage3D mode="macro" container={scrollerRef} screens={[<HomeScreen key="home" />]} />
<div className="h-[320px]" />
</div>;Props
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
screens | React.ReactNode[] | — | 螢幕內容(1–3 個);flip 背面用第 2 個,lineup 依序填入三台、不足則重複 |
device | "phone" | "tablet" | "laptop" | "phone" | 裝置外型 |
mode | "tilt" | "orbit" | "flip" | "lineup" | "macro" | "tilt" | 編排模式 |
caption | React.ReactNode | — | 側邊說明欄;flip 模式每次翻面都會延遲 120ms 重新滑入 |
flipped | boolean | — | 受控的翻面狀態 |
defaultFlipped | boolean | false | 非受控模式的初始翻面狀態 |
onFlippedChange | (flipped: boolean) => void | — | 翻面狀態改變時回呼 |
clickToFlip | boolean | true | flip 模式下點擊裝置即可翻面,並提供鍵盤用的翻面按鈕 |
perspective | number | 1400 | 舞台透視距離(px) |
maxTilt | number | 8 | tilt 模式的最大傾斜角(度) |
depthBlur | number | 3 | macro 模式非聚焦層的起始模糊(px),建議 2–4 |
flipStiffness | number | 120 | 翻面彈簧的 stiffness(damping 固定 20) |
gyro | boolean | false | 觸控裝置改用陀螺儀驅動 tilt |
container | React.RefObject<HTMLElement | null> | — | orbit/macro 捲動進度依據的捲動容器;未提供時以視窗為準 |
screenClassName | string | — | 透傳到每個螢幕區塊 |
className | string | — | 透傳到最外層容器 |
另外具名匯出 DeviceStage3DDevice、DeviceStage3DMode 與 DeviceStage3DProps 型別。
CSS 變數
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
--wb-ds3d-frame | color | #171717(深色模式 #262626) | 外框與側鍵顏色 |
--wb-ds3d-highlight | color | rgba(255,255,255,0.14) | 外框 1px 內高光 |
--wb-ds3d-back | color | #e5e5e5(深色模式 #525252) | 背殼漸層起始色 |
--wb-ds3d-deck | color | #d4d4d4(深色模式 #404040) | 筆電鍵盤底座顏色 |
--wb-ds3d-glow | color | rgba(115,115,115,0.16) | 舞台背景柔光 |
細節
- tilt:游標相對舞台中心換算成 −1 到 1,映射成 rotateY ±maxTilt、rotateX ∓maxTilt,經 stiffness 150/damping 18 的彈簧平滑,離開舞台回正。觸控裝置(
pointer: coarse)預設不跟隨;開啟gyro後改讀deviceorientation,iOS 會在使用者第一次觸控後請求權限 - orbit:捲動進度 0 到 1 映射 rotateY −30° 到 30°,translateZ 在中段推到 40px 最靠前
- flip:rotateY 0 到 180°,正面
screens[0]、背面screens[1];只有一個畫面時背面畫成背殼(鏡頭模組與霧面)。點到螢幕裡的按鈕、連結時不會翻面 - lineup:三台扇形排開,左右兩台往內轉 18°、往外移 60%、退後 80px、縮到 0.9;進入視口時每台間隔 0.12 秒從下方彈上來
- macro:捲動到舞台中心對齊視窗中心的過程中,整組從 1.6 倍拉回 1 倍、位移 12% 歸零;背景裝飾與側機(
screens多於一個時)從depthBlur模糊到清晰 - 筆電開蓋:螢幕面板以底邊為軸從 −90° 打開;一般模式在進入視口時以彈簧開蓋,orbit/macro 改綁捲動前 30%
可及性
- 最外層為
role="group",aria-roledescription="裝置展示";外框、側鍵、鏡頭、鍵盤等裝飾都是aria-hidden,螢幕內容保持可讀、可聚焦 - flip 模式提供一顆鍵盤用的翻面按鈕(
aria-pressed表示是否顯示背面),平常隱藏、Tab 聚焦時浮現,可用 Enter/空白鍵觸發;沒朝向使用者的那一面會設為inert,不會被 Tab 到 - macro 的側機只是裝飾,設為
aria-hidden與inert - 使用者系統開啟「減少動態效果」時,所有模式固定正面、不跟隨游標與捲動,lineup 直接到位,筆電直接開蓋,flip 改為兩面原地交叉淡接