You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Framer Motion AnimatePresence退出动画重复渲染问题

NextJs + Framer Motion 卡片动画重复渲染问题解决

问题场景

使用NextJs、Framer Motion和Tailwind开发时,页面中的卡片点击后会动画到屏幕中间,再次点击返回原列表。但再次点击同一卡片时,退出动画会出现Item组件重复渲染的问题。移除AnimatePresence组件后问题消失,但需要保留退出动画,因此需要解决该问题。

相关代码

List 组件

export function List({
    selectedId,
    setSelectedId,
}: {
    selectedId: string | null;
    setSelectedId: any;
}) {
    return (
        <React.Fragment>
            <div className="w-full max-w-5xl flex-[1_1_100%] py-11 px-6">
                <ul className="flex flex-wrap content-start">
                    {items.map((card) => (
                        <Card
                            key={card.id}
                            {...card}
                            selectedId={selectedId}
                            setSelectedId={setSelectedId}
                        />
                    ))}
                </ul>
            </div>
            <AnimatePresence>
                {selectedId && (
                    <Item
                        id={selectedId}
                        key="item"
                        setSelectedId={setSelectedId}
                    />
                )}
            </AnimatePresence>
        </React.Fragment>
    );
}

Item 组件

export function Item({
    id,
    setSelectedId,
}: {
    id: string;
    setSelectedId: any;
}) {
    const item = items.find((item) => item.id === id) as Item;

    const { title, category, image } = item;

    return (
        <React.Fragment>
            <motion.div
                initial={{ opacity: 0 }}
                animate={{ opacity: 1 }}
                exit={{ opacity: 0, transition: { duration: 0.15 } }}
                transition={{ duration: 0.2, delay: 0.15 }}
                style={{ pointerEvents: "auto" }}
                className="overlay z-[1]"
            >
                <div onClick={() => setSelectedId(null)}></div>
            </motion.div>
            <motion.div
                className="card-content-container open"
                transition={{ duration: 0.5 }}
            >
                <motion.div
                    className="card-content z-[1001]"
                    layout
                    layoutId={`card-container-${id}`}
                >
                    <motion.div
                        className="card-image-container"
                        layoutId={`card-image-container-${id}`}
                    >
                        <Image
                            className="card-image"
                            src={image.src}
                            alt=""
                            fill
                            priority
                            onClick={() => setSelectedId(null)}
                        />
                    </motion.div>
                    <motion.div
                        className="title-container"
                        layoutId={`title-container-${id}`}
                    >
                        <span className="category">{category}</span>
                        <h2>{title}</h2>
                    </motion.div>
                    <motion.div
                        className="content-container"
                        layoutId={`content-container-${id}`}
                    >
                        <LoremIpsum
                            p={1}
                            avgWordsPerSentence={5}
                            avgSentencesPerParagraph={2}
                        />
                    </motion.div>
                </motion.div>
            </motion.div>
        </React.Fragment>
    );
}

问题原因与解决办法

问题根源

问题出在Item组件的key值固定为"item"。Framer Motion的AnimatePresence组件依赖key来追踪组件的挂载和卸载状态:

  • 第一次点击卡片,selectedId从null变为对应ID,Item组件挂载;
  • 再次点击同一卡片,selectedId变为null,Item组件卸载;
  • 第三次点击同一卡片,selectedId变回该ID,此时因为key还是"item",React会认为是同一个组件进行更新而非重新挂载,导致AnimatePresence的退出动画逻辑混乱,进而出现重复渲染。

解决步骤

修改List组件中Item的key,将固定的"item"替换为selectedId:

<AnimatePresence>
    {selectedId && (
        <Item
            id={selectedId}
            key={selectedId} // 绑定key为选中的ID
            setSelectedId={setSelectedId}
        />
    )}
</AnimatePresence>

这样每次切换不同卡片,或同一卡片反复点击时,AnimatePresence都能正确识别组件的挂载/卸载状态,退出动画就能正常执行,不会出现重复渲染的问题。

对比你参考的正常运行的沙箱示例,它的弹窗组件key也是与选中ID绑定的,这就是它能正常工作的核心原因。


内容的提问来源于stack exchange,提问作者cheezit97

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 22:14:55