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

相关推荐
wordbaby15 分钟前
Flutter Form Builder 完全指南:告别 Controller 地狱
前端·flutter
A***071727 分钟前
React数据可视化应用
前端·react.js·信息可视化
泉城老铁1 小时前
Vue2实现语音报警
前端·vue.js·架构
临江仙4551 小时前
前端骚操作:用户还在摸鱼,新版本已悄悄上线!一招实现无感知版本更新通知
前端·vue.js
想个什么名好呢1 小时前
解决uniapp的H5项目uni-popup页面滚动穿透bug
前端
用户93816912553602 小时前
Vue3项目--mock数据
前端
前端加油站2 小时前
一种新HTML 页面转换成 PDF 技术方案
前端·javascript·vue.js
w***Q3502 小时前
Vue打包
前端·javascript·vue.js
有事没事实验室2 小时前
router-link的custom模式
前端·javascript·vue.js
常乐我净6162 小时前
十、路由和导航
前端