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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 17:02:39