Framer Motion (React):如何排序初始、退出和布局动画?
Framer Motion (React): How to order initial, exit and layout animations?
我正在使用 framer-motion 为网格列中的变化设置动画。
这是我想要做的:
- 我在网格中有九个按钮(#db-wrapper 是网格容器)。
- 用户切换到“最大化”视图(props.minimize 更改)第四列被添加到网格中,已经存在的按钮移动到它们的新位置,为其余按钮留下空单元格.然后,新按钮从底部滑入,填充空单元格。网格中的按钮现在位于连续的单元格中。
- 现在用户切换回最小化视图。首先,所有要删除的按钮都应该滑到底部。
- 然后,其余按钮应移动到它们在网格中的新位置(它们的旧位置,如开头),以便它们填充连续的单元格。
(基本上,我想在用户切换回来后向后执行第 1 步和第 2 步)
我已经完成了第一步和第二步,下面是功能组件:
const DrumButtons = (props) => {
const drumButtonsVariants = {
hidden: {
y: "140vh",
},
visible: {
y: 0,
transition: {
type: "tween",
duration: 1,
delay: 0.1,
},
},
exit: {
y: "140vh",
transition: {
type: "tween",
duration: 1,
delay: 0.1,
},
},
};
let dbWrapperStyle = {};
if (!props.minimized) {
dbWrapperStyle = {
gridTemplateColumns: "1fr 1fr 1fr 1fr",
};
}
let singleWrapperStyle = {
width: "100%",
height: "100%",
};
let buttonTransition = { duration: 0.5, delay: 0.1 };
return (
<div id="db-wrapper" style={dbWrapperStyle}>
<AnimatePresence>
{props.buttonsarr.map((elem, i) => {
return (
<motion.div
variants={drumButtonsVariants}
initial="hidden"
animate="visible"
exit="exit"
key={elem.press}
style={singleWrapperStyle}
>
<motion.button
layout
transition={buttonTransition}
key={elem.press}
className="drum-pad"
onClick={dbHandleClickWrapper(
props.changetext,
props.buttonsarr
)}
id={elem.name}
>
<span className="front">{elem.press.toUpperCase()}</span>
<audio
key={elem.press.toUpperCase()}
id={elem.press.toUpperCase()}
src={props.buttonsarr[i].source}
preload="auto"
className="clip"
></audio>
</motion.button>
</motion.div>
);
})}
</AnimatePresence>
</div>
);
};
到底是什么问题?
这是从“最大化”切换回“最小化”时当前发生的情况:
- 不再需要的按钮滑出到底部。同时,4 个网格列减少到 3 个列。因此,其余按钮将滑动到其在 3 列宽网格中的位置,为当前滑出但仍存在于 DOM.
中的按钮留下空单元格
- 删除其他按钮后,其余按钮再次移动以填充网格的连续单元格。
这是我试过的:
- 我试过将
when: "beforeChildren"
添加到 drumButtonsVariants
的转换中。没有任何变化。
- 我尝试过延迟,但从未达到预期的结果。如果我延迟布局动画,从 DOM 中移除按钮也会延迟,导致相同的不良结果。
你可以在这里查看我的完整代码:
(总支)https://github.com/Julian-Sz/FCC-Drum-Machine/tree/main
(有问题的版本)https://github.com/Julian-Sz/FCC-Drum-Machine/tree/284606cac7cbc4bc6e13bf432c563eab4814d370
随时复制和分叉此存储库!
诀窍是为 dbWrapperStyle
使用 State。
那么,AnimatePresence
和onExitComplete
就可以用了。
当用户再次切换到最小化时会发生这种情况:
- 移除的按钮滑出(但它们仍在DOM - 占据的网格单元格中)
- 现在它们从 DOM 中删除,并且
onExitComplete
触发:网格列更改为 3 列并且组件重新呈现。 DOM 中的移除和网格更改相继发生(没有任何延迟)- 导致动画流畅!
这是新组件:
const DrumButtons = (props) => {
const drumButtonsVariants = {
visible: {
y: 0,
transition: {
type: "tween",
duration: 0.8,
delay: 0.1,
},
},
exit: {
y: "140vh",
transition: {
type: "tween",
duration: 0.8,
delay: 0.1,
},
},
};
// BEFORE------------
// let dbWrapperStyle = {};
// if (!props.minimized) {
// dbWrapperStyle = {
// gridTemplateColumns: "1fr 1fr 1fr 1fr",
// };
// }
// AFTER-------------
const [dbWrapperStyle, setWrapperStyle] = useState({});
useEffect(() => {
if (!props.minimized) {
setWrapperStyle({ gridTemplateColumns: "1fr 1fr 1fr 1fr" });
}
}, [props.minimized]);
let singleWrapperStyle = {
width: "100%",
height: "100%",
};
let buttonTransition = {
type: "spring",
duration: 0.9,
delay: 0.1,
bounce: 0.5,
};
return (
<div id="db-wrapper" style={dbWrapperStyle}>
<AnimatePresence
onExitComplete={() => {
setWrapperStyle({ gridTemplateColumns: "1fr 1fr 1fr" });
}}
>
{props.buttonsarr.map((elem, i) => {
return (
<motion.div
variants={drumButtonsVariants}
initial="exit"
animate="visible"
exit="exit"
key={elem.press}
style={singleWrapperStyle}
>
<motion.button
layout
transition={buttonTransition}
key={elem.press}
className="drum-pad"
onClick={dbHandleClickWrapper(
props.changetext,
props.buttonsarr
)}
id={elem.name}
>
<span className="front">{elem.press.toUpperCase()}</span>
<audio
key={elem.press.toUpperCase()}
id={elem.press.toUpperCase()}
src={props.buttonsarr[i].source}
preload="auto"
className="clip"
></audio>
</motion.button>
</motion.div>
);
})}
</AnimatePresence>
</div>
);
};
我正在使用 framer-motion 为网格列中的变化设置动画。
这是我想要做的:
- 我在网格中有九个按钮(#db-wrapper 是网格容器)。
- 用户切换到“最大化”视图(props.minimize 更改)第四列被添加到网格中,已经存在的按钮移动到它们的新位置,为其余按钮留下空单元格.然后,新按钮从底部滑入,填充空单元格。网格中的按钮现在位于连续的单元格中。
- 现在用户切换回最小化视图。首先,所有要删除的按钮都应该滑到底部。
- 然后,其余按钮应移动到它们在网格中的新位置(它们的旧位置,如开头),以便它们填充连续的单元格。
(基本上,我想在用户切换回来后向后执行第 1 步和第 2 步)
我已经完成了第一步和第二步,下面是功能组件:
const DrumButtons = (props) => {
const drumButtonsVariants = {
hidden: {
y: "140vh",
},
visible: {
y: 0,
transition: {
type: "tween",
duration: 1,
delay: 0.1,
},
},
exit: {
y: "140vh",
transition: {
type: "tween",
duration: 1,
delay: 0.1,
},
},
};
let dbWrapperStyle = {};
if (!props.minimized) {
dbWrapperStyle = {
gridTemplateColumns: "1fr 1fr 1fr 1fr",
};
}
let singleWrapperStyle = {
width: "100%",
height: "100%",
};
let buttonTransition = { duration: 0.5, delay: 0.1 };
return (
<div id="db-wrapper" style={dbWrapperStyle}>
<AnimatePresence>
{props.buttonsarr.map((elem, i) => {
return (
<motion.div
variants={drumButtonsVariants}
initial="hidden"
animate="visible"
exit="exit"
key={elem.press}
style={singleWrapperStyle}
>
<motion.button
layout
transition={buttonTransition}
key={elem.press}
className="drum-pad"
onClick={dbHandleClickWrapper(
props.changetext,
props.buttonsarr
)}
id={elem.name}
>
<span className="front">{elem.press.toUpperCase()}</span>
<audio
key={elem.press.toUpperCase()}
id={elem.press.toUpperCase()}
src={props.buttonsarr[i].source}
preload="auto"
className="clip"
></audio>
</motion.button>
</motion.div>
);
})}
</AnimatePresence>
</div>
);
};
到底是什么问题?
这是从“最大化”切换回“最小化”时当前发生的情况:
- 不再需要的按钮滑出到底部。同时,4 个网格列减少到 3 个列。因此,其余按钮将滑动到其在 3 列宽网格中的位置,为当前滑出但仍存在于 DOM. 中的按钮留下空单元格
- 删除其他按钮后,其余按钮再次移动以填充网格的连续单元格。
这是我试过的:
- 我试过将
when: "beforeChildren"
添加到drumButtonsVariants
的转换中。没有任何变化。 - 我尝试过延迟,但从未达到预期的结果。如果我延迟布局动画,从 DOM 中移除按钮也会延迟,导致相同的不良结果。
你可以在这里查看我的完整代码:
(总支)https://github.com/Julian-Sz/FCC-Drum-Machine/tree/main
(有问题的版本)https://github.com/Julian-Sz/FCC-Drum-Machine/tree/284606cac7cbc4bc6e13bf432c563eab4814d370
随时复制和分叉此存储库!
诀窍是为 dbWrapperStyle
使用 State。
那么,AnimatePresence
和onExitComplete
就可以用了。
当用户再次切换到最小化时会发生这种情况:
- 移除的按钮滑出(但它们仍在DOM - 占据的网格单元格中)
- 现在它们从 DOM 中删除,并且
onExitComplete
触发:网格列更改为 3 列并且组件重新呈现。 DOM 中的移除和网格更改相继发生(没有任何延迟)- 导致动画流畅!
这是新组件:
const DrumButtons = (props) => {
const drumButtonsVariants = {
visible: {
y: 0,
transition: {
type: "tween",
duration: 0.8,
delay: 0.1,
},
},
exit: {
y: "140vh",
transition: {
type: "tween",
duration: 0.8,
delay: 0.1,
},
},
};
// BEFORE------------
// let dbWrapperStyle = {};
// if (!props.minimized) {
// dbWrapperStyle = {
// gridTemplateColumns: "1fr 1fr 1fr 1fr",
// };
// }
// AFTER-------------
const [dbWrapperStyle, setWrapperStyle] = useState({});
useEffect(() => {
if (!props.minimized) {
setWrapperStyle({ gridTemplateColumns: "1fr 1fr 1fr 1fr" });
}
}, [props.minimized]);
let singleWrapperStyle = {
width: "100%",
height: "100%",
};
let buttonTransition = {
type: "spring",
duration: 0.9,
delay: 0.1,
bounce: 0.5,
};
return (
<div id="db-wrapper" style={dbWrapperStyle}>
<AnimatePresence
onExitComplete={() => {
setWrapperStyle({ gridTemplateColumns: "1fr 1fr 1fr" });
}}
>
{props.buttonsarr.map((elem, i) => {
return (
<motion.div
variants={drumButtonsVariants}
initial="exit"
animate="visible"
exit="exit"
key={elem.press}
style={singleWrapperStyle}
>
<motion.button
layout
transition={buttonTransition}
key={elem.press}
className="drum-pad"
onClick={dbHandleClickWrapper(
props.changetext,
props.buttonsarr
)}
id={elem.name}
>
<span className="front">{elem.press.toUpperCase()}</span>
<audio
key={elem.press.toUpperCase()}
id={elem.press.toUpperCase()}
src={props.buttonsarr[i].source}
preload="auto"
className="clip"
></audio>
</motion.button>
</motion.div>
);
})}
</AnimatePresence>
</div>
);
};