React createPortal 实战:模态框逃出 overflow:hidden、事件冒泡与焦点管理
React createPortal 实战:模态框逃出 overflow:hidden、事件冒泡与焦点管理写模态框(Modal)时你大概率遇到过这个诡异现象:弹窗明明该盖住全屏,结果只在某个卡片内部显示一小块,还被父容器的滚动条截断了。加z-index: 9999也没用。问题不在层级,在于 DOM 结构。这篇用createPortal把这个坑填平,顺带讲清楚 Portal 里事件冒泡和焦点管理这两个真正容易翻车的地方。先复现:被父容器裁掉的弹窗假设有个卡片列表,每张卡片里放一个「查看详情」的弹窗:function Card() { const [open, setOpen] useState(false); return ( div classNamecard style{{ overflow: hidden, position: relative }} button onClick{() setOpen(true)}查看详情/button {open ( div classNamemodal-mask div classNamemodal这里是详情内容/div /div )} /div ); }你期望.modal-mask铺满屏幕,但实际它是.card的子节点。父级的overflow: hidden会把溢出部分裁掉,position: relative又让absolute/fixed定位的基准变成了这张卡片而不是视口。于是弹窗被困在卡片里。治标的办法是去掉父级的overflow和transform,但这些样式往往是布局必需的,牵一发动全身。真正的解法是让弹窗的 DOM 节点根本不待在卡片里。createPortal:渲染到别处,状态还在这里createPortal(children, container)做的事很简单:把children渲染到你指定的 DOM 节点里,而不是当前组件的位置。但这个组件在 React 树里的逻辑位置不变——props、state、context 全都照常从父组件流下来。import { createPortal } from react-dom; function Modal({ children, onClose }) { // document.body 不受任何卡片的 overflow/transform 影响 return createPortal( div classNamemodal-mask onClick{onClose} div classNamemodal onClick{(e) e.stopPropagation()} {children} /div /div, document.body ); }改造后的卡片:function Card() { const [open, setOpen] useState(false); return ( div classNamecard style{{ overflow: hidden }} button onClick{() setOpen(true)}查看详情/button {open Modal onClose{() setOpen(false)}这里是详情内容/Modal} /div ); }现在.modal-mask是document.body的直接子节点,position: fixed相对视口定位,不再被任何卡片裁剪。而open这个 state 依然住在Card里,onClose也是Card传下去的——逻辑归属和 DOM 归属彻底解耦。配套的 CSS:.modal-mask{position:fixed;inset:0;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;}.modal{background:#fff;padding:24px;border-radius:8px;min-width:320px;}坑一:事件冒泡走的是 React 树,不是 DOM 树这是 Portal 最反直觉的地方。弹窗的 DOM 节点在body下,但 React 的合成事件冒泡,走的是组件树而不是真实 DOM 树。也就是说,如果Card外面还包了个onClick,点击 Portal 里的按钮,事件依然会冒泡到那个外层onClick:function Panel() { return ( // 点弹窗里的按钮,这个 onClick 也会触发 div onClick{() console.log(panel clicked)} Card / /div ); }这经常导致「点弹窗内部,结果触发了背后面板的点击逻辑」。记住:Portal 里的 React 事件按组件层级冒泡。要拦就在合适的层级e.stopPropagation(),别指望 DOM 结构帮你隔离。上面Modal里给.modal加onClick{(e) e.stopPropagation()},就是防止点击弹窗内容时冒泡到.modal-mask触发onClose——这一步利用的正是「按组件树冒泡」的特性。坑二:焦点管理,别让键盘用户困在弹窗外弹窗打开后,键盘 Tab 焦点应该被「关」在弹窗内,关闭后焦点要还给触发它的按钮。很多手写模态框忽略这点,导致视障用户用起来完全乱套。至少要做三件事:打开时聚焦到弹窗、Esc 关闭、关闭后焦点归还。function Modal({ children, onClose }) { const ref useRef(null); useEffect(() { // 记住打开弹窗前的焦点元素,关闭时好还回去 const prev document.activeElement; ref.current?.focus(); const onKey (e) { if (e.key Escape) onClose(); }; document.addEventListener(keydown, onKey); return () { document.removeEventListener(keydown, onKey); // 归还焦点,否则焦点会掉到 body 上,键盘用户彻底迷路 prev instanceof HTMLElement prev.focus(); }; }, [onClose]); return createPortal( div classNamemodal-mask onClick{onClose} div classNamemodal ref{ref} tabIndex{-1} // 让这个 div 能接收 focus roledialog aria-modaltrue onClick{(e) e.stopPropagation()} {children} /div /div, document.body ); }tabIndex{-1}让.modal可以被.focus()聚焦但不进入 Tab 序列;roledialog和aria-modaltrue让屏幕阅读器知道这是个模态对话框。坑三:body 滚动穿透弹窗打开时,如果背景页面还能滚,移动端体验会很糟(滑弹窗结果背景在动)。打开时锁住 body 滚动:useEffect(() { const original document.body.style.overflow; document.body.style.overflow hidden; return () { document.body.style.overflow original; // 恢复原值,别写死成 }; }, []);注意恢复时用保存的original而不是硬写。如果同时开了多个需要锁滚动的组件,写死会导致先关的那个把滚动提前放开。生产项目里更稳的做法是用计数器管理,但单弹窗场景保存原值就够了。进阶:挂到专用容器而不是 body直接挂document.body在多数场景够用,但如果项目里有多个弹窗层级(弹窗里再弹确认框),或者要控制它们的 DOM 顺序,建议挂到一个专用的根节点:function usePortalContainer() { const [el] useState(() { const div document.createElement(div); div.id portal-root; return div; }); useEffect(() { document.body.appendChild(el); return () el.remove(); // 组件卸载时清掉,别留一堆空 div }, [el]); return el; }用useState的惰性初始化保证div只创建一次(直接useRef(document.createElement(...))也行,但每次渲染都会执行createElement,虽然结果被丢弃仍是浪费)。返回的el传给createPortal第二个参数即可。小结Portal 的价值:把弹窗 DOM 渲染到body,逃出父级overflow: hidden/position/transform的束缚,状态和 props 仍从原组件流转。事件冒泡按组件树走,不按 DOM 树——想隔离必须显式stopPropagation,别靠 DOM 结构。无障碍三件套:打开聚焦弹窗、Esc 关闭、关闭归还焦点,配roledialogaria-modal。锁 body 滚动记得保存并恢复原值,别硬写空字符串。一句话记忆:createPortal只搬 DOM 不搬逻辑——DOM 挂到 body,事件和状态还在原地。

相关新闻

LangChain、Agent与RAG:从Demo到生产环境的工程实践指南

LangChain、Agent与RAG:从Demo到生产环境的工程实践指南

如果你最近在关注大模型应用开发,大概率会反复看到这几个词:LangChain、Agent、RAG。它们被各种教程、文章和视频反复提及,但很多人学完后依然困惑——这些概念到底解决了什么问题?为什么我的第一个Demo跑通了,但真正想…

2026/7/30 5:07:48阅读更多 →
动画制作全流程解析:从角色设计到渲染输出的技术实践

动画制作全流程解析:从角色设计到渲染输出的技术实践

这次我们来看一个关于动画创作的项目,主题围绕"龙女等待勇者转生"的二次元故事展开。这类内容创作涉及动画制作、角色设计、叙事节奏等多个技术环节,对于想要进入动画领域的创作者来说,既是一次创意的实践,也是技术能力…

2026/7/30 5:07:48阅读更多 →
Quantum ESPRESSO ph.x 输入文件避坑指南:单q点声子计算实战

Quantum ESPRESSO ph.x 输入文件避坑指南:单q点声子计算实战

1. 项目概述:声子谱计算中的“临门一脚” 在凝聚态物理和材料计算领域,声子谱的计算是理解材料晶格动力学性质、预测热学行为乃至判断结构稳定性的核心手段。对于使用Quantum ESPRESSO(QE)这套开源第一性原理计算软件包的从业者来…

2026/7/30 5:07:48阅读更多 →
留学生论文神器|搞定Turnitin查重+英文润色+AI痕迹消除✅

留学生论文神器|搞定Turnitin查重+英文润色+AI痕迹消除✅

对于留学生来说,论文审核逻辑和国内完全不一样。 不是单纯降重就行,核心难点是:Turnitin重复率、AI Writing检测、英文句式地道度、学术语态合规。 很多同学作业分数低、被导师质疑AI代写、重复率飘红,不是写得不好,…

2026/7/30 6:16:35阅读更多 →
终极Windows任务栏美化神器:3分钟打造Mac风格居中体验

终极Windows任务栏美化神器:3分钟打造Mac风格居中体验

终极Windows任务栏美化神器:3分钟打造Mac风格居中体验 【免费下载链接】TaskbarX Center Windows taskbar icons with a variety of animations and options. 项目地址: https://gitcode.com/gh_mirrors/ta/TaskbarX 还在羡慕Mac电脑那优雅居中的任务栏吗&am…

2026/7/30 6:16:35阅读更多 →
论文工具避坑|2026年千万别再乱花钱!这5个套路太坑了❌

论文工具避坑|2026年千万别再乱花钱!这5个套路太坑了❌

写论文最亏的不是写得慢,是不停踩坑、不停交智商税。 市面上五花八门的论文工具,看着功能齐全、低价引流,实则全是隐形套路:免费是试用、低价是陷阱、改完直接翻车、论文悄悄泄露。 今天整理学生最容易踩的5大论文工具深坑&…

2026/7/30 6:16:35阅读更多 →
DDL急救|论文赶稿崩溃?这套零翻车流程真的救大命[特殊字符]

DDL急救|论文赶稿崩溃?这套零翻车流程真的救大命[特殊字符]

谁毕业季没经历过这种崩溃: 截止日期越来越近,开题没思路、初稿写不完、重复率居高不下、格式改到心态炸裂,熬夜通宵还越改越乱。 赶论文最忌讳瞎忙、乱换工具、盲目降重。其实只要找对流程和工具,短时间也能交出合规、合格的毕…

2026/7/30 6:16:35阅读更多 →
三拓化学:耐温耐磨粉公司,布局华南广东等地区,赋能涂料产业升级

三拓化学:耐温耐磨粉公司,布局华南广东等地区,赋能涂料产业升级

当前国内高端制造产业链持续升级,涂料、油墨、胶粘剂等细分领域对产品性能的要求不断提升,耐温、耐磨、抗刮、流平等功能性指标已成为衡量产品市场竞争力的核心标准。从家具木器漆的手感优化,到工业地坪漆的耐用性提升,再到五金烤…

2026/7/30 6:16:35阅读更多 →
LCD1602驱动全解析:从HD44780时序到跨平台实战避坑指南

LCD1602驱动全解析:从HD44780时序到跨平台实战避坑指南

1. 项目概述:从点亮第一行字符开始拿到一块单片机开发板,旁边放着一块小小的LCD1602液晶屏,这几乎是每个嵌入式初学者都会经历的经典场景。这块屏幕不大,只能显示两行,每行16个字符,但它却是连接我们代码世…

2026/7/30 6:14:35阅读更多 →
覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

🔹 工具基础介绍 OpenClaw 是开源生态中一款实用性较强的本地智能工具,凭借本地离线运行、可视化图形操作和任务自动化三大核心特性,赢得了众多用户的青睐。与普通在线对话AI工具不同,它属于能够直接操控本机软硬件的智能数字员工…

2026/7/29 9:47:45阅读更多 →
伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

所谓液压伺服阀体的精密激光焊接,是用激光束对阀座壳体(通常为不锈钢或铝合金)进行密封焊接,使阀体在21-35MPa的高压液压油或压缩气体中长期运行而不发生介质泄漏。液压伺服阀是高端液压系统的"大脑"。从航空航天飞行控…

2026/7/29 7:00:19阅读更多 →
D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 你是否还在…

2026/7/29 7:58:51阅读更多 →
3分钟解锁iOS应用自由:TrollInstallerX让你的iPhone摆脱安装限制 [特殊字符]

3分钟解锁iOS应用自由:TrollInstallerX让你的iPhone摆脱安装限制 [特殊字符]

3分钟解锁iOS应用自由:TrollInstallerX让你的iPhone摆脱安装限制 🚀 【免费下载链接】TrollInstallerX A TrollStore installer for iOS 14.0 - 16.6.1 项目地址: https://gitcode.com/gh_mirrors/tr/TrollInstallerX 你是否曾经因为iOS系统的严格…

2026/7/30 0:00:58阅读更多 →
[GESP202606 四级] 扫雷

[GESP202606 四级] 扫雷

B4557 [GESP202606 四级] 扫雷 https://www.luogu.com.cn/problem/B4557 中国计算机学会(CCF)2026年6月C四级讲解——扫雷 https://www.bilibili.com/video/BV1MCMg6AEXR/ B4557 [GESP202606 四级] 扫雷 https://www.bilibili.com/video/BV1ZKTj6ZEVh/ 2…

2026/7/30 0:00:58阅读更多 →
Windows驱动存储终极清理工具:DriverStoreExplorer完全指南

Windows驱动存储终极清理工具:DriverStoreExplorer完全指南

Windows驱动存储终极清理工具:DriverStoreExplorer完全指南 【免费下载链接】DriverStoreExplorer Driver Store Explorer 项目地址: https://gitcode.com/gh_mirrors/dr/DriverStoreExplorer 您是否曾因Windows系统盘空间不足而烦恼?是否遇到过设…

2026/7/30 0:00:58阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/30 0:27:26阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/30 4:47:18阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/29 14:26:42阅读更多 →