Vanilla Disintegratev1.6.1
ZH
本页内容

响应式框架必须继续拥有它渲染的 DOM 节点。使用 detach 更新框架状态,而不是让 Vanilla Disintegrate 直接调用 element.remove()。

effects.remove(element, {
  preset: 'dust',
  detach: () => removeFromFrameworkState(),
});

该钩子会在元素完成测量和截图后、正式提交删除时运行。若省略 detach,库仍会使用默认的 element.remove()。

恢复时,先让框架渲染元素,等待 DOM 提交,再把新的元素引用传给 restore()。响应式状态应保存业务数据,而不是已分离的 DOM 节点,因此这类集成通常保持默认的 retain: false。

恢复得到的是新 DOM 节点。它会继承实例预设;若删除时选择了另一个操作级 preset 或自定义 effect,请向 restore() 传入同样的展示配置。记住的配置只会自动跟随同一个被保留 DOM 节点。

React

React 必须同步提交它所拥有节点的移除。使用 flushSync() 明确这条边界,并在 useLayoutEffect() 中恢复已经插入的新节点。

'use client';

import { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { flushSync } from 'react-dom';
import Disintegrator from 'vanilla-disintegrate/snapdom';

export function DisintegratingCard() {
  const [visible, setVisible] = useState(true);
  const cardRef = useRef<HTMLElement | null>(null);
  const effectsRef = useRef<Disintegrator | null>(null);
  const pendingRestore = useRef(false);

  useEffect(() => {
    const effects = new Disintegrator({ preset: 'dust' });
    effectsRef.current = effects;

    return () => {
      effects.destroy();
      effectsRef.current = null;
    };
  }, []);

  useLayoutEffect(() => {
    const effects = effectsRef.current;
    const element = cardRef.current;

    if (!visible || !pendingRestore.current || !effects || !element) return;

    pendingRestore.current = false;
    effects.restore(element, { preset: 'dust' });
  }, [visible]);

  function removeCard() {
    const effects = effectsRef.current;
    const element = cardRef.current;
    if (!effects || !element) return;

    effects.remove(element, {
      preset: 'dust',
      detach: () => flushSync(() => setVisible(false)),
    });
  }

  function restoreCard() {
    pendingRestore.current = true;
    setVisible(true);
  }

  return visible ? (
    <article ref={cardRef}>
      <h2>动态</h2>
      <button onClick={removeCard}>删除</button>
    </article>
  ) : (
    <button onClick={restoreCard}>恢复</button>
  );
}

在 effect 中创建实例,并在 cleanup 中调用 destroy();该写法兼容 React Strict Mode。Next.js 等 React SSR 框架需要把组件标记为客户端组件。

Vue

Vue 会在浏览器绘制前批量应用状态更新。等待 nextTick() 后,restore() 就能获得最终 DOM 和几何信息。

<script setup lang="ts">
import { nextTick, onMounted, onUnmounted, ref } from 'vue';
import Disintegrator from 'vanilla-disintegrate/snapdom';

const visible = ref(true);
const card = ref<HTMLElement | null>(null);
let effects: Disintegrator | null = null;

onMounted(() => {
  effects = new Disintegrator({ preset: 'dust' });
});

onUnmounted(() => {
  effects?.destroy();
});

function removeCard() {
  if (!effects || !card.value) return;

  effects.remove(card.value, {
    preset: 'dust',
    detach: () => {
      visible.value = false;
    },
  });
}

async function restoreCard() {
  visible.value = true;
  await nextTick();

  if (effects && card.value) {
    effects.restore(card.value, { preset: 'dust' });
  }
}
</script>

<template>
  <article v-if="visible" ref="card">
    <h2>动态</h2>
    <button @click="removeCard">删除</button>
  </article>
  <button v-else @click="restoreCard">恢复</button>
</template>

该组件同样适用于 Nuxt,因为浏览器专属初始化位于 onMounted() 中。

Svelte

通过 bind:this 获取实时节点,从 onMount() 返回清理函数,并在恢复前等待 tick()。

<script lang="ts">
  import { onMount, tick } from 'svelte';
  import Disintegrator from 'vanilla-disintegrate/snapdom';

  let visible = true;
  let card: HTMLElement;
  let effects: Disintegrator | null = null;

  onMount(() => {
    effects = new Disintegrator({ preset: 'dust' });
    return () => effects?.destroy();
  });

  function removeCard() {
    if (!effects || !card) return;

    effects.remove(card, {
      preset: 'dust',
      detach: () => {
        visible = false;
      },
    });
  }

  async function restoreCard() {
    visible = true;
    await tick();

    if (effects && card) {
      effects.restore(card, { preset: 'dust' });
    }
  }
</script>

{#if visible}
  <article bind:this={card}>
    <h2>动态</h2>
    <button onclick={removeCard}>删除</button>
  </article>
{:else}
  <button onclick={restoreCard}>恢复</button>
{/if}

onMount() 只在浏览器执行,因此同样适用于 SvelteKit SSR。

Solid

Solid 会同步更新 signal 控制的分支。在 setVisible(true) 后安排一个微任务,可确保恢复时已经看到插入的新节点。

import { createSignal, onCleanup, onMount, Show } from 'solid-js';
import Disintegrator from 'vanilla-disintegrate/snapdom';

export function DisintegratingCard() {
  const [visible, setVisible] = createSignal(true);
  let card!: HTMLElement;
  let effects: Disintegrator | null = null;

  onMount(() => {
    effects = new Disintegrator({ preset: 'dust' });
  });

  onCleanup(() => effects?.destroy());

  function removeCard() {
    if (!effects || !card) return;

    effects.remove(card, {
      preset: 'dust',
      detach: () => setVisible(false),
    });
  }

  function restoreCard() {
    setVisible(true);
    queueMicrotask(() => effects?.restore(card, { preset: 'dust' }));
  }

  return (
    <Show when={visible()} fallback={<button onClick={restoreCard}>恢复</button>}>
      <article ref={card}>
        <h2>动态</h2>
        <button onClick={removeCard}>删除</button>
      </article>
    </Show>
  );
}

SolidStart 使用相同的客户端生命周期。

Angular

Angular Signals 可以在 detach 中提交条件视图。afterNextRender() 避免在 SSR 期间创建实例,DestroyRef 负责清理。

import {
  afterNextRender,
  ChangeDetectorRef,
  Component,
  DestroyRef,
  ElementRef,
  inject,
  signal,
  viewChild,
} from '@angular/core';
import Disintegrator from 'vanilla-disintegrate/snapdom';

@Component({
  selector: 'app-disintegrating-card',
  standalone: true,
  template: `
    @if (visible()) {
      <article #card>
        <h2>动态</h2>
        <button (click)="removeCard()">删除</button>
      </article>
    } @else {
      <button (click)="restoreCard()">恢复</button>
    }
  `,
})
export class DisintegratingCard {
  readonly visible = signal(true);
  readonly card = viewChild<ElementRef<HTMLElement>>('card');

  private readonly changeDetector = inject(ChangeDetectorRef);
  private readonly destroyRef = inject(DestroyRef);
  private effects: Disintegrator | null = null;

  constructor() {
    afterNextRender(() => {
      this.effects = new Disintegrator({ preset: 'dust' });
    });
    this.destroyRef.onDestroy(() => this.effects?.destroy());
  }

  removeCard() {
    const element = this.card()?.nativeElement;
    if (!this.effects || !element) return;

    this.effects.remove(element, {
      preset: 'dust',
      detach: () => {
        this.visible.set(false);
        this.changeDetector.detectChanges();
      },
    });
  }

  restoreCard() {
    this.visible.set(true);
    this.changeDetector.detectChanges();

    const element = this.card()?.nativeElement;
    if (this.effects && element) {
      this.effects.restore(element, { preset: 'dust' });
    }
  }
}

可用于任何 DOM 界面

Vanilla Disintegrate 可用于任何包含可测量 DOM 元素的浏览器界面。它不依赖框架,也不需要适配器或单独的集成包。

上面的 React、Vue、Svelte、Solid 和 Angular 并不是支持平台的清单,而是各自典型生命周期场景的示例。同样的方法适用于任何其他框架、库或纯 JavaScript 应用。

示例使用各框架的官方 DOM 和生命周期 API:React refs 与 effects、Vue template refs 与 lifecycle、Svelte lifecycle、Solid refs 与 cleanup、Angular component lifecycle。

在 GitHub 上编辑此页