Audio is part of a complete preset and independent from a custom visual effect. Call remove() or restore() from a user gesture such as a click so the library can resume Web Audio playback.
Preset audio and custom audio
A built-in preset is audible by default. It can only be muted; replacement audio would make it a different preset.
const audible = new Disintegrator({ preset: 'dust' });
const muted = new Disintegrator({ preset: 'dust', sound: false });
effects.remove(element, { preset: 'vapor', sound: false });
A custom effect is silent by default. Supply an explicit pair when it needs audio:
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 and restore are independent. At least one must be configured; use false for an intentionally silent phase.
type SoundDefinition = SoundOptions | SoundFactory;
type SoundPair =
| { remove: SoundDefinition; restore?: SoundDefinition | false }
| { remove?: SoundDefinition | false; restore: SoundDefinition };
type SoundSelection = SoundPair;
Prepare before the first interaction
Enabled audio is prepared during browser idle time by default. Use immediate when readiness matters more than initial work:
const effects = new Disintegrator({
preset: 'vapor',
audioPreparation: 'immediate',
});
The sounds field accepts actual sound definitions or pairs, not visual effect names:
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 disables automatic work; explicit prepareAudio() still works. If native audio is not ready, visual and sound wait for decoding and start together. Custom player factories own their loading and are not invoked by preparation.
audioWaitTimeout limits how long an operation waits for native audio: 1500 ms by default. On timeout, onError receives the reason and the visual continues without sound. Set a different duration in milliseconds, or false to wait indefinitely. Cancellation stops waiting immediately. Shared loading may continue to prepare audio for later operations; explicit prepareAudio() still waits for decoding.
Each instance has an 8 MiB LRU budget for decoded forward and reversed buffers. Instances on the same page share fetches, decoded buffers, and one reference-counted AudioContext. Configure the per-instance ownership budget through audioPreparation.cacheByteBudget; destroy() releases that instance’s ownership.
Pair removal and restoration
const snap = '/sounds/snap.mp3';
| What you want | remove | restore |
|---|---|---|
| One file, reversed on restoration | { src: snap } | { src: snap, reverse: true } |
| Two different files | { src: out } | { src: back } |
| Sound only when removing | { src: snap } | false |
| Sound only when restoring | false | { src: back } |
All built-in presets use the first arrangement.
Bundled names and asset URLs
The root and /snapdom entries resolve these stable src names without coupling audio selection to particle geometry:
type BuiltInSound = 'dust' | 'scatter' | 'vapor' | 'wind';
const sound = { src: 'wind', volume: 0.3 };
Import /sounds when another audio system or the dependency-free /core entry needs the generated file URLs:
import { builtInSounds } from 'vanilla-disintegrate/sounds';
audioElement.src = builtInSounds.vapor;
builtInSounds has the type Readonly<Record<BuiltInSound, string>>.
The files stay encoded, cacheable assets. They are not embedded as base64 or eagerly decoded into the JavaScript bundle.
Supported sources and local files
type SoundSource = BuiltInSound | string | URL | Blob | ArrayBuffer | ArrayBufferView | AudioBuffer;
Strings and URLs are fetched; blobs and encoded bytes are decoded locally; an AudioBuffer can be reused directly. A browser File is a Blob, so user audio can remain entirely on the device:
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 },
});
}
The package neither uploads nor persists the file.
Playback options
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 defaults to false; volume is linear from 0 to 1 and defaults to 1. delay is measured in milliseconds and defaults to 0. duration and fadeDuration are seconds: duration follows the visual and is capped by the source length when omitted, while fading defaults to 0. playbackRate defaults to 1 and is normalized to at least 0.01.
Playing a sound backwards
const remove = { src: '/sounds/snap.mp3' };
const restore = { src: '/sounds/snap.mp3', reverse: true };
With reverse, playback is aligned so the end of the reversed recording lands on the visual end. Trimming comes from the source’s end, and fadeDuration becomes a fade-in. The reversed decoded copy is cached; caller-owned AudioBuffer data is not modified.
Custom player
Use a factory to integrate an existing audio engine:
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();
},
};
};
The factory receives the operation, target element, and abort signal. finished may request early disposal after custom playback settles. Return stop() for cancellation and dispose() for unconditional cleanup.
Failure isolation
Autoplay restrictions, fetch failures, decode errors, and custom player exceptions never reject a visual operation solely because of audio. They are forwarded to onError; animation and the selected DOM action continue.