Vanilla Disintegratev1.6.1
ZH
本页内容

音频属于完整预设,与自定义视觉效果相互独立。请从点击等用户手势中调用 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';
目标removerestore
同一文件,恢复时倒放{ 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 操作继续执行。

在 GitHub 上编辑此页