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

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>
  );
}

错误原因

  1. Portal是类组件,直接给它绑定ref时,获取到的是组件实例而非DOM元素,但CSSTransition的nodeRef需要指向实际的DOM节点才能正确应用动画类。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 20:01:33