應用殼層與面板
片段時間軸
影音剪輯式多軌時間軸:片段可拖移與拉長、吸附格線、播放頭拖曳與播放、時間尺隨縮放換刻度、留言釘與音訊波形。
剪輯軟體那種多軌時間軸,做成可以直接放進網頁的元件:左側是軌道標頭(名稱、靜音、鎖定),右側是可水平捲動的時間區。片段可以拖移、拉兩端改長度,放開時吸附到次刻度與其他片段邊緣;同軌重疊會被拒絕並以 spring 回彈。播放頭可拖曳或點時間尺 seek,按空白鍵以 rAF 播放;時間尺依縮放自動換主刻度間距。音訊片段內畫波形,留言以釘子顯示在時間尺下方,hover 展開。
把這個效果裝進你的專案
在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。
npx webberui@latest add "https://webberui.com/r/clip-timeline.json?t=<安裝 token>"Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
1
500
編輯中的呼叫預設屬性
<ClipTimeline/>
完整範例包含預覽的排版與資料。先安裝元件,再把範例檔貼入你的專案。
安裝
npx webberui@latest add "https://webberui.com/r/clip-timeline.json?t=<安裝 token>"或在 components.json 設定 registries 後,改用 @webberui/clip-timeline 安裝。
使用
import * as React from "react";
import {
ClipTimeline,
type TimelineTrack,
} from "@/components/ui/clip-timeline";
const initialTracks: TimelineTrack[] = [
{
id: "video",
name: "影片",
clips: [
{ id: "v1", start: 0, duration: 5, label: "開場鏡頭", kind: "video" },
{ id: "v2", start: 5, duration: 8, label: "店內環景", kind: "video" },
],
},
{
id: "audio",
name: "音訊",
clips: [{ id: "a1", start: 0, duration: 12, label: "背景音樂", kind: "audio" }],
},
{
id: "subtitle",
name: "字幕",
clips: [{ id: "t1", start: 2, duration: 4, label: "歡迎光臨", kind: "text" }],
},
];
export function Editor() {
const [tracks, setTracks] = React.useState(initialTracks);
const [time, setTime] = React.useState(0);
return (
<ClipTimeline
tracks={tracks}
onTracksChange={setTracks}
duration={30}
currentTime={time}
onTimeChange={setTime}
comments={[{ id: "c1", t: 6, text: "這裡轉場太快" }]}
/>
);
}Props
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
tracks | TimelineTrack[] | — | 受控的軌道資料;未提供時為非受控 |
defaultTracks | TimelineTrack[] | [] | 非受控模式的初始軌道資料 |
onTracksChange | (tracks: TimelineTrack[]) => void | — | 拖移、改長度、刪除、靜音或鎖定後回呼 |
duration | number | 30 | 專案總長度(秒) |
currentTime | number | — | 受控的播放頭時間(秒) |
defaultCurrentTime | number | 0 | 非受控模式的初始播放頭時間 |
onTimeChange | (time: number) => void | — | 播放頭時間變動時回呼(播放、拖曳、點時間尺) |
playing | boolean | — | 受控的播放狀態 |
defaultPlaying | boolean | false | 非受控模式的初始播放狀態 |
onPlayingChange | (playing: boolean) => void | — | 播放狀態切換時回呼 |
zoom | number | — | 受控的縮放倍率(0.25–4),每秒寬度為 80px × zoom |
defaultZoom | number | 1 | 非受控模式的初始縮放倍率 |
onZoomChange | (zoom: number) => void | — | 縮放倍率變動時回呼 |
snap | boolean | true | 拖移時吸附到次刻度、其他片段邊緣與播放頭(6px 內) |
comments | TimelineComment[] | [] | 釘在時間尺下方的留言 |
showComments | boolean | true | 是否顯示留言釘 |
loop | boolean | false | 播放到結尾時是否從頭循環 |
height | number | — | 時間軸區域最大高度(px);超出時垂直捲動,時間尺固定在頂部 |
bounceStiffness | number | 500 | 同軌重疊被拒絕時回彈 spring 的 stiffness(damping 固定 35) |
className | string | — | 併入最外層容器 |
TimelineTrack
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
id | string | — | 軌道唯一識別碼 |
name | string | — | 軌道名稱,也是該列的無障礙名稱 |
clips | TimelineClip[] | — | 軌道上的片段 |
muted | boolean | false | 靜音(片段淡化顯示) |
locked | boolean | false | 鎖定(片段不可拖移、改長度或刪除) |
TimelineClip
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
id | string | — | 片段唯一識別碼 |
start | number | — | 起點(秒) |
duration | number | — | 長度(秒),最小 0.2 |
label | string | — | 顯示名稱 |
kind | "video" | "audio" | "text" | "image" | — | 片段種類,決定配色與圖示 |
peaks | number[] | — | 音訊波形峰值(0–1);未提供時以 id 產生確定性的假波形 |
TimelineComment
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
id | string | — | 留言唯一識別碼 |
t | number | — | 留言所在時間點(秒) |
text | string | — | 留言內容 |
author | string | — | 留言者名稱 |
另外具名匯出 formatTimecode(mm:ss.f 格式化)與 ClipKind、ClipTimelineProps 型別。
細節
- 時間尺換刻度:每秒寬度為 80px × zoom。主刻度間距從 0.1/0.5/1/2/5/10/30/60 秒裡挑「間距至少 64px」的最小者,次刻度為主刻度的五等分;主刻度小於 1 秒時標籤改為 mm:ss.f
- 吸附:拖移或拉長時,移動中的邊緣若距離次刻度、其他片段邊緣、專案起訖或播放頭在 6px 內,就對齊過去;
snap設為false可關閉 - 換軌:片段可上下拖到同種類(或空的)且未鎖定的軌道;軌道種類以第一個片段為準
- 重疊拒絕:放開時若與目標軌的其他片段重疊,變更不會送出,片段以 spring(stiffness 可調、damping 35)回彈到原位,拖曳中會先以紅框提示
- 播放:以
requestAnimationFrame搭配performance.now差值推進時間,掉幀不影響進度;到結尾停止,或在loop時從頭開始。播放頭超出可視範圍時自動水平捲動跟隨 - 配色:四種片段顏色走
--wb-ct-video、--wb-ct-audio、--wb-ct-text、--wb-ct-image變數,預設是降低彩度的藍、綠、紫、琥珀;影片片段另有斜紋 - 波形:音訊片段內以 SVG 長條畫波形,資料取自
peaks;沒有提供時由id的雜湊產生確定性假峰值,SSR 與 client 一致
可及性
- 軌道區為
role="grid"(aria-label為「時間軸」),每軌是role="row"並以軌名為aria-label,片段是role="gridcell",aria-label例如「影片 開場鏡頭,00:00 起,長 5 秒」,選取狀態以aria-selected表示 - 片段採 roving tabindex:Tab 只進入一個片段,↑/↓ 切換到相鄰軌道上時間最接近的片段;←/→ 微移選取片段 0.1 秒(Shift 為 1 秒),Alt + ←/→ 改長度,Delete 刪除,Enter 切換選取(IME 組字中的 Enter 會被忽略)
- 空白鍵播放/暫停(焦點在輸入框或按鈕時不攔截),+/- 縮放
- 播放頭把手為
role="slider",aria-valuetext為時間碼,可用方向鍵(Shift 加大步幅)、Home、End 操作 - 留言釘是按鈕,hover 或 focus 都會展開氣泡(
role="tooltip"),點擊跳到該時間點;移動或刪除結果會透過aria-live區域朗讀 - 使用者系統開啟「減少動態效果」時,重疊拒絕的片段直接回位不做 spring 回彈、留言氣泡只淡入不縮放、播放頭跟隨改為立即捲動