如何让CSS挂载动画在组件卸载时反向执行?
原生JS/CSS实现React组件卸载时淡出动画的解决方案
我明白你遇到的问题——React组件卸载时会直接移除DOM元素,导致淡出动画还没来得及执行就消失了。你用动画库解决是很省心的选择,但原生方案的底层逻辑其实也挺清晰的,核心思路就是先触发动画,等动画完全结束后再真正卸载组件,下面给你详细拆解实现步骤:
1. 定义CSS动画关键帧
先写好淡入和淡出的基础动画样式,注意用forwards让动画结束后保持最终状态:
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } .fade-in { animation: fadeIn 0.5s forwards; } .fade-out { animation: fadeOut 0.5s forwards; }
2. React组件中控制动画与卸载时序
我们需要拆分状态,用一个状态控制动画触发,另一个状态控制组件是否渲染,再通过监听animationend事件来衔接动画和卸载:
import { useState, useEffect, useRef } from 'react'; function AnimatedBox() { // 控制组件是否渲染的核心状态 const [isRendered, setIsRendered] = useState(true); // 控制是否触发淡出动画 const [isFadingOut, setIsFadingOut] = useState(false); const boxRef = useRef(null); // 监听动画结束事件,动画完成后再卸载组件 useEffect(() => { const handleAnimationEnd = () => { if (isFadingOut) { setIsRendered(false); setIsFadingOut(false); } }; const box = boxRef.current; if (box) { box.addEventListener('animationend', handleAnimationEnd); } // 清理监听事件 return () => { if (box) { box.removeEventListener('animationend', handleAnimationEnd); } }; }, [isFadingOut]); // 触发淡出动画的方法 const handleClose = () => { setIsFadingOut(true); }; // 组件未渲染时返回null if (!isRendered) return null; return ( <div ref={boxRef} className={isFadingOut ? 'fade-out' : 'fade-in'} style={{ width: '100px', height: '100px', backgroundColor: '#42b983' }} > <button onClick={handleClose}>关闭方块</button> </div> ); }
3. 核心原理说明
- 点击关闭时,不是直接把
isRendered设为false,而是先开启isFadingOut状态,给方块加上淡出动画类; - 当浏览器触发
animationend事件(动画执行完毕),再把isRendered设为false,此时组件才会真正从DOM中卸载; - 用
useRef绑定DOM元素,确保能正确监听动画事件,避免React状态更新导致的元素引用丢失。
额外注意点
- 不要用
setTimeout代替animationend监听,因为不同浏览器的动画执行精度有差异,事件监听是最可靠的方式; - 如果是列表中的动态组件(比如循环渲染的项),只需要给每个项单独维护
isFadingOut和isRendered状态即可,逻辑完全一致; - 你提到的
animation: both其实是控制动画的填充模式,但它解决不了React卸载DOM的时序问题,所以必须通过状态拆分来衔接。
你用动画库(比如Framer Motion、React Transition Group)确实能省不少事,它们内部已经封装了这套动画+DOM卸载的时序逻辑,但原生方案能让你更清楚底层的运作方式~
内容的提问来源于stack exchange,提问作者Ronny Efronny
相关产品推荐
相关产品推荐

