React-Spring多阶段动画时序控制问题求助
React-Spring 多阶段动画控制方案
React-Spring 完全能搞定你这种多阶段动画场景,之前的问题出在实现细节上——状态管理混乱、没利用好库的核心特性。下面直接给你可行的解决方案:
核心优化思路
- 用单阶段状态替代多布尔值:用数字类型的状态(比如
animationStage)管理动画阶段(初始/阶段1/阶段2/阶段3/阶段4),避免多布尔值的逻辑冲突。 - 利用
to数组实现单阶段内的多步动画:比如碰撞效果的「先移动再回退」,不用拆分多个状态,直接给to传数组就能自动执行序列动画。 - 配置
reset: true确保状态切换时动画重触发:解决之前动画不响应状态变化的问题。 - 用
onRest自动推进阶段:每个阶段动画结束后自动切换到下一阶段,不用手动触发。
完整代码示例
import { useState } from 'react'; import { useSpring, animated } from '@react-spring/web'; // 动画阶段:0=初始状态, 1=阶段1(middle上移+阴影放大), 2=阶段2(top下移入视), 3=阶段3(碰撞回弹), 4=阶段4(top移出视) export default function BoxAnimation() { const [animationStage, setAnimationStage] = useState(0); // MiddleBox 动画配置 const middleBoxSpring = useSpring({ transform: () => { switch (animationStage) { case 0: return 'translateY(0)'; // 初始在视口底部 case 1: return 'translateY(-50px)'; // 阶段1:上移50px case 2: return 'translateY(-50px)'; // 阶段2:保持位置 case 3: return ['translateY(-70px)', 'translateY(-50px)']; // 阶段3:先上移20px再回退 case 4: return 'translateY(-50px)'; // 阶段4:保持位置 default: return 'translateY(0)'; } }, reset: true, // 碰撞阶段用高张力+低摩擦,模拟弹性效果 config: animationStage === 3 ? { tension: 600, friction: 15 } : { duration: 300 }, onRest: () => { // 动画结束后自动推进阶段,直到完成所有流程 if (animationStage < 4) setAnimationStage(prev => prev + 1); }, }); // Shadow 动画配置 const shadowSpring = useSpring({ transform: () => { switch (animationStage) { case 0: return 'scale(0.8)'; // 初始缩小 case 1: return 'scale(1)'; // 阶段1:放大到正常尺寸 case 2: return 'scale(1)'; // 阶段2:保持 case 3: return ['scale(1.1)', 'scale(1)']; // 阶段3:先放大再恢复 case 4: return 'scale(1)'; // 阶段4:保持 default: return 'scale(0.8)'; } }, reset: true, config: animationStage === 3 ? { tension: 600, friction: 15 } : { duration: 300 }, }); // TopBox 动画配置 const topBoxSpring = useSpring({ transform: () => { switch (animationStage) { case 0: return 'translateY(-100%)'; // 初始在视口外上方 case 1: return 'translateY(-100%)'; // 阶段1:保持 case 2: return 'translateY(0)'; // 阶段2:下移进入视口 case 3: return ['translateY(20px)', 'translateY(0)']; // 阶段3:先下移20px再回退 case 4: return 'translateY(-100%)'; // 阶段4:移回视口外 default: return 'translateY(-100%)'; } }, reset: true, config: animationStage === 3 ? { tension: 600, friction: 15 } : { duration: 300 }, }); // 手动触发动画(组件加载时自动触发可改成 useEffect) const startAnimation = () => { setAnimationStage(0); setTimeout(() => setAnimationStage(1), 100); // 重置后启动第一阶段 }; return ( <div style={{ position: 'relative', width: '100vw', height: '100vh', overflow: 'hidden' }}> {/* 阴影元素 */} <animated.div style={{ position: 'absolute', bottom: '50px', left: '50%', transform: 'translateX(-50%)', ...shadowSpring, width: '200px', height: '20px', backgroundColor: 'rgba(0,0,0,0.3)', borderRadius: '50%', }} /> {/* MiddleBox */} <animated.div style={{ position: 'absolute', bottom: '0', left: '50%', transform: 'translateX(-50%)', ...middleBoxSpring, width: '200px', height: '200px', backgroundColor: '#42b983', borderRadius: '8px', }} /> {/* TopBox */} <animated.div style={{ position: 'absolute', left: '50%', transform: 'translateX(-50%)', ...topBoxSpring, width: '200px', height: '200px', backgroundColor: '#35495e', borderRadius: '8px', }} /> <button onClick={startAnimation} style={{ position: 'absolute', top: '20px', left: '20px' }}> 触发动画 </button> </div> ); }
进阶:用useChain自定义时序
如果需要更灵活的时序控制(比如阶段2提前和阶段1重叠),可以用useChain串联多个动画的ref:
import { useChain, useSpringRef } from '@react-spring/web'; // 给每个动画创建ref const middleRef = useSpringRef(); const shadowRef = useSpringRef(); const topRef = useSpringRef(); // 在对应的useSpring中添加ref属性 const middleBoxSpring = useSpring({ ref: middleRef, // ...其他配置 }); // 串联动画:第三个动画(topBox)在第一个动画执行到80%时启动 useChain([middleRef, shadowRef, topRef], [0, 0, 0.8]);
关于动画库的选择
你完全没选错库,React-Spring的声明式动画模型非常适合这种多阶段复杂场景。之前的问题只是没利用好to数组、reset配置和状态管理的最佳实践。
内容的提问来源于stack exchange,提问作者sepher
相关产品推荐
相关产品推荐

