---
title: "VoiceBeam"
description: "沿元素底边律动的音频响应辉光"
category: Effects
status: beta
since: 0.6.2
tags: [voice, audio, glow, mic, dictation, waveform]
syncStatus: reviewed
verified: true
---

## 用法

### 基础
包裹恰好一个自带圆角的元素；传麦克风的 `stream`，或用 `level` 手动驱动。
:::TuffDemoWrapper{demo="VoiceBeamShowcaseDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import { computed, ref } from 'vue'

  const colorMode = useColorMode()
  const beamTheme = computed(() => (colorMode.value === 'dark' ? 'dark' : 'light'))

  // 没有麦克风时，用逐帧 level getter 驱动。
  const level = ref(0.5)
  let raf = 0
  let t = 0
  function tick() {
    t += 0.016
    level.value = 0.35 + 0.32 * Math.abs(Math.sin(t * 1.7))
    raf = requestAnimationFrame(tick)
  }
  raf = requestAnimationFrame(tick)
  onBeforeUnmount(() => cancelAnimationFrame(raf))
  </script>

  <template>
    <TxVoiceBeam :theme="beamTheme" :level="() => level" :border-radius="16">
      <TxCard :radius="16" :padding="24" shadow="none">想问点什么…</TxCard>
    </TxVoiceBeam>

    <TxVoiceBeam :theme="beamTheme" :level="() => level" processing :border-radius="16">
      <TxCard :radius="16" :padding="24" shadow="none">转写中…</TxCard>
    </TxVoiceBeam>
  </template>
---
:::

### 麦克风输入
`useMicrophone()` 关闭回声消除、降噪与自动增益后请求 `getUserMedia`；`start()` 必须在点击事件里调用。

```vue
<script setup lang="ts">
import { useMicrophone } from '@talex-touch/tuffex/pro'

const mic = useMicrophone()
const live = computed(() => mic.state.value === 'live')
</script>

<template>
  <TxVoiceBeam :stream="mic.stream.value" :processing="transcribing">
    <TxCard :radius="16" :padding="24" shadow="none">想问点什么…</TxCard>
  </TxVoiceBeam>
  <TxButton :aria-pressed="live" @click="live ? mic.stop() : mic.start()">
    {{ live ? '停止' : '说话' }}
  </TxButton>
</template>
```

### 宿主预设与配色
`type` 选择宿主预设，显式传入的几何属性覆盖预设值；`colorVariant` 选择调色板。

```vue
<template>
  <TxVoiceBeam type="pill" color-variant="ocean" :scale="0.9">
    <div class="pill">录音中…</div>
  </TxVoiceBeam>

  <TxVoiceBeam type="mobile" color-variant="candy" :level="() => 0.8">
    <div class="screen">正在聆听</div>
  </TxVoiceBeam>
</template>
```

### 最佳实践

- 不要把辉光当作麦克风开着的唯一指示：保留文字状态与按钮的 `aria-pressed`，并用 `role="status"` 播报变化。
- `level` 传 getter（`:level="() => meter.value"`）而不是响应式数字：getter 每帧采样一次，不触发重渲染。
- 需保持清晰的文字与控件放到 `position: relative; z-index: 5`；被包裹元素内的浮层会被裁掉，用 portal 移出。
- 关闭语音界面时调用 `mic.stop()`，组合式函数只在卸载时停止音轨。
- 实例要少：每个实例都有模糊图层与画布，不适合密集列表。

## API 参考

### 属性

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `type` | `'default' \| 'pill' \| 'mobile'` | `'default'` | 宿主预设（聊天输入框 / 录音胶囊 / 手机底部），提供几何默认值。 |
| `stream` | `MediaStream \| null` | `null` | 要响应的实时音频；优先于 `level`。 |
| `level` | `number \| () => number` | `0` | 无 stream 时的手动电平（0-1）。 |
| `sensitivity` | `number` | `3.1` | 分析音频的输入增益。 |
| `threshold` | `number` | `0.015` | 噪声门（0-1），低于它的电平视为静音。 |
| `attack` / `release` | `number` | `0.325` / `0.86` | 辉光上升与回落的秒数。 |
| `idle` / `breatheDuration` | `number` | `0.23` / `5.2` | 静音时的存在感与呼吸周期（秒）。 |
| `reach` / `spread` | `number` | `1.2` / `1.05` | 满电平时的升高与横向扩张倍数。 |
| `bands` | `boolean` | `true` | 低 / 中 / 高频段独立驱动各色瓣。 |
| `flow` | `number` | `48` | 满电平下频谱的横向流速（px/s）。 |
| `processing` | `boolean` | `false` | 收拢为一束往返行进的光束并保持点亮。 |
| `processingDuration` / `processingLevel` / `processingEase` / `processingTravel` / `processingCurve` | `number` | `1.1` / `0.55` / `0.6` / `1.55` / `2.1` | 处理阶段的往返周期、保持亮度、形变时长、行程与缓动。 |
| `cornerFollow` | `number` | `0.45` | 处理阶段辉光沿圆角抬起的程度。 |
| `colorVariant` | `'colorful' \| 'mono' \| 'ocean' \| 'sunset' \| …` | `'colorful'` | 色瓣调色板。 |
| `colors` | `string[]` | 无 | 最多七个色瓣颜色，中心优先。 |
| `bandColors` | `{ core?, above?, mid?, below? }` | 主题默认 | 光带脊线与色边的颜色。 |
| `theme` | `'dark' \| 'light' \| 'auto'` | `'dark'` | 背景适配；`auto` 跟随系统偏好。 |
| `staticColors` | `boolean` | `false` | 关闭缓慢的色相漂移。 |
| `hueRange` / `hueDuration` | `number` | `24` / `40`、`12` / `8.5` | 色相漂移范围（度）与周期（秒）；默认值分暗色 / 亮色。 |
| `active` | `boolean` | `true` | 关闭时辉光淡出并停止音频分析。 |
| `paused` | `boolean` | `false` | 把辉光、光带与分析冻结在最后一帧。 |
| `borderRadius` | `number` | 自动探测 | 圆角半径（px）。 |
| `brightness` / `saturation` | `number` | 主题默认 | 辉光亮度与饱和度倍数。 |
| `glowSize` | `number` | `1` | 辉光模糊半径倍数。 |
| `strokeOpacity` / `innerOpacity` / `bloomOpacity` | `number` | `1` | 描边、内光与光晕层的不透明度倍数。 |
| `scale` | `number` | `1` | 一次缩放全部像素尺寸。 |
| `bend`、`bandStrength`、`bandWidth`、`bandPosition`、`bandCurve`、`bandSpread`、`bandSkew`、`bandOffset`、`bandTail`、`bandTailPosition`、`bandTailCurve`、`bandTailOverflow`、`bandAberration` | `number` | 已调校 | 辉光轮廓与光带的形状。 |
| `distortion` / `distortionDetail` | `number` | `0.62` / `2.3` | 光带下方光的横向扭曲强度与噪声粒度。 |
| `glowWidth`、`glowHeight`、`lobeSpacing`、`rangeWidth`、`rangeHeight`、`softness`、`coreSize`、`coreLight`、`coreLightWidth`、`coreLightHeight`、`strokeScale`、`innerScale`、`innerHeight`、`bloomScale`、`bloomHeight` | `number` | 已调校 | 色瓣、可见范围、核心与光晕的几何。 |
| `strength` | `number` | `1` | 整体效果不透明度（0-1），不影响子元素。 |
| `css` | `string` | 无 | 追加在生成样式表之后的 CSS；`{id}` 按实例替换。 |

### 插槽

| 插槽 | 参数 | 说明 |
|------|------|------|
| `default` | - | 被包裹的元素；光束图层渲染在它背后与上方。 |

### 事件

| 事件 | 载荷 | 说明 |
|------|------|------|
| `level` | `(level: number)` | 每帧触发，携带当前展示的平滑电平。 |
| `activate` | - | 淡入完成时触发。 |
| `deactivate` | - | 淡出完成时触发。 |

### CSS 变量

| 变量 | 来源 | 说明 |
|------|------|------|
| `--voice-strength` | `strength` | 光束图层不透明度（0-1）。 |
| `--voice-stroke-opacity` / `--voice-inner-opacity` / `--voice-bloom-opacity` | 宿主样式 | 各层不透明度倍数，与 `strokeOpacity` / `innerOpacity` / `bloomOpacity` 相乘；组件不写入。 |

## 概述

- 按子元素的 `border-top-left-radius`（取不到时为 16 px）裁剪，光束贴合元素边缘。
- 全页共用一个约 60 fps 封顶的 `requestAnimationFrame` 循环；实例滚出视口（256 px 边距）后注销并释放分析器。
- 每页共用一个 `AudioContext`，每个 stream 一个 source 节点（引用计数），每个实例一个分析器；音频只分析，不播放。
- 使用 `stream` 时读取 RMS 电平与三个频段（80-300、300-2000、2000-6000 Hz）。
- 减少动态效果时停止待机呼吸、色彩流动、色相漂移、扭曲与处理扫描；对声音的反应保留，因为它本质是电平表。

## 技术实现

- `styles.ts`、`presets.ts`、`voice-driver.ts`、`audio.ts`、`color.ts` 逐字移植自 [Jakubantalik/Libraries · voice-glow](https://github.com/Jakubantalik/Libraries/tree/main/packages/voice-glow)（MIT © Jakub Antalik）；`useMicrophone` 是上游 React hook 的 Vue 移植。
- 源码：`packages/tuffex/packages/components/src/voice-beam/`。

<TuffDocSourceLink />
