Disintegrator объединяет одну полную визуальную конфигурацию, необязательное аудио, захват, анимацию layout, фоновую подготовку и хранилище удалённых узлов.
Entry points
| Entry point | Содержимое во время выполнения | Дополнительная зависимость |
|---|---|---|
vanilla-disintegrate/core | Disintegrator, вспомогательные функции эффектов и пресетов, все публичные типы | Нет |
vanilla-disintegrate/particles | Фабрики частиц, визуальные пресеты и типы частиц | Нет |
vanilla-disintegrate/sounds | URL четырёх встроенных аудиофайлов и типы звука | Нет |
vanilla-disintegrate | Disintegrator, четыре полных пресета, частицы, звуки, helpers и публичные типы | Нет |
vanilla-disintegrate/snapdom | Корневой API со SnapDOM в качестве стандартного адаптера захвата | @zumer/snapdom |
Корневой entry point и /snapdom регистрируют четыре полных встроенных пресета. Только /snapdom предоставляет адаптер захвата; /core содержит жизненный цикл без встроенных пресетов, частиц, встроенных звуковых файлов и захвата. Ни один entry point не выбирает пресет по умолчанию. Специализированные экспорты подробно описаны на страницах частиц, захвата и аудио.
Конструктор
import Disintegrator, { createParticleEffect } from 'vanilla-disintegrate/snapdom';
const ready = new Disintegrator({ preset: 'dust' });
const custom = new Disintegrator({ effect: createParticleEffect() });
Нужно передать ровно одно из двух: preset или effect.
type DisintegratorOptions =
| { preset: PresetSelection; effect?: never; sound?: false; fallback?: FallbackEffectDefinition }
| { preset?: never; effect: EffectDefinition; sound?: false | SoundSelection; fallback?: FallbackEffectDefinition };
Пресет — неделимый выбор визуала и аудио. Его можно заглушить через sound: false, но нельзя совместить с заменяющей анимацией или другим звуком. Пользовательский эффект — объект с полными фазами remove и restore; без пары звуков он бесшумный.
Общие опции:
| Опция | Тип | По умолчанию | Назначение |
|---|---|---|---|
presets | Record<string, PresetDefinition> | {} | Добавляет полные пресеты, доступные через preset |
layout | boolean | LayoutOptions | true | Перемещает соседей на новые позиции после удаления |
preparation | boolean | PreparationOptions | visible-idle | Готовит снимки зарегистрированных элементов |
audioPreparation | false | 'immediate' | 'idle' | AudioPreparationOptions | 'idle' | Управляет подготовкой и бюджетом владения декодированным аудио |
audioWaitTimeout | number | false | 1500 | Максимальное ожидание аудио перед продолжением без звука |
respectReducedMotion | boolean | true | Пропускает анимацию при запросе уменьшенного движения |
fallback | FallbackEffectDefinition | — | Бесшумно заменяет фазу, которой недоступен WebGL2 |
capture | SnapshotCapture | SnapDOM¹ | Превращает элемент в canvas для эффектов, которым нужен снимок |
overlayRoot | HTMLElement | () => HTMLElement | document.body | Принимает fixed-слои анимации |
zIndex | number | 2147483646 | Задаёт уровень слоя |
random | () => number | Math.random | Инициализирует воспроизводимый поток случайных чисел для операции |
snapdom | SnapdomCaptureOptions | — | Настраивает toCanvas() SnapDOM; доступно только из /snapdom |
onTrigger | (context) => void | — | Вызывается после проверки и до захвата |
onStart | (context) => void | — | Вызывается после изменения DOM при старте воспроизведения |
onComplete | (context) => void | — | Вызывается после нормального завершения визуальной фазы |
onError | (error, context) => void | — | Получает изолированные ошибки захвата, анимации, звука и колбэков |
¹ SnapDOM используется по умолчанию только в /snapdom. Корневой entry point и /core требуют capture, если выбранному эффекту нужен снимок.
Ошибки фоновой подготовки сообщают context.operation === 'prepare'; ошибки операций сообщают remove или restore.
LayoutOptions
Анимация раскладки использует технику FLIP, чтобы после удаления переместить затронутые элементы с их прежних позиций.
| Параметр | Тип | По умолчанию | Назначение |
|---|---|---|---|
enabled | boolean | true | Включает анимацию раскладки, не заменяя остальные значения по умолчанию |
duration | number | 300 | Длительность анимации перестроения в миллисекундах |
easing | string | 'cubic-bezier(0.22, 1, 0.36, 1)' | CSS timing function для WAAPI |
container | HTMLElement | (element) => HTMLElement | null | Родитель цели | Задаёт систему координат и необязательную цель анимации высоты |
siblings | 'following' | 'all' | (element, container) => HTMLElement[] | 'following' | Выбирает участвующие дочерние элементы |
animateContainer | boolean | true | Анимирует изменившуюся высоту контейнера |
layout: false отключает анимацию перестроения. layout применяется только к remove(); при восстановлении вставленный элемент анимируется в своей итоговой позиции. Отрицательный duration приводится к нулю, а нечисловой — к значению по умолчанию.
PreparationOptions
| Параметр | Тип | По умолчанию | Назначение |
|---|---|---|---|
strategy | 'immediate' | 'idle' | 'visible-idle' | 'visible-idle' | Определяет, когда можно захватывать зарегистрированные элементы |
shouldPrepare | (element) => boolean | () => true | Фильтрует элементы до постановки в очередь |
root | Element | Document | null | null | Корень Intersection Observer |
rootMargin | string | '300px 0px' | Запускает visible-idle до входа элемента во viewport |
concurrency | number | 1 | Максимум одновременных захватов; целое число от 1 до 8 |
invalidateOnResize | boolean | true | Инвалидирует подготовленный снимок при изменении размеров |
observeMutations | boolean | false | Следит за изменениями поддерева и событиями загрузки потомков |
idleTimeout | number | 750 | Максимальное ожидание requestIdleCallback в миллисекундах |
fallbackDelay | number | 250 | Задержка при отсутствии idle callbacks |
scrollSettle | number | 120 | Пауза после прокрутки до фоновой работы |
animationSettle | number | 400 | Максимальное ожидание активных анимаций элемента перед захватом |
cachePixelBudget | number | 8_000_000 | Ёмкость LRU в физических пикселях canvas |
preparation: false отключает автоматическую подготовку, но не явные вызовы prepare(). Отрицательные значения времени и бюджета кэша приводятся к нулю, а нечисловые — к значениям по умолчанию.
AudioPreparationOptions
| Параметр | Тип | По умолчанию | Назначение |
|---|---|---|---|
strategy | 'immediate' | 'idle' | 'idle' | Определяет момент загрузки и декодирования нативного аудио |
sounds | SoundPreparationSelection | Звук экземпляра | Выбирает звуки для подготовки вместо звука экземпляра |
cacheByteBudget | number | 8 * 1024 * 1024 | Бюджет владения декодированным PCM в LRU одного экземпляра |
audioPreparation: false отключает автоматическую работу. Явная подготовка аудио остаётся доступной. Отрицательный бюджет кэша приводится к нулю.
Полные пресеты
import Disintegrator, { createParticleEffect, definePreset } from 'vanilla-disintegrate/snapdom';
const dissolve = definePreset({
effect: createParticleEffect({
remove: { curve: 'float' },
restore: { curve: 'settle' },
}),
sound: {
remove: { src: '/audio/out.ogg', volume: 0.4 },
restore: { src: '/audio/in.ogg', volume: 0.4 },
},
});
const effects = new Disintegrator({
preset: 'dissolve',
presets: { dissolve },
});
definePreset() замораживает пресет, визуальные фазы и объекты настроек нативного звука, не замораживая бинарные аудиоданные пользователя.
const builtInPresets: Readonly<Record<'dust' | 'scatter' | 'vapor' | 'wind', Readonly<PresetDefinition>>>;
builtInPresets экспортируется корневым entry point и /snapdom. Каждое значение — полный замороженный пресет частиц и аудио. Entry point /particles вместо него экспортирует particlePresets, содержащий только визуальную конфигурацию.
Контракт пользовательского эффекта
import { defineEffect } from 'vanilla-disintegrate/core';
interface EffectPhase {
requires?: 'webgl2';
needsSnapshot?: boolean;
animate: AnimationFactory;
}
interface EffectDefinition {
remove: EffectPhase;
restore: EffectPhase;
}
const effect = defineEffect({
remove: { needsSnapshot: false, animate: removeFactory },
restore: { needsSnapshot: false, animate: restoreFactory },
});
needsSnapshot по умолчанию равен true. defineEffect() замораживает эффект и объекты обеих фаз, сохраняя их узкий выведенный TypeScript-тип.
Резервный эффект WebGL2
createParticleEffect() и встроенные пресеты автоматически помечают свои фазы как requires: 'webgl2'. Перед захватом библиотека проверяет доступность WebGL2. Если он выключен или недоступен, fallback запускается вместо основной фазы и не создаёт ненужный Canvas-снимок.
import Disintegrator, { defineEffect } from 'vanilla-disintegrate/snapdom';
const fade = defineEffect({
remove: {
needsSnapshot: false,
animate: ({ element }) => element.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 180 }),
},
restore: {
needsSnapshot: false,
animate: ({ element }) => element.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 180 }),
},
});
const effects = new Disintegrator({ preset: 'dust', fallback: fade });
Fallback подходит и к пресетам, и к любому пользовательскому эффекту, фаза которого объявляет requires: 'webgl2'. Он может использовать CSS, WAAPI, Canvas 2D, SVG или другой рендерер и по умолчанию работает без звука. TypeScript не позволяет передать в fallback ещё одну WebGL2-фазу; JavaScript-конфигурация проходит такую же проверку в рантайме. Если рендерер частиц не сможет запуститься после предварительной проверки, библиотека попробует fallback после уже созданного снимка.
Фабрика анимации получает AnimationContext:
| Поле | Тип | Значение |
|---|---|---|
operation | 'remove' | 'restore' | Активная фаза |
element | HTMLElement | Исходный элемент приложения |
layer | HTMLElement | Пустой позиционированный слой, принадлежащий библиотеке |
snapshot | HTMLCanvasElement | null | Исходный снимок либо null для фазы без снимка |
visual | HTMLCanvasElement | null | Лениво создаваемая полноразмерная копия snapshot |
bounds | DOMRectReadOnly | Итоговая геометрия во viewport, использованная слоем |
signal | AbortSignal | Прерывается при отмене или уничтожении экземпляра |
reducedMotion | false | Запросы уменьшенного движения пропускаются до вызова фабрики |
random | () => number | Псевдослучайный поток отдельной операции |
addCleanup | (callback: () => void) => void | Регистрирует очистку для завершения, отмены и ошибки |
AnimationFactory может вернуть Web Animation, любой PromiseLike, контроллер AnimationPlayback либо null, если анимацию создать не удалось.
interface AnimationPlayback {
finished: PromiseLike<unknown>;
element?: HTMLElement;
duration?: number;
layoutDelay?: number;
cancel?(): void;
dispose?(): void;
}
element добавляется в layer, если у него ещё нет родителя. duration синхронизирует нативное аудио, layoutDelay задерживает перестроение при удалении, cancel() останавливает воспроизведение, а dispose() освобождает ресурсы рендерера при любом варианте завершения.
remove(target, options?)
Отсоединяет найденный элемент и синхронно возвращает EffectOperation.
remove(target: EffectTarget, options?: RemoveOptions): EffectOperation;
const operation = effects.remove('.card', {
preset: 'scatter',
sound: false,
retain: true,
layout: { duration: 260 },
detach: (element) => element.remove(),
});
target может быть HTMLElement или строкой-селектором. Отсутствующий селектор выбрасывает исключение до начала операции.
detach заменяет стандартный commit через element.remove(). Используйте его, когда реактивный рендерер должен удалить узел через своё состояние. Если callback выбросит ошибку, она попадёт в onError, а библиотека безопасно выполнит нативное удаление.
restore(target, options?)
Анимирует подключённый и измеримый элемент в его текущей геометрии.
restore(target: EffectTarget, options?: RestoreOptions): EffectOperation;
const operation = effects.restore(element, {
preset: 'scatter',
sound: false,
});
Метод выбрасывает исключение, если элемент отсоединён либо имеет нулевую ширину или высоту.
Для любой операции передайте один полный preset, один пользовательский effect с необязательным определением звука этой фазы или ничего, чтобы унаследовать конфигурацию экземпляра либо сохранённого узла. Унаследованный звук и звук пресета можно только заглушить через sound: false.
Параметры операций
| Параметр | Где доступен | Значение |
|---|---|---|
preset | Обе операции | Выбирает зарегистрированное имя или полный пресет, переданный напрямую |
effect | Обе операции | Выбирает полный пользовательский эффект; несовместим с preset |
sound | Обе операции | Добавляет звук фазы к явному пользовательскому эффекту либо заглушает через false |
onTrigger | Обе операции | Добавляет callback операции перед захватом |
onStart | Обе операции | Добавляет callback операции после изменения DOM |
onComplete | Обе операции | Добавляет callback операции после нормального завершения визуала |
onError | Обе операции | Добавляет изолированный callback ошибок |
retain | Удаление | Сохраняет отсоединённый оригинал под operation.removalId |
layout | Удаление | Включает или переопределяет анимацию раскладки для этого удаления |
detach | Удаление | Заменяет стандартный commit через element.remove() |
Колбэки операции выполняются дополнительно к колбэкам экземпляра. Если не передан ни preset, ни effect, визуал и звук наследуются; унаследованный звук и звук пресета можно заглушить, но нельзя заменить независимо.
Сохранённые элементы
const element = effects.take(removalId); // HTMLElement | null
const discarded = effects.discard(removalId); // boolean
const count = effects.discardAll(); // number
take() забирает запись из хранилища. discard() и discardAll() освобождают ссылки без вставки содержимого.
Подготовка снимков
const unregister = effects.register(elements);
await effects.prepare(elements);
effects.invalidate(elements);
effects.clearPrepared();
invalidate() обновляет и подготовленные canvas, и кеши движка через context.invalidate. Явный prepare() также запрашивает обновление; после clearPrepared() оно требуется при следующем захвате. Это относится и к изменениям CSSOM, которые не видны DOM-наблюдателям. Пользовательские адаптеры с собственным кешем должны учитывать context.invalidate.
register() следует выбранной стратегии. prepare() захватывает сразу даже при отключённой фоновой подготовке. Повторные регистрации одного элемента используют счётчик ссылок; каждая возвращённая функция отмены освобождает только свою регистрацию.
register() возвращает идемпотентную функцию () => void; prepare() возвращает Promise<void>; invalidate() и clearPrepared() возвращают void.
Подготовка аудио
await effects.prepareAudio({
remove: { src: 'dust' },
restore: { src: 'dust', reverse: true },
});
effects.discardPreparedAudio();
effects.clearPreparedAudio();
Без аргумента prepareAudio() и discardPreparedAudio() используют настроенную пару экземпляра. Явные методы работают даже при отключённой автоматической подготовке. Декодированные буферы используют ограниченный LRU владения экземпляра, а загрузки, буферы и AudioContext разделяются между экземплярами.
prepareAudio() возвращает Promise<void>, discardPreparedAudio() — число освобождённых декодированных записей во владении экземпляра, а clearPreparedAudio() — void.
destroy()
effects.destroy();
Отменяет активные операции, останавливает наблюдатели и фоновые задачи, освобождает снимки и владение декодированным аудио, очищает сохранённые узлы. Общий контекст воспроизведения закрывается после уничтожения последнего владельца. Повторный вызов destroy() безопасен; запуск новой работы после него выбрасывает исключение.
EffectOperation
interface EffectOperation {
readonly operation: 'remove' | 'restore';
readonly removalId: RemovalId | null;
readonly finished: Promise<{
operation: 'remove' | 'restore';
status: 'completed' | 'cancelled' | 'skipped' | 'rejected';
removalId: RemovalId | null;
}>;
cancel(): void;
}
Handle не открывает элемент и освобождает ссылку на него после завершения.
| Статус | Действие с содержимым | Когда завершается finished |
|---|---|---|
completed | Выполнено; визуальная фаза завершилась нормально | После визуальной очистки |
cancelled | Выполнено; визуал или аудио остановлены | После очистки отмены |
skipped | Выполнено без доступной визуальной фазы | После резервной очистки |
rejected | Не начато; DOM не изменён | После завершения текущего владельца |
Колбэки и приоритет
Колбэки экземпляра выполняются для каждой операции, а колбэки операции — рядом с ними. Приоритет имеет явная конфигурация операции, затем запомненная конфигурация сохранённого узла, затем конфигурация экземпляра. Конкурирующий вызов для уже занятого элемента завершается как rejected после освобождения текущим владельцем.
interface EffectContext {
operation: 'remove' | 'restore';
element: HTMLElement;
overlay: HTMLElement | null;
removalId: RemovalId | null;
}
interface EffectErrorContext {
operation: 'remove' | 'restore' | 'prepare';
element: HTMLElement;
overlay: HTMLElement | null;
removalId: RemovalId | null;
}
onTrigger вызывается после проверки и измерения, но до захвата и изменения DOM; его overlay равен null. onStart вызывается после изменения содержимого и начала воспроизведения. onComplete вызывается только после нормального завершения визуала и раскладки. onError получает ошибки подготовки, захвата, анимации, аудио, очистки и пользовательских колбэков, не позволяя им прервать обязательную работу с DOM или очистку.