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

相关推荐
ZC跨境爬虫7 小时前
跟着 MDN 学 HTML day_9:(信件语义标记)
前端·css·笔记·ui·html
前端老石人7 小时前
HTML 字符引用完全指南
开发语言·前端·html
幼儿园技术家8 小时前
前端如何设计权限系统(RBAC / ABAC)?
前端
前端摸鱼匠9 小时前
Vue 3 的v-bind合并行为:讲解v-bind与普通属性合并的规则
前端·javascript·vue.js·前端框架·ecmascript
REDcker10 小时前
浏览器端Web程序性能分析与优化实战 DevTools指标与工程清单
开发语言·前端·javascript·vue·ecmascript·php·js
donecoding11 小时前
一个 sudo 引发的血案:npm 全局包权限错乱彻底修复
前端·node.js·前端工程化
风骏时光牛马11 小时前
Raku正则匹配与数据批量处理实操案例
前端
nbwenren11 小时前
2026实测:Gemini 3 镜像站视觉能力实践——拍照原型图,一键生成 HTML+CSS 代码
前端·css·html
Lee川12 小时前
Prisma 实战指南:像搭积木一样设计古诗词数据库
前端·数据库·后端