如何解决React动画结束后useState更新延迟引发的旧值闪现问题?
问题描述
我是React新手,正在制作一个简单动画,但动画结束后会有一瞬间显示opacity和translate的旧值,请问有没有办法消除这种现象?我尝试过使用useRef并重新渲染组件,但没有效果,请忽略timeout函数。
代码示例
JS代码
const { useEffect, useState } = React; const Titlu = () => { let [opacitate, setOpacitate] = useState(0); let [translate, setTranslate] = useState(-30); function Schimba() { setOpacitate(prevOpacity => prevOpacity = 1); setTranslate(prevTranslate => prevTranslate = 20); } function timeout(timp) { return new Promise((resolve, reject) => { setTimeout(resolve, 1000); }) } return ( <div className="Part1"> <h1 className="ForkyTitlu" onAnimationEnd={Schimba} style={{ opacity:opacitate, transform: `translate(${translate}%, 0%)` }} > <span className ="Forky">Forky</span> <span className ="Nutrition">Nutrition</span> </h1> </div> ); } ReactDOM .createRoot(root) .render(<Titlu />);
CSS代码
.Part1 { display: inline-flex; margin-bottom: 50vh; border-bottom: 3px black; } .ForkyTitlu { color: orange; font-size: 3vw; /* margin-left: 12vw; margin-top: 40vh; transform: translateX(-50%); */ animation-name: titluAnimation; animation-delay: 1s; animation-duration: 2s; animation-iteration-count: 1; position: relative; } @keyframes titluAnimation { 100% { opacity: 1; transform: translateX(20%); } } .Forky { color: black; font-size: 9vw; text-shadow: -30px 15px 30px #0000009c; } .Nutrition { color: orange; font-size: 3.5vw; text-shadow: -30px 15px 30px orange; }
HTML代码
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script> <div id="root"></div>
解决方案
问题根源是CSS动画结束后会自动还原到元素的初始样式,而onAnimationEnd触发的React状态更新是异步的,两者的时间差导致了闪回现象。下面是两种解决方法:
方案1:使用CSS animation-fill-mode: forwards(推荐)
这个属性会让动画结束后,元素保持动画最后一帧的样式,彻底避免闪回,同时可以简化React代码。
修改后的CSS
.ForkyTitlu { color: orange; font-size: 3vw; animation-name: titluAnimation; animation-delay: 1s; animation-duration: 2s; animation-iteration-count: 1; position: relative; /* 添加这行,让动画结束后保留最后一帧 */ animation-fill-mode: forwards; /* 补充初始样式,和动画起始状态一致 */ opacity: 0; transform: translateX(-30%); } @keyframes titluAnimation { 100% { opacity: 1; transform: translateX(20%); } }
简化后的React代码
const { useEffect, useState } = React; const Titlu = () => { // 移除不需要的状态和回调函数 function timeout(timp) { return new Promise((resolve, reject) => { setTimeout(resolve, 1000); }) } return ( <div className="Part1"> <h1 className="ForkyTitlu"> <span className ="Forky">Forky</span> <span className ="Nutrition">Nutrition</span> </h1> </div> ); } ReactDOM .createRoot(root) .render(<Titlu />);
方案2:同步状态与动画逻辑(适合需要React控制样式的场景)
如果必须用React state控制样式,需要确保动画初始帧和React初始状态完全一致,同时可以通过添加类名代替行内样式更新,减少异步延迟:
- 在CSS中定义动画前后的类名
- 用React state控制类名切换,代替行内样式
不过方案1更简洁高效,优先选择前者。
内容的提问来源于stack exchange,提问作者ImaPug
相关产品推荐
相关产品推荐

