React登录页useEffect切换Lottie动画重复渲染问题求助
问题描述
在React开发简易登录页时,设置了用户名和密码两个输入框,通过useEffect Hook在指定div中渲染Lottie动画。预期点击输入框时切换动画,点击页面其他区域恢复默认动画,但运行代码后每次点击输入框都会渲染新动画,旧动画仍保留,无法实现仅显示当前对应动画的效果。
以下是原代码:
import './App.css'; import { useState, useEffect } from 'react'; import lottie from 'lottie-web'; import sideNod from './sidenod.json'; import nod from './nod.json' function App() { const[userId,setUserId] = useState(''); const[password,setPassword] = useState(''); const[touched,setTouched] = useState(false); console.log(touched) useEffect(() => { lottie.loadAnimation({ container: document.getElementById('avatar'), animationData: touched ? nod : sideNod, renderer: 'svg', loop: true, autoplay: true, })} ,[touched]); function usernameChangeHandler(e){ setUserId(e.target.value); } function passwordChangeHandler(e){ touched ? setTouched(true) : setTouched(false); setPassword(e.target.value); } function formSubmitHandler(e){ e.preventDefault(); } return ( <div id='wrapper'> <div id='avatar' style={{ width: 300, height: 300 }}></div> <form onSubmit={(e) => formSubmitHandler()}> <label id='login-bar'> <input id='user-name' placeholder='Username' value = {userId} onChange = {(e) => usernameChangeHandler(e)} onFocus={() => setTouched(true)} onBlur={() => setTouched(false)} /> <input id='password' placeholder='Password' type='password' value = {password} onChange = {(e) => passwordChangeHandler(e)} onFocus={() => setTouched(true)} onBlur={() => setTouched(false)} /> </label> <button id='submit-btn' type='Submit'>Login</button> </form> </div> ); } export default App;
解决方案
问题根源是每次调用lottie.loadAnimation都会在容器中新增一个动画实例,旧实例未被销毁导致叠加显示。同时原代码存在逻辑冗余和输入框切换时的动画闪烁问题,修改方案如下:
- 保存动画实例并清理旧实例:用
useRef存储lottie返回的动画实例,在组件卸载或依赖变更时,调用destroy()方法销毁旧动画,避免实例叠加。 - 移除无效逻辑:删除
passwordChangeHandler中无意义的touched判断代码。 - 优化输入框焦点判断:通过监听文档点击事件,判断是否有输入框处于聚焦状态,只有当所有输入框都失焦时才恢复默认动画,避免切换输入框时的动画闪烁。
修改后的完整代码:
import './App.css'; import { useState, useEffect, useRef } from 'react'; import lottie from 'lottie-web'; import sideNod from './sidenod.json'; import nod from './nod.json' function App() { const[userId, setUserId] = useState(''); const[password, setPassword] = useState(''); const[touched, setTouched] = useState(false); // 保存动画实例的ref const animationRef = useRef(null); // 保存输入框的ref,用于判断焦点 const usernameInputRef = useRef(null); const passwordInputRef = useRef(null); useEffect(() => { // 销毁旧动画实例 if (animationRef.current) { animationRef.current.destroy(); } // 加载新动画 animationRef.current = lottie.loadAnimation({ container: document.getElementById('avatar'), animationData: touched ? nod : sideNod, renderer: 'svg', loop: true, autoplay: true, }); // 组件卸载时清理动画 return () => { if (animationRef.current) { animationRef.current.destroy(); } }; }, [touched]); // 监听文档点击,判断是否需要恢复默认动画 useEffect(() => { const handleClickOutside = (e) => { const isUsernameFocused = usernameInputRef.current === document.activeElement; const isPasswordFocused = passwordInputRef.current === document.activeElement; if (!isUsernameFocused && !isPasswordFocused) { setTouched(false); } }; document.addEventListener('click', handleClickOutside); return () => { document.removeEventListener('click', handleClickOutside); }; }, []); function usernameChangeHandler(e){ setUserId(e.target.value); } function passwordChangeHandler(e){ setPassword(e.target.value); } function formSubmitHandler(e){ e.preventDefault(); } return ( <div id='wrapper'> <div id='avatar' style={{ width: 300, height: 300 }}></div> <form onSubmit={formSubmitHandler}> <label id='login-bar'> <input ref={usernameInputRef} id='user-name' placeholder='Username' value={userId} onChange={usernameChangeHandler} onFocus={() => setTouched(true)} /> <input ref={passwordInputRef} id='password' placeholder='Password' type='password' value={password} onChange={passwordChangeHandler} onFocus={() => setTouched(true)} /> </label> <button id='submit-btn' type='Submit'>Login</button> </form> </div> ); } export default App;
内容的提问来源于stack exchange,提问作者Dhruv Uniyal
相关产品推荐
相关产品推荐

