为什么动漫 js 只播放第一个动态 "add"ed 动画到时间轴?

Why is anime js only playing the first of dynamically "add"ed animations to a timeline?

所以 我正在向下面的动画 js 时间轴动态添加动画:

const target = useRef();
const animeRef = useRef(null)       


      animeRef.current = anime
      .timeline({
        autoplay: false,
        easing: "easeOutExpo",
        duration: 250 * animationNodes.length,
        complete: () => resetAnimationParams(_actorIdAnimating, _coordsFinal),
      })
      .set(idString, { translateX: -23, translateY: -34, rotateZ: "315deg" });

      animationNodes.forEach((node) => {
        animeRef.current.add(node)
      })

      animeRef.current.play()

它适用于第一个 animationNodes 对象,但仅适用于第一个。

例如使用以下输入,仅播放第一个关键帧动画。

    animationNodes = [
    {
        "keyframes": [
            {
                "translateY": -100,
                "translateX": 40
            }
        ],
        "targets": "#id-0"
    },
    {
        "keyframes": [
            {
                "translateX": 40
            }
        ],
        "targets": "#id-0"
    }
]

这是在一个反应​​组件中,动画引用来自

const animationRef = useRef();

编辑: 我已经通过删除它来确认完整功能中没有有效代码。结果是一样的

编辑2: 动画内容:

  <img
    ref={target}
    className={styles.CharacterToken}
    alt={""}
    src={props.tokenImage}
  />

编辑3: 所以我认为这可能是 animejs 时间轴的问题 - 所以我重构以简化,连接所有关键帧。有趣的是,只有第一个关键帧播放,就像我们之前有时间轴时一样。我的 DOM 引用会在第一个关键帧动画后发生变化吗?

完整代码如下。

import anime from "animejs";
import React, { useEffect, useRef, useState } 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, setAnimeRef] = useState()
  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,
  ];
  let didAnimate = false;
  let dispatch = useDispatch();

  useEffect(() => {
    if (actorIdAnimating === props.actorHereId && !didAnimate) {
      animateMovement(animationPath, actorIdAnimating, coordsFinal);
    }
  }, [actorIdAnimating, props.actorHereId, animationPath]);

  const resetAnimationParams = (_actorIdAnimating, _coordsFinal) => {
    dispatch(actions.setIsAnimatingtoCoords(undefined, undefined, undefined));
    dispatch(actions.moveActorLocationCombat(_actorIdAnimating, _coordsFinal));
  };

  function animateMovement(
    _animationPath,
    _actorIdAnimating,
    _coordsFinal
  ) {
    let keyframes = []
    _animationPath.forEach((square) => {
      const direction = () => {
        const deltaX = square.x - square.parent.x; // 1, 0, -1
        const deltaY = square.y - square.parent.y; // 1, 0, -1
        if (deltaX === -1 && deltaY === 0) return "north";
        else if (deltaX === 1 && deltaY === 0) return "south";
        else if (deltaX === 0 && deltaY === 1) return "east";
        else if (deltaX === 0 && deltaY === -1) return "west";
        else if (deltaX === -1 && deltaY === 1) return "northEast";
        else if (deltaX === -1 && deltaY === -1) return "northWest";
        else if (deltaX === 1 && deltaY === 1) return "southEast";
        else if (deltaX === 1 && deltaY === -1) return "southWest";
      };
      keyframes.push(animationsByDirection[direction()].keyframes[0])
    });
    setAnimeRef(anime
    .set(target.current, { translateX: -23, translateY: -34, rotateZ: "315deg" }))

    setAnimeRef(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}
      />
  );
}

编辑4: 所以我再次重构,制作了最简单的版本。我删除了动漫 js - 我现在只使用网络动画 API。

请看这个codepen - https://codepen.io/wjkmartin/pen/YzxzJWJ

我试图让动画无限期地相互跟随,从最后一个结束的地方开始。

所以我最终切换到网络动画API,但解决方案是一样的。此外,时间表不是最好的方法。如果使用 anime js,我认为调用 anime({}) 的第二个代码片段是更好的方法。

重要的学习点是,变换并非天生相加——它们相互替换。

关键帧不会加在一起,它们会相互替换。

在我下面的解决方案中,我为每个动画手动添加每个变换。不知道为什么我认为积累会自动发生。

此外 - 在链中的每个动画之后,我将 DOM 元素的变换设置为动画的结束状态。

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 animationPath = useSelector((state) => state.UI.animationPath);
  const actorIdAnimating = 0;
  const coordsFinal = [
    animationPath[animationPath.length - 1]?.x,
    animationPath[animationPath.length - 1]?.y,
  ];

  let dispatch = useDispatch();

  useEffect(() => {
    const animRef = target.current;
    if (animationPath.length > 0 && props.actorHereId === 0) {
      let keyframeEffects = [];
      let xTransformAccumulated = 0;
      let yTransformAccumulated = 0;
      animationPath.forEach((node, index) => {
        function direction(_node) {
          const deltaX = _node.x - _node.parent.x; // 1, 0, -1
          const deltaY = _node.y - _node.parent.y; // 1, 0, -1
          if (deltaX === -1 && deltaY === 0) return "north";
          else if (deltaX === 1 && deltaY === 0) return "south";
          else if (deltaX === 0 && deltaY === 1) return "east";
          else if (deltaX === 0 && deltaY === -1) return "west";
          else if (deltaX === -1 && deltaY === 1) return "northEast";
          else if (deltaX === -1 && deltaY === -1) return "northWest";
          else if (deltaX === 1 && deltaY === 1) return "southEast";
          else if (deltaX === 1 && deltaY === -1) return "southWest";
        }
        const delay = 250 * index;
        const x = animationsByDirection[direction(node)].x;
        const y = animationsByDirection[direction(node)].y;

        xTransformAccumulated += x;
        yTransformAccumulated += y;

        keyframeEffects.push(
          new KeyframeEffect(
            animRef,
            [
              {
                transform: `translate(${xTransformAccumulated}px,${yTransformAccumulated}px) rotateZ(315deg)`,
              },
            ],
            { delay: delay, duration: 250, iterations: 1 }
          )
        );
      });

      let animations = [];
      let promises = [];
      keyframeEffects.forEach((effect, index) => {
        let animation = new Animation(effect);
        animation.finished.then(() => {
            animRef.style.transform =
              animation.effect.getKeyframes()[0].transform;
         
        });
        promises.push(animation.finished);
        animations.push(animation);
      });
      animations.forEach((animation) => {
        
        animation.play();
      });
      Promise.all([...promises]).then((values) => {
        resetAnimationParams(actorIdAnimating, coordsFinal);
        
      });
    }
  }, []);

  const resetAnimationParams = (_actorIdAnimating, _coordsFinal) => {
    dispatch(actions.setIsAnimatingtoCoords(undefined, undefined, undefined));
    dispatch(actions.setAnimationPath([]));
    dispatch(actions.moveActorLocationCombat(_actorIdAnimating, _coordsFinal));
  };

  return (
    <img
      ref={target}
      className={styles.CharacterToken}
      alt={""}
      src={props.tokenImage}
    />
  );
}