You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用React实现Telegram消息删除的溶解粒子效果?

复刻Telegram消息删除的「溶解」动画效果

我想要实现Telegram中消息删除时的溶解效果,动画表现为内容逐渐溶解——淡出、模糊,然后散射成粒子,最终粒子消失。

需要做一个React组件,能包裹任意子元素,触发动画(比如点击按钮)时按以下步骤执行:

  • 淡出
  • 模糊
  • 过渡为粒子效果(粒子要从内容本身生成,而非仅覆盖区域)
  • 短时间后粒子消失

我尝试用react-tsparticles实现粒子效果,gsap做淡出和模糊动画,但目前效果不够流畅自然,尤其是粒子部分达不到预期。

以下是我目前的代码:

const { useRef } = React;
    
function DisintegrateEffect() {
  const divRef = useRef(null);

  const handleDelete = () => {
    if (!divRef.current) return;

    gsap.to(divRef.current, {
      duration: 0.8,
      opacity: 0,
      scale: 1.2,
      filter: 'blur(15px)',
      y: -10,
      ease: 'power2.out',
      onComplete: () => {
        divRef.current.style.display = 'none';
      }
    });
  };

  return (
    <div
      ref={divRef}
      onClick={handleDelete}
      style={{
        padding: '16px',
        background: 'red',
        color: 'white',
        fontWeight: 'bold',
        cursor: 'pointer',
        display: 'inline-block'
      }}
    >
      删除
    </div>
  );
}

ReactDOM.render(<DisintegrateEffect />, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js"></script>
<div id="container"></div>

优化后的实现方案

要实现和Telegram一致的流畅效果,核心是让粒子从内容的像素位置生成,而非随机覆盖区域。可以通过将目标元素转为Canvas图像、提取像素数据来生成对应位置的粒子,再结合GSAP同步动画节奏。

实现步骤

  1. 捕获目标元素的图像数据:用html2canvas将子元素转为Canvas,获取每个像素的位置和颜色。
  2. 同步GSAP动画与粒子生成:在淡出模糊的动画过程中,逐步生成对应位置的粒子。
  3. 粒子散射动画:让粒子从原位置向外扩散并消失,模拟自然溶解效果。

完整代码示例

import React, { useRef, useState, useEffect } from 'react';
import { loadFull } from 'tsparticles';
import { Particles } from 'react-tsparticles';
import gsap from 'gsap';
import html2canvas from 'html2canvas';

const DisintegrateWrapper = ({ children, triggerAnimation }) => {
  const contentRef = useRef(null);
  const particlesRef = useRef(null);
  const [showContent, setShowContent] = useState(true);
  const [particlesOptions, setParticlesOptions] = useState({});

  // 初始化tsparticles
  const particlesInit = async (main) => {
    await loadFull(main);
  };

  // 从元素像素生成粒子配置
  const generateParticlesFromElement = async () => {
    if (!contentRef.current) return;

    // 将目标元素转为canvas
    const canvas = await html2canvas(contentRef.current);
    const ctx = canvas.getContext('2d');
    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const pixels = imageData.data;

    const particles = [];
    // 每隔4个像素取一个点,避免粒子过多影响性能
    for (let y = 0; y < canvas.height; y += 4) {
      for (let x = 0; x < canvas.width; x += 4) {
        const index = (y * canvas.width + x) * 4;
        const alpha = pixels[index + 3];
        // 只保留不透明的像素点
        if (alpha > 0) {
          particles.push({
            x: x,
            y: y,
            color: `rgba(${pixels[index]}, ${pixels[index + 1]}, ${pixels[index + 2]}, ${alpha / 255})`,
          });
        }
      }
    }

    // 设置粒子参数
    setParticlesOptions({
      particles: {
        number: { value: particles.length },
        position: {
          value: particles.map(p => ({ x: p.x, y: p.y })),
        },
        color: { value: particles.map(p => p.color) },
        shape: { type: 'circle' },
        size: { value: 2 },
        move: {
          enable: true,
          speed: 6,
          direction: 'none',
          random: true,
          straight: false,
          outModes: { default: 'out' },
        },
        opacity: {
          value: 1,
          animation: {
            enable: true,
            speed: 0.2,
            minimumValue: 0,
            sync: false,
          },
        },
      },
      interactivity: { events: { onClick: { enable: false } } },
      detectRetina: true,
    });
  };

  // 触发完整动画流程
  useEffect(() => {
    if (!triggerAnimation || !contentRef.current) return;

    const animate = async () => {
      // 第一步:执行淡出+模糊动画
      gsap.to(contentRef.current, {
        duration: 0.3,
        opacity: 0,
        filter: 'blur(8px)',
        ease: 'power2.out',
        onComplete: () => setShowContent(false),
      });

      // 同步生成粒子
      await generateParticlesFromElement();

      // 第二步:粒子散射后停止并消失
      setTimeout(() => {
        particlesRef.current?.stop();
      }, 800);
    };

    animate();
  }, [triggerAnimation]);

  return (
    <div style={{ position: 'relative', display: 'inline-block' }}>
      {showContent && (
        <div ref={contentRef} style={{ position: 'relative', zIndex: 1 }}>
          {children}
        </div>
      )}
      {!showContent && (
        <div style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }}>
          <Particles
            id="tsparticles"
            init={particlesInit}
            options={particlesOptions}
            ref={particlesRef}
          />
        </div>
      )}
    </div>
  );
};

// 使用示例
const App = () => {
  const [trigger, setTrigger] = useState(false);

  return (
    <div style={{ padding: '20px' }}>
      <DisintegrateWrapper triggerAnimation={trigger}>
        <div 
          style={{ 
            padding: '16px', 
            background: '#2563eb', 
            color: 'white', 
            borderRadius: '8px',
            cursor: 'pointer'
          }}
          onClick={() => setTrigger(true)}
        >
          点击删除这条消息
        </div>
      </DisintegrateWrapper>
    </div>
  );
};

export default App;

关键优化点

  • 像素级粒子生成:通过html2canvas捕获元素的像素信息,粒子完全匹配原内容的形状和颜色,还原Telegram的溶解质感。
  • 动画节奏同步:GSAP的淡出模糊动画与粒子生成同步执行,避免过渡突兀。
  • 性能优化:间隔采样像素点,控制粒子数量,同时调整粒子移动速度和透明度动画参数,平衡效果与性能。

依赖安装

需要安装以下依赖包:

npm install gsap react-tsparticles html2canvas

内容的提问来源于stack exchange,提问作者Ton Vital

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 13:40:21