音频属于完整预设,与自定义视觉效果相互独立。请从点击等用户手势中调用 remove() 或 restore(),以便库恢复 Web Audio 播放。
预设音频与自定义音频
内置预设默认带声音。它只能被静音;替换音频就意味着创建另一个预设。
const audible = new Disintegrator({ preset: 'dust' });
const muted = new Disintegrator({ preset: 'dust', sound: false });
effects.remove(element, { preset: 'vapor', sound: false });
自定义效果默认静音。需要音频时,请显式提供一对定义:
import Disintegrator, { createParticleEffect } from 'vanilla-disintegrate/snapdom';
const effects = new Disintegrator({
effect: createParticleEffect(),
sound: {
remove: { src: 'dust', volume: 0.35 },
restore: { src: 'dust', volume: 0.35, reverse: true },
},
});
remove 与 restore 相互独立。至少要配置一个方向;有意静音的阶段请使用 false。
type SoundDefinition = SoundOptions | SoundFactory;
type SoundPair =
| { remove: SoundDefinition; restore?: SoundDefinition | false }
| { remove?: SoundDefinition | false; restore: SoundDefinition };
type SoundSelection = SoundPair;
首次交互前准备
默认情况下,已启用的音频会在浏览器空闲时准备。若就绪时间比首屏工作量更重要,可使用 immediate:
const effects = new Disintegrator({
preset: 'vapor',
audioPreparation: 'immediate',
});
sounds 字段接收实际声音定义或声音对,而不是视觉效果名称:
type SoundPreparationSelection =
SoundDefinition | SoundSelection | readonly (false | SoundDefinition | SoundSelection)[];
import { builtInPresets } from 'vanilla-disintegrate';
const effects = new Disintegrator({
preset: 'dust',
audioPreparation: {
strategy: 'idle',
sounds: [builtInPresets.dust.sound, builtInPresets.wind.sound],
},
});
await effects.prepareAudio({
remove: { src: 'scatter' },
restore: { src: 'scatter', reverse: true },
});
effects.discardPreparedAudio();
effects.clearPreparedAudio();
audioPreparation: false 会关闭自动准备;显式 prepareAudio() 仍然可用。若原生音频尚未就绪,视觉与声音会等待解码完成后同步开始。自定义播放器工厂自行负责加载,准备阶段不会调用它们。
audioWaitTimeout 限制操作等待原生音频的时间,默认 1500 毫秒。超时后,原因会传递给 onError,视觉动画将无声继续。可设置其他毫秒数,或使用 false 无限等待。取消操作会立即停止等待。共享加载仍可为后续操作准备音频;显式 prepareAudio() 仍会等待解码完成。
每个实例拥有 8 MiB 的 LRU 预算,用于正向和反向解码缓冲区。同一页面的实例共享网络请求、解码缓冲区和一个引用计数的 AudioContext。可通过 audioPreparation.cacheByteBudget 设置实例所有权预算;destroy() 会释放该实例的所有权。
配对删除与恢复
const snap = '/sounds/snap.mp3';
| 目标 | remove | restore |
|---|---|---|
| 同一文件,恢复时倒放 | { src: snap } | { src: snap, reverse: true } |
| 两个不同文件 | { src: out } | { src: back } |
| 仅删除时播放 | { src: snap } | false |
| 仅恢复时播放 | false | { src: back } |
所有内置预设都采用第一种方式。
内置名称与资源 URL
根入口与 /snapdom 会解析这些稳定的 src 名称,而不会把音频选择与粒子几何绑定:
type BuiltInSound = 'dust' | 'scatter' | 'vapor' | 'wind';
const sound = { src: 'wind', volume: 0.3 };
当其他音频系统或无依赖的 /core 入口需要生成后的文件 URL 时,请导入 /sounds:
import { builtInSounds } from 'vanilla-disintegrate/sounds';
audioElement.src = builtInSounds.vapor;
builtInSounds 的类型为 Readonly<Record<BuiltInSound, string>>。
这些文件保持为已编码、可缓存的资源,不会以 base64 嵌入 JavaScript,也不会被提前解码。
支持的来源与本地文件
type SoundSource = BuiltInSound | string | URL | Blob | ArrayBuffer | ArrayBufferView | AudioBuffer;
字符串与 URL 会被请求;blob 与编码字节在本地解码;现成的 AudioBuffer 可直接复用。浏览器 File 属于 Blob,因此用户音频可以完全留在设备上:
import Disintegrator, { createParticleEffect } from 'vanilla-disintegrate/snapdom';
const file = fileInput.files?.[0];
if (file) {
const effects = new Disintegrator({
effect: createParticleEffect(),
sound: { remove: { src: file }, restore: false },
});
}
包既不会上传也不会持久化该文件。
播放选项
interface SoundOptions {
src: SoundSource;
reverse?: boolean;
volume?: number;
duration?: number;
fadeDuration?: number;
delay?: number;
playbackRate?: number;
}
const sound = {
src: new URL('/sounds/snap.ogg', location.href),
volume: 0.35,
delay: 40,
duration: 0.7,
fadeDuration: 0.12,
playbackRate: 1.05,
};
reverse 默认为 false;线性 volume 的范围为 0 到 1,默认值为 1。delay 使用毫秒,默认为 0。duration 与 fadeDuration 使用秒:省略时,声音时长跟随视觉动画且不超过源文件长度,淡入淡出默认为 0。playbackRate 默认为 1,最小规范为 0.01。
反向播放
const remove = { src: '/sounds/snap.mp3' };
const restore = { src: '/sounds/snap.mp3', reverse: true };
启用 reverse 后,播放会对齐,使反向录音的结尾落在视觉动画结尾。裁剪从源文件尾部开始,fadeDuration 会变成淡入。反向解码副本会被缓存;调用方提供的 AudioBuffer 数据不会被修改。
自定义播放器
使用工厂接入现有音频引擎:
interface SoundContext {
operation: 'remove' | 'restore';
element: HTMLElement;
signal: AbortSignal;
}
interface SoundPlayback {
finished?: PromiseLike<unknown>;
stop?(): void;
dispose?(): void;
}
type SoundFactory = (context: SoundContext) => SoundPlayback | PromiseLike<SoundPlayback | void> | void;
const sound = ({ operation, element, signal }) => {
const playback = audioEngine.play(operation === 'remove' ? 'card-out' : 'card-in');
const stop = () => playback.stop();
signal.addEventListener('abort', stop, { once: true });
return {
finished: playback.finished,
stop,
dispose() {
signal.removeEventListener('abort', stop);
playback.dispose();
},
};
};
工厂接收操作、目标元素和中止信号。finished 可以在自定义播放结束后触发提前清理。返回 stop() 用于取消,返回 dispose() 用于无条件清理。
故障隔离
自动播放限制、请求失败、解码错误和自定义播放器异常,都不会仅因音频而拒绝视觉操作。它们会转发给 onError,动画与所选 DOM 操作继续执行。