为什么动漫 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}
/>
);
}
所以 我正在向下面的动画 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}
/>
);
}