AnimatePresence 下的运动元素在从 DOM 中移除之前不播放退出动画
motion elements under AnimatePresence not playing exit animation before being removed from the DOM
我在使用 framer-motion-animate-presence component
时遇到问题
我正在尝试创建一个菜单导航系统,其中的子菜单在进入或退出时会来回滑动,但您当前所在的菜单不会执行退出动画,即使它与输入动画,我已经为我的所有菜单页面设置了唯一的键和 ID,所以我不明白为什么 Animate Presence 会立即删除您导航的菜单,而不是等待其动画完成然后删除它。
link to a video displaying the issue
使用下拉组件的相关部分:
<Header sections={["Intro", "What I Do", "About Me"]}
sectionLinks={["#intro", "#what-i-do", "#about-me"]}
logo={ <h4>{"<dev> Finn"}</h4> }
>
<Button icon={ <BsTwitter/> } type="header" link="https://www.youtube.com/watch?v=IF6k0uZuypA&t=842s"/>
<Button icon={ <FaDiscord/> } type="header"/>
<Button icon={ <FiMenu/> } type="header">
<Dropdown key="dropdown"
pages={[
{
name: "main",
items: [
{
text: "Menu",
textType: "title",
},
{
text: "Settings",
buttonType: "navigation",
icon: <RiSettings4Fill/>,
goToPage: "settings",
goToDirection: "right"
}
]
},
{
name: "settings",
items: [
{
text: "Settings",
textType: "bold",
buttonType: "navigation",
icon: <FaArrowLeft/>,
goToPage: "main",
goToDirection: "left"
},
{
text: "Appearance",
toggleType: "theme",
icon: ["", "☀️"],
},
{
text: "Cool Mode",
toggleType: "toggle",
icon: ["", ""],
}
]
}
]}/>
</Button>
</Header>
下拉组件: - (最后的 return 语句是问题所在)
import React, { useEffect, useState, useRef } from "react";
import { motion, AnimatePresence } from "framer-motion";
import Button from "./Button";
import Toggle from "./Toggle";
function Dropdown(props) {
const [[activePage, pageDirection], setActivePage] = useState(["main", null]);
const [menuHeight, setMenuHeight] = useState(null);
const dropDownPage = useRef()
function calcHeight() {
const height = dropDownPage.current.offsetHeight
setMenuHeight(height)
}
function DropdownItem(subprops) {
return (
<>
<a className="dropdown__item" onClick={() => {
subprops.goToPage && setActivePage([subprops.goToPage, subprops.goToDirection])
console.log(activePage, pageDirection)
}}>
{subprops.textType === "title" ?
<>
<span className="dropdown__title">{subprops.text}</span>
</> :
<>
{subprops.buttonType === "navigation" ?
<>
<div className="dropdown__button-container">
<Button icon={subprops.icon} type="dropdown"/>
</div>
</> :
<>
{subprops.buttonType === "link" ?
<>
<div className="dropdown__button-container">
<Button icon={subprops.icon} type="dropdown"/>
</div>
</> :
<>
{subprops.toggleType === "theme" ?
<>
<div className="dropdown__toggle-container">
<Toggle icons={subprops.icon} type="theme"/>
</div>
</> :
<>
<div className="dropdown__toggle-container">
<Toggle icons={subprops.icon} type="toggle"/>
</div>
</>
}
</>
}
</>
}
{subprops.textType === "bold" ?
<span className="dropdown__item__text-bold">{subprops.text}</span> :
<span className="dropdown__item__text">{subprops.text}</span>
}
</>
}
</a>
</>
)
}
const dropdownVariants = {
exit: {
x: "150%",
transition: {
type: "spring",
bounce: 0,
damping: 15,
mass: 1,
stiffness: 50,
}
},
enter: {
x: 0,
transition: {
type: "spring",
bounce: 0.3,
damping: 15,
mass: 1,
stiffness: 100
}
}
}
const pageVariants = {
enter: (pageDirection) => {
return {
x: pageDirection === "left" ? "-100%" : pageDirection === "right" ? "100%" : 0,
transition: {
type: "spring",
bounce: 0.3,
damping: 15,
mass: 1,
stiffness: 100
}
}
},
center: {
x: 0,
transition: {
type: "spring",
bounce: 0.3,
damping: 15,
mass: 1,
stiffness: 100
}
},
exit: (pageDirection) => {
return {
x: pageDirection === "left" ? "-100%" : pageDirection === "right" ? "100%" : 0,
transition: {
type: "spring",
bounce: 0.3,
damping: 15,
mass: 1,
stiffness: 100
}
}
}
}
return (
<motion.div className="header__dropdown" style={{ height: menuHeight}} key="header__dropdown"
variants={dropdownVariants}
initial="exit"
animate="enter"
exit="exit"
>
<AnimatePresence custom={pageDirection}>
{props.pages.map((page) => {
return (
<React.Fragment key={page["name"]}>
{activePage === page["name"] &&
<motion.div ref={dropDownPage} id={page["name"]} key={page["name"]}
custom={pageDirection}
variants={pageVariants}
initial="enter"
animate="center"
exit="exit"
>
{page["items"].map((item, index) => {
return (
<DropdownItem key={index}
text={item["text"]}
textType={item["textType"]}
link={item["link"]}
buttonType={item["buttonType"]}
toggleType={item["toggleType"]}
icon={item["icon"]}
goToPage={item["goToPage"]}
goToDirection={item["goToDirection"]}
/>
)
})}
</motion.div>
}
</React.Fragment>
)
})}
</AnimatePresence>
</motion.div>
)
}
export default Dropdown
AnimatePresence
将在其直接子级从 DOM 中移除时对其进行动画处理。在您的下拉组件中,<AnimatePresence>
的直接子元素是 <React.Fragment>
元素,它们看起来从未真正被删除过。您出于某种原因需要这些元素吗?如果你删除它们,并将有条件渲染的 motion.div
元素作为子元素(带键),它应该可以工作。
<AnimatePresence custom={pageDirection}>
{props.pages.filter((p) => activePage === p["name"]).map((page) => {
return (
<motion.div ref={dropDownPage} id={page["name"]} key={page["name"]}
custom={pageDirection}
variants={pageVariants}
initial="enter"
animate="center"
exit="exit"
>
{page["items"].map((item, index) => {
return (
<DropdownItem key={index}
text={item["text"]}
textType={item["textType"]}
link={item["link"]}
buttonType={item["buttonType"]}
toggleType={item["toggleType"]}
icon={item["icon"]}
goToPage={item["goToPage"]}
goToDirection={item["goToDirection"]}
/>
)
})}
</motion.div>
)
})}
</AnimatePresence>
我猜您正在使用片段,因为您正在映射整个页面数组。我添加了一个 filter
以将数组减少到仅在调用 map
.
之前应该呈现的项目
我在使用 framer-motion-animate-presence component
时遇到问题我正在尝试创建一个菜单导航系统,其中的子菜单在进入或退出时会来回滑动,但您当前所在的菜单不会执行退出动画,即使它与输入动画,我已经为我的所有菜单页面设置了唯一的键和 ID,所以我不明白为什么 Animate Presence 会立即删除您导航的菜单,而不是等待其动画完成然后删除它。
link to a video displaying the issue
使用下拉组件的相关部分:
<Header sections={["Intro", "What I Do", "About Me"]}
sectionLinks={["#intro", "#what-i-do", "#about-me"]}
logo={ <h4>{"<dev> Finn"}</h4> }
>
<Button icon={ <BsTwitter/> } type="header" link="https://www.youtube.com/watch?v=IF6k0uZuypA&t=842s"/>
<Button icon={ <FaDiscord/> } type="header"/>
<Button icon={ <FiMenu/> } type="header">
<Dropdown key="dropdown"
pages={[
{
name: "main",
items: [
{
text: "Menu",
textType: "title",
},
{
text: "Settings",
buttonType: "navigation",
icon: <RiSettings4Fill/>,
goToPage: "settings",
goToDirection: "right"
}
]
},
{
name: "settings",
items: [
{
text: "Settings",
textType: "bold",
buttonType: "navigation",
icon: <FaArrowLeft/>,
goToPage: "main",
goToDirection: "left"
},
{
text: "Appearance",
toggleType: "theme",
icon: ["", "☀️"],
},
{
text: "Cool Mode",
toggleType: "toggle",
icon: ["", ""],
}
]
}
]}/>
</Button>
</Header>
下拉组件: - (最后的 return 语句是问题所在)
import React, { useEffect, useState, useRef } from "react";
import { motion, AnimatePresence } from "framer-motion";
import Button from "./Button";
import Toggle from "./Toggle";
function Dropdown(props) {
const [[activePage, pageDirection], setActivePage] = useState(["main", null]);
const [menuHeight, setMenuHeight] = useState(null);
const dropDownPage = useRef()
function calcHeight() {
const height = dropDownPage.current.offsetHeight
setMenuHeight(height)
}
function DropdownItem(subprops) {
return (
<>
<a className="dropdown__item" onClick={() => {
subprops.goToPage && setActivePage([subprops.goToPage, subprops.goToDirection])
console.log(activePage, pageDirection)
}}>
{subprops.textType === "title" ?
<>
<span className="dropdown__title">{subprops.text}</span>
</> :
<>
{subprops.buttonType === "navigation" ?
<>
<div className="dropdown__button-container">
<Button icon={subprops.icon} type="dropdown"/>
</div>
</> :
<>
{subprops.buttonType === "link" ?
<>
<div className="dropdown__button-container">
<Button icon={subprops.icon} type="dropdown"/>
</div>
</> :
<>
{subprops.toggleType === "theme" ?
<>
<div className="dropdown__toggle-container">
<Toggle icons={subprops.icon} type="theme"/>
</div>
</> :
<>
<div className="dropdown__toggle-container">
<Toggle icons={subprops.icon} type="toggle"/>
</div>
</>
}
</>
}
</>
}
{subprops.textType === "bold" ?
<span className="dropdown__item__text-bold">{subprops.text}</span> :
<span className="dropdown__item__text">{subprops.text}</span>
}
</>
}
</a>
</>
)
}
const dropdownVariants = {
exit: {
x: "150%",
transition: {
type: "spring",
bounce: 0,
damping: 15,
mass: 1,
stiffness: 50,
}
},
enter: {
x: 0,
transition: {
type: "spring",
bounce: 0.3,
damping: 15,
mass: 1,
stiffness: 100
}
}
}
const pageVariants = {
enter: (pageDirection) => {
return {
x: pageDirection === "left" ? "-100%" : pageDirection === "right" ? "100%" : 0,
transition: {
type: "spring",
bounce: 0.3,
damping: 15,
mass: 1,
stiffness: 100
}
}
},
center: {
x: 0,
transition: {
type: "spring",
bounce: 0.3,
damping: 15,
mass: 1,
stiffness: 100
}
},
exit: (pageDirection) => {
return {
x: pageDirection === "left" ? "-100%" : pageDirection === "right" ? "100%" : 0,
transition: {
type: "spring",
bounce: 0.3,
damping: 15,
mass: 1,
stiffness: 100
}
}
}
}
return (
<motion.div className="header__dropdown" style={{ height: menuHeight}} key="header__dropdown"
variants={dropdownVariants}
initial="exit"
animate="enter"
exit="exit"
>
<AnimatePresence custom={pageDirection}>
{props.pages.map((page) => {
return (
<React.Fragment key={page["name"]}>
{activePage === page["name"] &&
<motion.div ref={dropDownPage} id={page["name"]} key={page["name"]}
custom={pageDirection}
variants={pageVariants}
initial="enter"
animate="center"
exit="exit"
>
{page["items"].map((item, index) => {
return (
<DropdownItem key={index}
text={item["text"]}
textType={item["textType"]}
link={item["link"]}
buttonType={item["buttonType"]}
toggleType={item["toggleType"]}
icon={item["icon"]}
goToPage={item["goToPage"]}
goToDirection={item["goToDirection"]}
/>
)
})}
</motion.div>
}
</React.Fragment>
)
})}
</AnimatePresence>
</motion.div>
)
}
export default Dropdown
AnimatePresence
将在其直接子级从 DOM 中移除时对其进行动画处理。在您的下拉组件中,<AnimatePresence>
的直接子元素是 <React.Fragment>
元素,它们看起来从未真正被删除过。您出于某种原因需要这些元素吗?如果你删除它们,并将有条件渲染的 motion.div
元素作为子元素(带键),它应该可以工作。
<AnimatePresence custom={pageDirection}>
{props.pages.filter((p) => activePage === p["name"]).map((page) => {
return (
<motion.div ref={dropDownPage} id={page["name"]} key={page["name"]}
custom={pageDirection}
variants={pageVariants}
initial="enter"
animate="center"
exit="exit"
>
{page["items"].map((item, index) => {
return (
<DropdownItem key={index}
text={item["text"]}
textType={item["textType"]}
link={item["link"]}
buttonType={item["buttonType"]}
toggleType={item["toggleType"]}
icon={item["icon"]}
goToPage={item["goToPage"]}
goToDirection={item["goToDirection"]}
/>
)
})}
</motion.div>
)
})}
</AnimatePresence>
我猜您正在使用片段,因为您正在映射整个页面数组。我添加了一个 filter
以将数组减少到仅在调用 map
.