Disintegrator 把一套完整视觉配置、可选音频、捕获、布局动画、后台准备与保留节点存储组合在一起。
入口
| 入口 | 运行时内容 | 额外依赖 |
|---|---|---|
vanilla-disintegrate/core | Disintegrator、效果与预设辅助函数,以及所有公共类型 | 无 |
vanilla-disintegrate/particles | 粒子工厂、视觉预设和粒子类型 | 无 |
vanilla-disintegrate/sounds | 四个内置音频文件的 URL 和声音类型 | 无 |
vanilla-disintegrate | Disintegrator、四个完整预设、粒子、声音、辅助函数和公共类型 | 无 |
vanilla-disintegrate/snapdom | 根入口 API,并把 SnapDOM 配置为默认捕获适配器 | @zumer/snapdom |
根入口与 /snapdom 注册四个完整内置预设。只有 /snapdom 提供捕获适配器;/core 只提供生命周期,不包含内置预设、粒子、内置声音文件或捕获。任何入口都不会选择默认预设。粒子、捕获和音频页面分别详细说明了对应的专用导出。
构造器
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 | 接收固定定位的动画覆盖层 |
zIndex | number | 2147483646 | 设置覆盖层层级 |
random | () => number | Math.random | 为每次操作初始化可复现的伪随机流 |
snapdom | SnapdomCaptureOptions | — | 配置 SnapDOM 的 toCanvas();仅 /snapdom 可用 |
onTrigger | (context) => void | — | 验证后、捕获前调用 |
onStart | (context) => void | — | DOM 变更后、播放开始时调用 |
onComplete | (context) => void | — | 视觉阶段正常完成后调用 |
onError | (error, context) => void | — | 接收隔离的捕获、动画、声音与回调错误 |
¹ SnapDOM 仅在 /snapdom 中作为默认值。若所选效果需要快照,根入口与 /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)' | 传递给 WAAPI 的 CSS timing function |
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 工作 |
concurrency | number | 1 | 最大并发捕获数,规范为 1 到 8 的整数 |
invalidateOnResize | boolean | true | 尺寸变化时使已准备快照失效 |
observeMutations | boolean | false | 监听子树变化和后代元素的加载事件 |
idleTimeout | number | 750 | 等待 requestIdleCallback 的最长毫秒数 |
fallbackDelay | number | 250 | 不支持 idle callback 时的延迟 |
scrollSettle | number | 120 | 滚动停止后开始后台工作的静默时间 |
animationSettle | number | 400 | 捕获前等待元素现有动画的最长时间 |
cachePixelBudget | number | 8_000_000 | 以 canvas 物理像素计算的 LRU 容量 |
传入 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 由根入口和 /snapdom 导出。每个值都是一个完整且冻结的粒子与音频预设。/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 });
备用效果既适用于预设,也适用于声明 requires: 'webgl2' 的任意自定义效果。它可以使用 CSS、WAAPI、Canvas 2D、SVG 或其他渲染器,默认保持静音。TypeScript 不允许在 fallback 中再次传入 WebGL2 阶段,JavaScript 配置也会在运行时接受同样的验证。若预检查后粒子渲染器仍无法启动,库会在已创建快照的基础上尝试备用效果。
动画工厂接收 AnimationContext:
| 字段 | 类型 | 含义 |
|---|---|---|
operation | 'remove' | 'restore' | 当前阶段 |
element | HTMLElement | 应用中的原始元素 |
layer | HTMLElement | 由库管理的空白定位覆盖层 |
snapshot | HTMLCanvasElement | null | 原始捕获;无快照阶段为 null |
visual | HTMLCanvasElement | null | 按需创建的 snapshot 全尺寸副本 |
bounds | DOMRectReadOnly | 覆盖层使用的最终视口几何信息 |
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 替换默认的 element.remove() 提交。响应式渲染器需要通过自身状态删除节点时使用它。若回调抛错,错误会发送给 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 | 两种操作 | 添加捕获前的操作回调 |
onStart | 两种操作 | 添加 DOM 提交后的操作回调 |
onComplete | 两种操作 | 添加视觉效果正常完成后的操作回调 |
onError | 两种操作 | 添加隔离的错误回调 |
retain | 删除 | 使用 operation.removalId 保存被分离的原始节点 |
layout | 删除 | 为本次删除启用或覆盖布局动画 |
detach | 删除 | 替换默认的 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() 通过 context.invalidate 同时刷新预备 canvas 和捕获引擎。显式 prepare() 也要求刷新;clearPrepared() 后的下一次捕获必须刷新。这包括 DOM 观察器无法检测的 CSSOM 修改。具有独立缓存的自定义适配器必须处理 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;
}
句柄不暴露元素,并在结束后释放其引用。
| 状态 | 内容操作 | 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 操作或清理。