媒體

Audio Visualizer

Web Audio API 音訊頻譜視覺化:以 canvas 2D 繪製長條、鏡像與圓形三種款式,可分析真實音源或以合成資料驅動示意動畫。

這是 WebberUI Pro 元件

下方的安裝指令需要授權才能執行,未授權時會回 401。

怎麼安裝 Pro 元件 →查看方案 →

把這個效果裝進你的專案

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

npx webberui@latest add "https://webberui.com/r/audio-visualizer.json?t=<安裝 token>"

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

Audio Visualizer 是用 React、Web Audio API 與 canvas 2D 做的音訊視覺化(音樂頻譜、聲波動畫)元件,提供長條、中線鏡像與圓形徑向三種款式。可以接 <audio>/<video> 或麥克風串流讀取即時頻譜,沒有音源時改用程式合成的示意動畫。

Playground

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

48
1
編輯中的呼叫預設屬性
<AudioVisualizer/>

完整範例包含預覽的排版與資料。先安裝元件,再把範例檔貼入你的專案。

適用情境

  • Podcast 或音樂播放器、節目單集頁的播放狀態視覺化
  • 語音輸入、語音留言或 AI 語音助理的「正在聆聽」回饋
  • 線上會議、錄音工具的麥克風音量檢查畫面
  • 音樂產品或活動頁的 hero 裝飾背景(不接音源,使用合成資料)
  • 不適合:需要拖曳定位播放進度的波形,改用 Waveform Scrubber

安裝

npx webberui@latest add "https://webberui.com/r/audio-visualizer.json?t=<安裝 token>"

或在 components.json 設定 registries 後,改用 @webberui/audio-visualizer 安裝。

使用

不提供任何音源時,元件以程序化合成資料驅動,適合作為裝飾性的示意動畫:

import { AudioVisualizer } from "@/components/ui/audio-visualizer";

<div className="h-40 overflow-hidden rounded-xl">
  <AudioVisualizer variant="bars" />
</div>

分析真實媒體元素

把 <audio> / <video> 的 ref 傳給 media,元件會透過 Web Audio API 讀取即時頻譜。媒體聲音會經由音訊圖回放,維持可聽見:

"use client";

import * as React from "react";
import { AudioVisualizer } from "@/components/ui/audio-visualizer";

export function Player() {
  const audioRef = React.useRef<HTMLAudioElement>(null);
  const [playing, setPlaying] = React.useState(false);

  return (
    <div>
      <audio ref={audioRef} src="/track.mp3" />
      <AudioVisualizer
        variant="mirror"
        media={audioRef}
        active={playing}
        color="#22d3ee"
        className="h-40"
      />
      <button
        onClick={() => {
          const el = audioRef.current;
          if (!el) return;
          if (playing) el.pause();
          else void el.play();
          setPlaying(!playing);
        }}
      >
        播放 / 暫停
      </button>
    </div>
  );
}

分析麥克風

把 getUserMedia 取得的 MediaStream 傳給 stream(麥克風不會回放,避免回授):

const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
<AudioVisualizer variant="radial" stream={stream} sensitivity={1.4} />

範例

影片播放器加上頻譜

media 也接受 <video> 的 ref;用影片的 play/pause 事件同步 active,暫停時畫面凍結成靜止剪影。

"use client";

import * as React from "react";
import { AudioVisualizer } from "@/components/ui/audio-visualizer";

export function EpisodePlayer() {
  const videoRef = React.useRef<HTMLVideoElement>(null);
  const [playing, setPlaying] = React.useState(false);

  return (
    <div className="space-y-3">
      <video
        ref={videoRef}
        src="/episodes/ep-12.mp4"
        controls
        onPlay={() => setPlaying(true)}
        onPause={() => setPlaying(false)}
      />
      <AudioVisualizer
        media={videoRef}
        active={playing}
        bars={32}
        smoothing={0.6}
        className="h-16"
        label="第 12 集音訊頻譜"
      />
    </div>
  );
}

麥克風收音與釋放

在使用者點按鈕後才呼叫 getUserMedia;停止時關掉串流的音軌並把 stream 設回 null。

const [stream, setStream] = React.useState<MediaStream | null>(null);

async function startListening() {
  setStream(await navigator.mediaDevices.getUserMedia({ audio: true }));
}

function stopListening() {
  stream?.getTracks().forEach((track) => track.stop());
  setStream(null);
}

<AudioVisualizer
  variant="mirror"
  stream={stream}
  active={stream !== null}
  sensitivity={1.6}
  color="#ef4444"
  className="h-12"
  label="麥克風音量"
/>

不接音源的裝飾背景

不傳 media 與 stream 時使用合成資料,可以直接當成音樂活動頁的背景。

<section className="relative h-80 overflow-hidden rounded-2xl bg-neutral-950">
  <AudioVisualizer variant="radial" bars={96} color="#a78bfa" className="absolute inset-0 opacity-60" />
  <h2 className="relative z-10 p-10 text-3xl font-semibold text-white">年度歌單上線</h2>
</section>

Props

Prop型別預設值說明
variant"bars" | "mirror" | "radial""bars"視覺化款式:底部長條、中線鏡像、圓形徑向
mediaRefObject<HTMLMediaElement | null>—要分析的 <audio>/<video> ref;提供後分析真實頻譜
streamMediaStream | nullnull直接提供音訊串流(例如麥克風);優先於 media
activebooleantrue是否運行動畫;false 時凍結為靜止低振幅狀態
barsnumber48頻段數量(radial 為射線數),內部夾在 4–256
colorstring隨主題的 neutral 色主色;未指定時跟隨 currentColor(隨深淺主題切換)
fftSizenumber2048FFT 視窗大小,需為 2 的次方(32–32768)
smoothingnumber0.8頻譜平滑係數 0–1,越大越平順、反應越慢
sensitivitynumber1靈敏度倍率,放大整體振幅
labelstring"音訊視覺化"套用於容器 aria-label 的無障礙標籤
classNamestring—附加到最外層容器的 class

細節

  • 雙模式資料來源。 提供 media 或 stream 時建立 AnalyserNode,每幀以 getByteFrequencyData 讀取真實頻譜,並依「近似對數」分組為 bars 條、強調低頻;未提供音源時退回程序化合成資料(多層正弦振盪 + 節拍脈衝),示範環境無需外部音檔即可展示
  • canvas 2D 單層繪製。 三種款式都畫在同一張 <canvas>:bars 由底部升起、mirror 以中線上下鏡像、radial 沿圓周向外輻射;每幀 clearRect 後重繪,圓角以原生 roundRect(不支援時退化為 arcTo 手繪)
  • MediaElementSource 只能建立一次。 同一個媒體元素在整個生命週期僅能建立一次來源節點,重複建立會拋錯;元件以 WeakMap 快取「context / source / analyser」音訊圖,重掛載或重渲染時重用,元素被回收後自動釋放
  • AudioContext 生命週期。 由麥克風 stream 建立的 context 會在卸載時 close();媒體元素的音訊圖因被快取重用故不關閉。context 若處於 suspended(瀏覽器需使用者手勢),在 active 時嘗試 resume()
  • 以 devicePixelRatio(上限 2)設定實際畫布尺寸再用 setTransform 縮放,Retina 螢幕維持銳利;ResizeObserver 監看容器變化即時重繪
  • 未指定 color 時讀取 canvas 的 currentColor(由 text-neutral-* class 解析),並以 MutationObserver 監看 <html> 的 class 變化,深淺主題切換時即時重讀顏色

可及性

  • 容器帶 role="img" 與可自訂的 aria-label(預設「音訊視覺化」),內部 <canvas> 標記 aria-hidden,不朗讀逐格畫面
  • 使用者系統開啟「減少動態效果」時,requestAnimationFrame 迴圈完全停用,只渲染一張靜止的低振幅剪影,DOM 結構不變(毋須兩段式掛載)
  • 視覺化為使用者已聽見之聲音的裝飾性映射;請確保實際的播放控制(播放/暫停按鈕等)本身具備完整的鍵盤與標籤支援

常見問題

可以在 Next.js App Router 使用嗎?

可以。檔案開頭已經有 "use client",在 Server Component 頁面裡照一般方式 import 即可。AudioContext、ResizeObserver 等瀏覽器 API 都只在 effect 裡使用。

捲出畫面後還會一直繪製嗎?

不會。元件用 IntersectionObserver 監看 canvas,捲出視窗時取消 requestAnimationFrame,捲回來再接上;active 為 false 或開啟減少動態效果時則完全不啟動迴圈。

怎麼改顏色?

傳入 color,任何 canvas 可用的 CSS 色值都可以。未指定時讀取 canvas 上 text-neutral-800/dark:text-neutral-200 解析出的顏色,並在 <html> 的 class 改變時重新讀取。

播放音樂時聲音會不見嗎?

不會。接 media 時,音訊圖會把聲音接回輸出端,維持可聽見;接 stream(麥克風)時則刻意不回放,避免回授。

為什麼接上音源後畫面沒有反應?

瀏覽器通常要求使用者手勢後才允許 AudioContext 播放。context 處於 suspended 時,元件會在 active 時嘗試 resume(),建議在使用者按下播放後才把 active 設為 true。