CSSTransition与React Portals配合失效问题求助
问题:CSSTransition 与 React Portal 配合使用时出现错误
我有一个实现了缩放动画的弹窗项目,使用CSSTransition时一切正常,但将CSSTransition内部的容器div替换为自定义Portal组件后出现错误。
原项目代码
/src/App.js
import React, { useState } from 'react'; import './style.css'; import Modal from './Modal'; export default function App() { const [ isOpen, setIsOpen ] = useState(false); const handleClick = () => { setIsOpen(!isOpen); }; return ( <div> <h1>Hello StackBlitz!</h1> <p>Start editing to see some magic happen :)</p> <button onClick={handleClick}>Click Me</button> <br /><br /> <Modal isOpen={isOpen}> <div>Hello World</div> </Modal> </div> ); }
/src/Portal.js
import { Component } from 'react'; import ReactDOM from 'react-dom'; const portal = document.getElementById('portal'); export default class Portal extends Component { render() { return ReactDOM.createPortal(this.props.children, portal); } }
/src/Modal.jsx(正常工作版本)
import React, { useRef } from "react"; import { CSSTransition } from "react-transition-group"; import Portal from "./Portal"; export default function Modal({ isOpen, children }) { const container = useRef(null); return ( <CSSTransition nodeRef={container} in={isOpen} timeout={300} classNames="modal" unmountOnExit onEnter={() => console.log('entered')} onExited={() => console.log('exited')} > <div ref={container} style={{ display: 'inline-block', backgroundColor: '#0f0', padding: '50px', }}> {children} </div> </CSSTransition> ); }
/src/style.css
h1, p { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; } .modal-enter { transform: scale(0); } .modal-enter-active { transform: scale(1); transition: transform 300ms; } .modal-exit { transform: scale(1); } .modal-exit-active { transform: scale(0); transition: transform 300ms; }
错误场景
将Modal.jsx中CSSTransition内部的div替换为Portal组件后报错,修改后的代码如下:
import React, { useRef } from "react"; import { CSSTransition } from "react-transition-group"; import Portal from "./Portal"; export default function Modal({ isOpen, children }) { const container = useRef(null); return ( <CSSTransition nodeRef={container} in={isOpen} timeout={300} classNames="modal" unmountOnExit onEnter={() => console.log('entered')} onExited={() => console.log('exited')} > <Portal ref={container} style={{ display: 'inline-block', backgroundColor: '#0f0', padding: '50px', }}> {children} </Portal> </CSSTransition> ); }
错误原因
Portal是类组件,直接给它绑定ref时,获取到的是组件实例而非DOM元素,但CSSTransition的nodeRef需要指向实际的DOM节点才能正确应用动画类。Portal本身仅负责将子元素渲染到指定的DOM节点,动画效果应该作用在Portal内部的容器元素上,而非Portal组件本身。
修复方案
调整Modal.jsx的结构,将ref绑定到Portal内部的容器div上,让CSSTransition能正确获取DOM元素:
修改后的/src/Modal.jsx
import React, { useRef } from "react"; import { CSSTransition } from "react-transition-group"; import Portal from "./Portal"; export default function Modal({ isOpen, children }) { const container = useRef(null); return ( <CSSTransition nodeRef={container} in={isOpen} timeout={300} classNames="modal" unmountOnExit onEnter={() => console.log('entered')} onExited={() => console.log('exited')} > <Portal> <div ref={container} style={{ display: 'inline-block', backgroundColor: '#0f0', padding: '50px', }}> {children} </div> </Portal> </CSSTransition> ); }
说明
- 保持
Portal组件原有逻辑不变,它负责将内部的div渲染到指定的#portal节点。 - 将动画所需的
ref绑定到Portal内部的div上,CSSTransition就能正确为这个DOM元素添加/移除动画类,实现缩放效果。
内容的提问来源于stack exchange,提问作者Viewsonic
相关产品推荐
相关产品推荐

