Vanilla Disintegratev1.6.1
ZH
本页内容

Disintegrator 把一套完整视觉配置、可选音频、捕获、布局动画、后台准备与保留节点存储组合在一起。

入口

入口运行时内容额外依赖
vanilla-disintegrate/coreDisintegrator、效果与预设辅助函数,以及所有公共类型无
vanilla-disintegrate/particles粒子工厂、视觉预设和粒子类型无
vanilla-disintegrate/sounds四个内置音频文件的 URL 和声音类型无
vanilla-disintegrateDisintegrator、四个完整预设、粒子、声音、辅助函数和公共类型无
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 阶段的对象;未提供声音对时保持静音。

共享选项:

选项类型默认值作用
presetsRecord<string, PresetDefinition>{}添加可通过 preset 寻址的完整自定义预设
layoutboolean | LayoutOptionstrue删除后把相邻元素移动到新位置
preparationboolean | PreparationOptionsvisible-idle为已注册元素准备快照
audioPreparationfalse | 'immediate' | 'idle' | AudioPreparationOptions'idle'控制准备与解码音频所有权预算
audioWaitTimeoutnumber | false1500无声继续前的最长音频等待时间
respectReducedMotionbooleantrue用户请求减少动态效果时跳过动画
fallbackFallbackEffectDefinition—WebGL2 渲染器不可用时静默替换该阶段
captureSnapshotCaptureSnapDOM¹为需要快照的效果把元素转换成 canvas
overlayRootHTMLElement | () => HTMLElementdocument.body接收固定定位的动画覆盖层
zIndexnumber2147483646设置覆盖层层级
random() => numberMath.random为每次操作初始化可复现的伪随机流
snapdomSnapdomCaptureOptions—配置 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 技术,在删除后把受影响的元素从原位置移动到新位置。

参数类型默认值作用
enabledbooleantrue启用布局动画,同时保留其他默认设置
durationnumber300重排动画时长,单位为毫秒
easingstring'cubic-bezier(0.22, 1, 0.36, 1)'传递给 WAAPI 的 CSS timing function
containerHTMLElement | (element) => HTMLElement | null目标的父元素定义坐标空间和可选的高度动画目标
siblings'following' | 'all' | (element, container) => HTMLElement[]'following'选择参与动画的子元素
animateContainerbooleantrue为变化后的容器高度添加动画

传入 layout: false 会关闭重排动画。layout 只用于 remove();恢复时,被插入的元素会在最终布局位置执行动画。负数 duration 会归零,非有限值会使用默认值。

PreparationOptions

参数类型默认值作用
strategy'immediate' | 'idle' | 'visible-idle''visible-idle'决定何时可以捕获已注册元素
shouldPrepare(element) => boolean() => true在加入调度队列前筛选元素
rootElement | Document | nullnullIntersection Observer 的根
rootMarginstring'300px 0px'在元素进入视口之前启动 visible-idle 工作
concurrencynumber1最大并发捕获数,规范为 1 到 8 的整数
invalidateOnResizebooleantrue尺寸变化时使已准备快照失效
observeMutationsbooleanfalse监听子树变化和后代元素的加载事件
idleTimeoutnumber750等待 requestIdleCallback 的最长毫秒数
fallbackDelaynumber250不支持 idle callback 时的延迟
scrollSettlenumber120滚动停止后开始后台工作的静默时间
animationSettlenumber400捕获前等待元素现有动画的最长时间
cachePixelBudgetnumber8_000_000以 canvas 物理像素计算的 LRU 容量

传入 preparation: false 会关闭自动准备,但不会禁用显式的 prepare() 调用。负数的计时和缓存预算会归零,非有限值会使用对应默认值。

AudioPreparationOptions

参数类型默认值作用
strategy'immediate' | 'idle''idle'决定何时获取并解码原生音频
soundsSoundPreparationSelection实例声音选择要准备的声音,替代实例声音
cacheByteBudgetnumber8 * 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'当前阶段
elementHTMLElement应用中的原始元素
layerHTMLElement由库管理的空白定位覆盖层
snapshotHTMLCanvasElement | null原始捕获;无快照阶段为 null
visualHTMLCanvasElement | null按需创建的 snapshot 全尺寸副本
boundsDOMRectReadOnly覆盖层使用的最终视口几何信息
signalAbortSignal取消操作或销毁实例时中止
reducedMotionfalse减少动态效果的请求会在工厂运行前被跳过
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 操作或清理。

在 GitHub 上编辑此页