媒體
卡帶播放器
擬物卡帶外觀的音訊播放器:捲軸隨播放時間旋轉、兩側磁帶量隨進度增減,倒帶動畫、可拖曳進度條與字幕軌。
一台看得見磁帶在走的音訊播放器。捲軸以 requestAnimationFrame 直接讀 audio.currentTime 旋轉,左側供帶的磁帶量隨進度變小、右側收帶變大;按倒帶時顯示時間依距離長短快速轉回開頭,捲軸跟著反轉。進度條可拖曳、點擊或用鍵盤跳轉,另有快轉 10 秒、靜音、音量與字幕軌。上方標籤紙寫著曲名、副標、編目號碼與 A/B 面,三色條可自訂。沒有傳 audioSrc 時,元件會在瀏覽器端合成一段約 12 秒的短旋律,不需要任何音檔就能試玩;正式使用時傳入你自己的音訊網址即可。
把這個效果裝進你的專案
在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。
npx webberui@latest add "https://webberui.com/r/cassette-player.json?t=<安裝 token>"Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
0.8
1
編輯中的呼叫預設屬性
<CassettePlayer/>
完整範例包含預覽的排版與資料。先安裝元件,再把範例檔貼入你的專案。
安裝
npx webberui@latest add "https://webberui.com/r/cassette-player.json?t=<安裝 token>"或在 components.json 設定 registries 後,改用 @webberui/cassette-player 安裝。
使用
import { CassettePlayer } from "@/components/ui/cassette-player";
<CassettePlayer
audioSrc="/audio/afternoon-walk.mp3"
trackTitle="午後散步"
archiveLabel="家庭錄音帶 1987"
catalogueNumber="WB-0042"
sideLabel="A"
captionTracks={[
{
vtt: "WEBVTT\n\n00:00.000 --> 00:04.000\n午後的巷口,風把曬衣繩吹得輕輕搖晃。\n",
srclang: "zh-Hant",
label: "繁體中文",
default: true,
},
]}
onPlaybackChange={(state) => console.log(state.playing, state.time)}
/>;Props
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
audioSrc | string | — | 音訊網址;未提供時使用內建合成的約 12 秒短旋律(依 sideLabel 換曲),僅供示範 |
captionTracks | CassetteCaptionTrack[] | — | 字幕軌清單;有字幕時控制列出現字幕開關 |
trackTitle | string | — | 曲名,寫在標籤紙上,也作為播放器的 aria-label |
archiveLabel | string | — | 標籤紙副標,例如錄音來源或年份 |
catalogueNumber | string | — | 編目號碼,以等寬字顯示在標籤右上 |
sideLabel | string | "A" | 卡帶面別,以大字顯示在標籤左側 |
initialVolume | number | 0.8 | 初始音量(0–1),只在掛載時讀取 |
loop | boolean | false | 播完是否從頭循環 |
reelSpeed | number | 1 | 捲軸轉速倍率;1 為每秒 300 度 |
stripes | [string, string, string] | ["#e2583e", "#f2a541", "#3f88c5"] | 標籤紙三色條(由上而下);未傳時讀 --wb-cassette-stripe-1..3 變數,變數未設才用預設色 |
onPlaybackChange | (state: CassettePlaybackState) => void | — | 播放、暫停、跳轉、載入完成時回呼 |
grain | boolean | false | 在外殼疊一層細顆粒噪點 |
className | string | — | 透傳到最外層容器 |
CassetteCaptionTrack
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
src | string | — | WebVTT 檔案網址 |
vtt | string | — | WebVTT 原文字串(含 WEBVTT 標頭),會自動轉成 Blob URL;提供時優先於 src |
srclang | string | — | 語言代碼,例如 zh-Hant |
label | string | — | 字幕軌名稱 |
default | boolean | false | 是否為預設字幕軌;有任一軌為 true 時字幕預設開啟 |
CassettePlaybackState
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
playing | boolean | — | 是否正在播放 |
time | number | — | 目前播放時間(秒) |
duration | number | — | 音訊總長(秒);尚未載入時為 0 |
另外具名匯出 CassettePlayerProps、CassetteCaptionTrack 與 CassettePlaybackState 型別。
細節
- 同步迴圈:播放中以 requestAnimationFrame 讀
audio.currentTime,不靠timeupdate(約 4Hz,捲軸會跳格)。捲軸角度 = 時間 × 300 ×reelSpeed(取 360 的餘數);供帶磁帶量由 1 線性縮到 0.6、收帶由 0.6 長到 1。這些值寫進 motion value,不會每幀觸發 React 重新渲染 - 倒帶:暫停同步迴圈與播放,把顯示時間從目前位置動畫回 0,時長 = 220ms + 每秒距離 40ms,上限 1 秒;捲軸隨之快速反轉。結束後
currentTime設為 0,若原本在播放就續播 - 內建合成音源:未傳
audioSrc時,元件以純 JS 逐樣本合成 24 個音符(正弦+三角波、attack/release 包絡、少量磁帶嘶聲與轉速抖動),編碼成 WAV Blob 後餵給同一個audio元素,所以跳轉、倒帶、循環的行為和真實音檔完全相同;卸載時 revoke Blob URL。A 面與 B 面會得到不同的旋律 - 字幕:
vtt字串會轉成 Blob URL 放進track,選定的字幕軌設為 hidden 載入,元件自己讀activeCues顯示在外殼下方 - 主題變數:
--wb-cassette-duration(按鈕轉場,預設 150ms)、--wb-cassette-ease(預設 ease-out)、--wb-cassette-stripe-1..3(三色條)、--wb-cassette-paper(標籤紙色)、--wb-cassette-tape(磁帶色) - 標籤紙文字以容器寬度(cqw)縮放,播放器多寬標籤就多大
可及性
- 外層為
role="group",aria-label為曲名;卡帶外殼純裝飾(aria-hidden),audio元素不顯示原生控制列 - 所有控制都是原生
button,附繁中aria-label(播放/暫停、倒帶、快轉 10 秒、靜音、字幕);靜音與字幕開關帶aria-pressed,皆有focus-visible外框 - 進度條為
role="slider",aria-valuetext讀成「1 分 02 秒,共 3 分 10 秒」;鍵盤 ←/→ 前後 5 秒、Home/End 跳到開頭或結尾,指標可拖曳或點擊跳轉 - 播放狀態變化(播放中、已暫停、倒帶中、播放結束)由
sr-only的aria-live區域播報 - 使用者系統開啟「減少動態效果」時,捲軸與磁帶量凍結在當下的比例不再轉動,倒帶直接跳回開頭不播動畫,其餘功能照常