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.

之前应该呈现的项目