Аудио входит в состав готового пресета и не связано с пользовательским визуальным эффектом. Вызывайте 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() по-прежнему ждёт декодирования.
У каждого экземпляра есть LRU-бюджет 8 MiB для прямых и развёрнутых декодированных буферов. Экземпляры на одной странице совместно используют загрузки, буферы и один 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 файлов
Корневой entry point и /snapdom распознают стабильные имена src, не связывая выбор звука с геометрией частиц:
type BuiltInSound = 'dust' | 'scatter' | 'vapor' | 'wind';
const sound = { src: 'wind', volume: 0.3 };
Импортируйте /sounds, если другой аудиодвижок или независимый /core должен получить сгенерированные URL файлов:
import { builtInSounds } from 'vanilla-disintegrate/sounds';
audioElement.src = builtInSounds.vapor;
Тип builtInSounds — Readonly<Record<BuiltInSound, string>>.
Файлы остаются сжатыми и кэшируемыми ресурсами. Они не встраиваются в JavaScript как base64 и не декодируются заранее.
Поддерживаемые источники и локальные файлы
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 становится fade-in. Развёрнутая декодированная копия кэшируется; данные пользовательского 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() для обязательной очистки.
Изоляция ошибок
Ограничения autoplay, ошибки загрузки и декодирования, а также исключения пользовательского плеера сами по себе не отклоняют визуальную операцию. Они передаются в onError, а анимация и выбранное действие с DOM продолжаются.