Framer Motion结合React Portals使用时过渡动画未触发问题求助
问题原因
你遇到的动画不触发问题有两个核心原因:
- Context中事件监听的处理函数引用不一致,导致点击按钮时会重复触发状态更新,打断Framer Motion的动画执行流程
- 当使用React Portal挂载组件时,AnimatePresence需要明确感知子元素的挂载/卸载周期,你当前把条件渲染逻辑放在AnimatePresence内部,首次添加子元素时Framer Motion没有正确触发状态切换,导致元素停留在initial的
opacity:0状态
修复步骤
1. 修复事件监听的引用问题
Context.js中的useEffect内,添加和移除的事件监听函数需要保持同一个引用,避免重复绑定:
// Context.js 修改后的useEffect部分 React.useEffect(() => { const startEl = document.getElementById("start-portal"); // 把处理函数提取成单独变量,保证add和remove用的是同一个引用 const handleClick = () => setActiveItem(0); startEl.addEventListener("click", handleClick); return () => { startEl.removeEventListener("click", handleClick); }; }, []);
2. 调整AnimatePresence和条件渲染的层级
把条件渲染逻辑上移到Portal外层,让AnimatePresence直接包裹需要挂载/卸载的Portal内容,保证动画触发逻辑正常:
首先修改index.js:
// index.js 修改后 import React from "react"; import ReactDOM from "react-dom"; import { AnimatePresence } from "framer-motion"; import Modal from "./Modal"; import { ContextProvider, useContext } from "./Context"; import "./styles.css"; const REACT_ROOT = document.getElementById("react-root"); const PORTAL_ROOT = document.getElementById("portal"); const PortalModal = () => { const { activeItem } = useContext(); if (!PORTAL_ROOT) return null; // 仅当activeItem不是-1时渲染Modal,让AnimatePresence感知整个Modal的挂载卸载 return ReactDOM.createPortal( <AnimatePresence exitBeforeEnter> {activeItem !== -1 && <Modal key="modal" />} </AnimatePresence>, PORTAL_ROOT ); }; const App = () => ( <ContextProvider> <PortalModal /> </ContextProvider> ); ReactDOM.render(<App />, REACT_ROOT);
然后修改Modal.js,移除内部的AnimatePresence,保留motion元素的动画配置:
// Modal.js 修改后 import React from "react"; import { m } from "framer-motion"; import { useContext } from "./Context"; const Modal = () => { const { activeItem, setActiveItem } = useContext(); return ( <> {activeItem === 0 && ( <m.div key={"step-1"} className={"step"} initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} > <h2>STEP 1</h2> <button onClick={() => setActiveItem(-1)}>CLOSE</button> <button onClick={() => setActiveItem(1)}>NEXT</button> </m.div> )} {activeItem === 1 && ( <m.div key={"step-2"} className={"step"} initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} > <h2>STEP 2</h2> <button onClick={() => setActiveItem(-1)}>CLOSE</button> <button onClick={() => setActiveItem(0)}>PREV</button> </m.div> )} </> ); }; export default Modal;
3. 可选排查项
如果修复后还是有问题,检查你的.step类对应的CSS样式,不要手动设置opacity或者transition属性,避免和Framer Motion生成的内联样式冲突。
内容的提问来源于stack exchange,提问作者heyron
相关产品推荐
相关产品推荐

