Framer Motion退出动画未触发问题排查求助
为何Framer Motion的退出动画无法触发?
问题场景
需求是实现5个容器从顶部向下、5个容器从底部向上移动至中间后消失的动画效果,但当前代码的退出动画无法正常触发。
现有组件代码
'use client'; import React from 'react'; import { motion, AnimatePresence } from 'framer-motion'; export default function TransLayout({ children, backgroundColor }) { const [isVisible, setIsVisible] = React.useState(false); React.useEffect(() => { setIsVisible(true); return () => setIsVisible(false); }, [backgroundColor]); const anim = (variants, custom = null) => { return { initial: 'initial', animate: 'enter', exit: 'exit', // Ensuring exit animations are added custom, variants, }; }; const columnsPiece = 5; return ( <div className='page stairs' style={{ backgroundColor }}> <motion.div {...anim(opacity)} className='transition-background' /> <AnimatePresence mode="wait"> {isVisible && ( <div key={backgroundColor} className="content-wrapper"> <div className='transition-container'> {/* Top Containers */} {[...Array(columnsPiece)].map((_, i) => ( <motion.div key={`top-${i}`} {...anim(expandTop, columnsPiece - i)} className='topContainer' exit={{ display: 'none', // Prevents visible elements on exit ...expandTop.exit(columnsPiece - i), }} /> ))} {/* Bottom Containers */} {[...Array(columnsPiece)].map((_, i) => ( <motion.div key={`bottom-${i}`} {...anim(expandBottom, columnsPiece - i)} className='bottomContainer' exit={{ display: 'none', // Prevents visible elements on exit ...expandBottom.exit(columnsPiece - i), }} /> ))} </div> <motion.div key="content" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} // Fade out content when exiting transition={{ delay: 0.5 }} > {children} </motion.div> </div> )} </AnimatePresence> </div> ); }
动画配置代码
export const expandTop = { initial: { top: '-100vh', // Top starts off-screen }, enter: (i) => ({ top: 0, transition: { duration: 0.4, delay: 0.05 * i, ease: [0.215, 0.61, 0.355, 1], }, }), exit: (i) => ({ top: '-100vh', // Moves top back off-screen when exiting transition: { duration: 0.4, ease: [0.215, 0.61, 0.355, 1], }, }), }; export const expandBottom = { initial: { top: '100vh', // Bottom starts off-screen }, enter: (i) => ({ top: 0, transition: { duration: 0.4, delay: 0.05 * i, ease: [0.215, 0.61, 0.355, 1], }, }), exit: (i) => ({ top: '100vh', // Moves bottom back off-screen when exiting transition: { duration: 0.4, ease: [0.215, 0.61, 0.355, 1], }, }), }; export const opacity = { initial: { opacity: 0.5, }, enter: { opacity: 0, }, exit: { opacity: 0.5, }, };
问题原因分析
transition-background元素不在AnimatePresence范围内:Framer Motion的AnimatePresence只能监控其内部包裹的元素的挂载/卸载状态,该元素在外部,所以退出动画永远不会触发。- exit属性中添加了
display: none:这个属性会让元素立即从视觉上消失,跳过动画执行过程——Framer Motion需要元素保持在DOM中直到动画完成,再自动移除它。 - content元素的key固定不变:当
backgroundColor变化时,content元素的key始终是"content",AnimatePresence不会将其识别为需要替换的元素,因此不会触发exit动画。
修复方案
针对上述问题,修改代码如下:
'use client'; import React from 'react'; import { motion, AnimatePresence } from 'framer-motion'; export default function TransLayout({ children, backgroundColor }) { const [isVisible, setIsVisible] = React.useState(false); React.useEffect(() => { setIsVisible(true); return () => setIsVisible(false); }, [backgroundColor]); const anim = (variants, custom = null) => { return { initial: 'initial', animate: 'enter', exit: 'exit', custom, variants, }; }; const columnsPiece = 5; return ( <div className='page stairs' style={{ backgroundColor }}> <AnimatePresence mode="wait"> {isVisible && ( <div key={backgroundColor} className="content-wrapper"> {/* 将transition-background移入AnimatePresence内部 */} <motion.div {...anim(opacity)} className='transition-background' /> <div className='transition-container'> {/* Top Containers - 移除exit中的display: none */} {[...Array(columnsPiece)].map((_, i) => ( <motion.div key={`top-${i}`} {...anim(expandTop, columnsPiece - i)} className='topContainer' /> ))} {/* Bottom Containers - 移除exit中的display: none */} {[...Array(columnsPiece)].map((_, i) => ( <motion.div key={`bottom-${i}`} {...anim(expandBottom, columnsPiece - i)} className='bottomContainer' /> ))} </div> {/* 让content的key随backgroundColor变化,触发新旧元素替换 */} <motion.div key={`content-${backgroundColor}`} initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ delay: 0.5 }} > {children} </motion.div> </div> )} </AnimatePresence> </div> ); }
额外说明
AnimatePresence的mode="wait"会确保旧元素的退出动画完全完成后,再渲染新元素,符合你的过渡需求。- 动画配置无需修改,只要保证元素在
AnimatePresence内部,且没有强制提前隐藏元素的属性,退出动画就能正常触发。
内容的提问来源于stack exchange,提问作者Emre Memiş
相关产品推荐
相关产品推荐

