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

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

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

很多人以为是 overflow: hidden 或 z-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: hidden 或 overflow: 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,在 非 portal 与 portal 模式下的差异。

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 的意义,就是让元素彻底脱离这些祖先的影响。

相关推荐
青柠之夏cc3 小时前
前端拖拽功能原生实现,不引入拖拽库完成业务
开发语言·前端·python
liangshanbo12153 小时前
前端大文件切片上传怎么做?
前端
Sand(ContextGate)4 小时前
Python Agent 测试实战:测试与评估,让 Agent 像传统软件一样可交付
前端·javascript·python·microsoft·ai
广州华水科技4 小时前
单北斗GNSS变形监测系统在城市安全与地质灾害中的应用前景
前端
颜进强4 小时前
20 · NestJs循环依赖与 forwardRef:容器为什么在环面前会死,"占位再补齐"怎么救
前端·后端·ai编程
颜进强4 小时前
19 · NestJs @Global 落地账:装饰器与 `isGlobal` 参数,各在什么场景上岗
前端·后端·ai编程
码艺-Alimjan4 小时前
Tauri 网站To桌面应用实战总结
前端·javascript·vue
颜进强4 小时前
18 · NestJS动态模块与 forRoot:`imports: [ConfigModule.forRoot({...})]` 到底在 import 什么
前端·后端·ai编程
YZ1225525 小时前
【Docker专题】使用Docker部署Vue-Flask项目前后端分离版【前端Docker部署】
前端·vue.js·docker
liangshanbo12155 小时前
浏览器渲染过程:高级前端面试题
前端