ReactDOM.createPortal 与 position: fixed 的本质区别

在前端开发中,我们常遇到这样的问题:

为什么我的 tooltip、弹窗、下拉菜单明明是 position: fixed,却还是被父元素裁剪、遮挡?

很多人以为是 overflow: hiddenz-index 的问题,但实际上------根本原因在于 "包含块 (containing block)" 的创建机制

1. overflow 本身不会影响 fixed

单独的 overflow: hidden 并不会让 fixed 元素失效:

css 复制代码
<div style={{ overflow: 'hidden' }}>
  <div style={{ position: 'fixed', top: 0 }}>我是 fixed</div>
</div>

上例中,fixed 仍然相对 视口 (viewport) 定位,不会被裁剪。

2. 谁在"偷走"你的 fixed?

当某个祖先元素具有以下任意属性时,它会创建一个新的 包含块 (containing block) ,从而让 fixed 元素失去对 viewport 的参照:

属性 示例
transform transform: translate(0, 0)
filter filter: blur(0)
perspective perspective: 1000px
contain contain: layout/paint
will-change will-change: transform
backdrop-filter backdrop-filter: blur(10px)

一旦出现这些属性,position: fixed 的元素将不再相对窗口定位,而是相对该祖先元素定位。

3. 被裁剪的真相

如果上述祖先元素同时还设置了 overflow: hiddenoverflow: auto

则 fixed 元素的可视区域也会被父元素裁剪掉:

less 复制代码
<div style={{
  transform: 'translateZ(0)',  // 关键:创建包含块
  overflow: 'hidden',
  border: '1px solid red',
  height: 100,
  width: 100,
}}>
  <div style={{
    position: 'fixed', 
    top: 0, 
    left: -20,
    background: 'black', 
    color: 'white'
  }}>
    我被裁剪了
  </div>
</div>

4. ReactDOM.createPortal 的作用

ReactDOM.createPortal 会把组件直接渲染到 document.body(或指定容器):

javascript 复制代码
ReactDOM.createPortal(
  <Tooltip />,
  document.body
)

这样做可以完全绕过那些"有问题的父容器",

确保你的 tooltip、弹窗永远相对 viewport 定位,不受裁剪或 transform 干扰。

5. Tooltip 对比示例

下面这段代码展示了同样的 tooltip,在 非 portalportal 模式下的差异。

less 复制代码
import React from 'react';
import ReactDOM from 'react-dom';

function NonPortalTooltip() {
  return (
    <div style={{
      position: 'relative',
      width: 200,
      height: 120,
      overflow: 'hidden',
      transform: 'translateZ(0)', // 创建包含块
      border: '1px solid #aaa',
      marginBottom: 20
    }}>
      <div style={{ padding: 8 }}>父容器(overflow + transform)</div>
      <div style={{
        position: 'fixed',
        top: 80, left: 150,
        background: 'black',
        color: 'white',
        padding: '4px 8px'
      }}>
        非 Portal Tooltip(会被裁剪)
      </div>
    </div>
  );
}

function PortalTooltip() {
  return (
    <div style={{
      position: 'relative',
      width: 200,
      height: 120,
      overflow: 'hidden',
      transform: 'translateZ(0)',
      border: '1px solid #aaa'
    }}>
      <div style={{ padding: 8 }}>父容器(overflow + transform)</div>
      {ReactDOM.createPortal(
        <div style={{
          position: 'fixed',
          top: 80, left: 150,
          background: 'green',
          color: 'white',
          padding: '4px 8px'
        }}>
          Portal Tooltip(不会被裁剪)
        </div>,
        document.body
      )}
    </div>
  );
}

export default function App() {
  return (
    <>
      <NonPortalTooltip />
      <PortalTooltip />
    </>
  );
}

运行后你会看到:

  • 黑色 tooltip(非 portal)会被父容器裁剪;
  • 绿色 tooltip(通过 portal 渲染)则始终完整可见。

✅ 一句话总结

position: fixed 相对视口定位的前提是:祖先元素没有创建新的包含块

ReactDOM.createPortal 的意义,就是让元素彻底脱离这些祖先的影响。

相关推荐
落霞的思绪1 小时前
配置React和React-dom为CDN引入
前端·react.js·前端框架
Hacker_Z&Q1 小时前
CSS 笔记2 (属性)
前端·css·笔记
Anastasiozzzz1 小时前
LeetCode Hot100 295. 数据流的中位数 MedianFinder
java·服务器·前端
Exquisite.2 小时前
Nginx
服务器·前端·nginx
打小就很皮...2 小时前
dnd-kit 实现表格拖拽排序
前端·react.js·表格拖拽·dnd-kit
Ulyanov2 小时前
从静态到沉浸:打造惊艳的Web技术发展历程3D时间轴
前端·javascript·html5·gui开发
打小就很皮...2 小时前
React 19 + Vite 6 + SWC 构建优化实践
前端·react.js·vite·swc
Highcharts.js2 小时前
使用Highcharts与React集成 官网文档使用说明
前端·react.js·前端框架·react·highcharts·官方文档
这是个栗子2 小时前
AI辅助编程(二) - 通译千问
前端·ai·通译千问
VT.馒头2 小时前
【力扣】2625. 扁平化嵌套数组
前端·javascript·算法·leetcode·职场和发展·typescript