React环境下anime.js时间线动态添加动画仅第一个播放如何解决?
问题修复方案
核心问题排查
- 局部变量
didAnimate逻辑失效
你将didAnimate声明为组件函数体内的普通let变量,组件每次重渲染都会将其重置为false,且useEffect闭包捕获的永远是当前渲染批次的变量值,动画执行标记完全不生效,会导致动画重复触发、被打断的问题。
修复方式:将didAnimate改为ref存储:
const didAnimate = useRef(false);
执行完动画后设置didAnimate.current = true,判断条件改为!didAnimate.current。
- anime实例不该用state存储
用useState存anime实例会触发不必要的组件重渲染,重渲染会导致DOM引用波动、动画被打断,应该改用useRef存储动画实例:
const animeRef = useRef(null);
赋值直接写animeRef.current = anime(/* 配置 */),不需要用setAnimeRef。
complete回调写法错误
你之前注释掉的complete回调是直接调用函数,而非传入函数引用:
// *错误写法*:动画初始化时就会执行该函数 complete: resetAnimationParams(_actorIdAnimating, _coordsFinal) // *正确写法*:动画结束后才触发 complete: () => resetAnimationParams(_actorIdAnimating, _coordsFinal)
错误写法会在动画创建时就触发redux状态更新,导致组件重渲染,直接打断后续动画执行。
关键帧无属性变化导致视觉上无动画效果
你给出的测试用例中,第一个关键帧结束时translateX已经是40,第二个关键帧的目标值还是translateX:40,没有属性变化,自然看不到动画效果,修改第二个关键帧的属性值即可验证。动画位置衔接错误
要实现动画从上一次结束位置开始,不要每次调用动画都用anime.set重置为固定的translateX: -23, translateY: -34,应该读取元素当前的transform值作为下一次动画的初始值,或者用变量存储上一次动画的最终坐标,直接复用为起点。
修复后的精简代码示例
import anime from "animejs"; import React, { useEffect, useRef } from "react"; import { useDispatch, useSelector } from "react-redux"; import actions from "../../../../../../DataHandlers/redux/actions"; import animationsByDirection from "./animationsByDirection"; import styles from "./CharacterToken.module.css"; export default function CharacterToken(props) { const target = useRef(); const animeRef = useRef(null); const didAnimate = useRef(false); const animationPath = useSelector((state) => state.UI.animationPath); const actorIdAnimating = useSelector((state) => state.UI.actorIdAnimating); const coordsFinal = [ animationPath[animationPath.length - 1]?.x, animationPath[animationPath.length - 1]?.y, ]; const dispatch = useDispatch(); useEffect(() => { if (actorIdAnimating === props.actorHereId && !didAnimate.current) { animateMovement(animationPath, actorIdAnimating, coordsFinal); didAnimate.current = true; } // 可选:动画路径重置时重置didAnimate标记 return () => { if(!animationPath?.length) didAnimate.current = false; } }, [actorIdAnimating, props.actorHereId, animationPath]); const resetAnimationParams = (_actorIdAnimating, _coordsFinal) => { dispatch(actions.setIsAnimatingtoCoords(undefined, undefined, undefined)); dispatch(actions.moveActorLocationCombat(_actorIdAnimating, _coordsFinal)); }; function animateMovement(_animationPath, _actorIdAnimating, _coordsFinal) { const keyframes = [] _animationPath.forEach((square) => { const deltaX = square.x - square.parent.x; const deltaY = square.y - square.parent.y; let direction; if (deltaX === -1 && deltaY === 0) direction = "north"; else if (deltaX === 1 && deltaY === 0) direction = "south"; else if (deltaX === 0 && deltaY === 1) direction = "east"; else if (deltaX === 0 && deltaY === -1) direction = "west"; else if (deltaX === -1 && deltaY === 1) direction = "northEast"; else if (deltaX === -1 && deltaY === -1) direction = "northWest"; else if (deltaX === 1 && deltaY === 1) direction = "southEast"; else if (deltaX === 1 && deltaY === -1) direction = "southWest"; keyframes.push(animationsByDirection[direction].keyframes[0]) }); // 仅首次初始化时重置初始位置,后续动画不要加这行 if(!animeRef.current) { anime.set(target.current, { translateX: -23, translateY: -34, rotateZ: "315deg" }); } animeRef.current = anime({ targets: target.current, keyframes: keyframes, loop: false, easing: "easeOutExpo", duration: 750, complete: () => resetAnimationParams(_actorIdAnimating, _coordsFinal) }) } return ( <img ref={target} className={styles.CharacterToken} alt={""} src={props.tokenImage} /> ); }
内容的提问来源于stack exchange,提问作者William Martin
相关产品推荐
相关产品推荐

