如何用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同步动画节奏。
实现步骤
- 捕获目标元素的图像数据:用
html2canvas将子元素转为Canvas,获取每个像素的位置和颜色。 - 同步GSAP动画与粒子生成:在淡出模糊的动画过程中,逐步生成对应位置的粒子。
- 粒子散射动画:让粒子从原位置向外扩散并消失,模拟自然溶解效果。
完整代码示例
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
相关产品推荐
相关产品推荐

