
上一篇讲到了top-layer和z-index的内容上一篇说到了showPopover()之后的气泡能压住z-index: 99999的元素。但是气泡而已给 toast 也上 popover 不就完了这算什么难点。先把上一篇遗留的问题说完这篇主要讲的是z-index: 99999 输给 z-index: 2**、dialog 自己也有两种、以及会发现一个有有意思的现象一个气泡看得见点不着**。一、Top Layer 是什么浏览器渲染页面时除了你熟悉的那棵 DOM 渲染树还有一层独立存在的顶层Top Layer。模态 dialog、打开状态的 popover都会被浏览器直接挂到这一层。它的规矩和 DOM 里完全不同// 进场方式只有两张门票dialog.showModal();// 模态 dialogpopoverEl.showPopover();// popover// 验证状态:modal 伪类Chromium 143 实测命中dialogEl.matches(:modal);// true在浏览器中的展现是这个样子如果有多个的话会依次向下排列CSS 中也没有属性能把你送进这一层HTMHell 2025 年 12 月那篇 Top Layer 专文的原话是「目前没有任何属性可以提升元素到 top layer」。z-index: 99999在这里失效不是因为数字不够大而是因为z-index 这套规则在 Top Layer 根本不参与。z-index 的游戏是「同一层叠上下文内比大小」而 Top Layer 压根不在页面那棵上下文树里。z-index 是班级排名Top Layer 是另一个年级。二、你的 z-index 大战为什么总输先看一场我在本地跑的对抗赛divclasstrap-wrap transformedidwrapdivclasstrap-innerz-index: 99999/div/divdivclassrivalz-index: 2/div.trap-wrap.transformed{transform:translateZ(0);}.trap-inner{position:absolute;inset:0;z-index:99999;}.rival{position:absolute;z-index:2;}自己先猜一下谁赢再往下看。T3a 父级有 transform: 重叠点(130,555) 最顶层rival ✅ z-index:2 打败了 99999被封顶 T3b 父级去掉 transform: 同一点最顶层trap-inner ✅ 99999 反杀成功z-index 没变输赢变了同一个元素同一个 99999我只是在父级上加了/去掉了transform: translateZ(0)输赢就反转了。从结果就可以看出z-index 不是全局排名是上下文内排名。父级一旦带着transform、opacity、filter、will-change这类属性就会创建一个新的层叠上下文子元素的 99999 只在这个小圈子里有效——而这个圈子整体在根上下文里的排位约等于 0。通俗一点来说99999 是班级第一但整个班在年级里的排位是垫底隔壁班一个 z-index: 2 就把你们全班压了。position: fixed救不了它——fixed 只是换个姿势留在原地上下文树里的辈分一点没变。想彻底退出战场只有两条路去掉父级的 transform 或者 进 Top Layer。三、Top Layer 的三条实测规则3.1 dialog 也有两种showModal()进 Top Layershow()不进。T2 show() 后 :modal 命中: False非模态不进 Top Layer T2b 重叠点(790,555) 最顶层: n-rival ✅ z-index:10 打败了非模态 dialog非模态打开的 dialog 就是个普通定位元素z-index: 10 说压就压。所以「用了 dialog 就高枕无忧」是个错觉——dialog 的地位取决于打开方式不是标签名。要 Top Layer必须showModal()。3.2 Top Layer 内部按进层顺序排队多个 Top Layer 元素同时存在时谁在上面答案是最新进层的排最上。T4c 反向先气泡后模态框: 同一点命中dlg-t ✅ 模态框最新进层压住气泡先开的气泡被后开的模态框直接压住。进层顺序就是栈后进者居上。3.3 看得见点不着下面会有一个最反直觉的部分。把顺序反过来先开模态框再开气泡——按 3.2 的规则气泡最新进层应该在最上面。测试结果T4a 先开模态框后开气泡: 重叠点(500,160) 像素(0, 255, 0)绿色气泡视觉在 backdrop 之上, elementFromPointdlg-t → ✅ 看得见视觉最新进层在上却点不着指针被 backdrop 截走 T4a-b 点击气泡中心: 按钮被点到False, 气泡被 light dismiss 关闭HTMHell 2025/12 记录的同款无障碍 bug分开成几点说截图像素采样是纯绿——气泡渲染在 backdrop 之上能看得清清楚楚但同一位置的elementFromPoint返回的是模态框指针事件被 backdrop 整个截走在气泡正中间放了按钮再真实点击——按钮的 onclick 没触发气泡倒是被点空白的 light dismiss 关掉了。以为的是点到了重试实际触发的是关闭。HTMHell 的作者在 2025 年 12 月记录了一模一样的事故toast 用 popover 实现模态框开着时后台弹出报错 toast用户看得见、Tab 不进去、点击永远到不了按钮——键盘和读屏用户直接被锁死。模态框打开时模态框之外的整页都是 inert惰性状态视觉层叠救不了交互。气泡在视觉上赢了你在无障碍上一败涂地。顺带解开 Vol.01 的一个伏笔为什么手写「点遮罩关闭」可以用e.target dialog判断因为 backdrop 上的点击target 就是对话框元素本身——backdrop 截走的指针事件全都记在 dialog 头上。两篇的机制在这里接上了。实操结论模态框打开前先把气泡收干净或者把 toast 挂进 dialog 内部跟着一起进层。3.4 白送的还不止这些还记得《button按钮原生开关弹窗零 JS 搞定80%交互场景》这篇文章中的代码里的lastFocused吗测试结果T1b 打开后焦点落在: inner-btndialog 内第一个可聚焦元素自动聚焦 T1b 关闭后焦点归还: focus-open ✅ 自动回到触发按钮lastFocused 白送showModal()打开时自动聚焦弹窗内第一个可聚焦元素关闭时焦点自动归还触发按钮。手写版最容易漏的两步原生自带。再来两个原生自带的dialogiddlg-vformmethoddialog!-- 提交即关闭无需 JS --buttonvalueok好/button/form/dialogT5 form methoddialog 提交: returnValueok弹窗自动关闭 ✅ T6 ::backdrop 计算样式: backgroundrgba(0, 0, 0, 0.5)methoddialog的表单提交后弹窗自动关闭用户点的是哪个按钮dialog.returnValue里查得到::backdrop伪元素能加样式磨砂玻璃就一行backdrop-filter: blur(4px)的事。四、怎么选场景用什么理由强制用户处理的表单/确认dialogshowModal()Top Layer inert 焦点管理提示气泡/toast/菜单popoverTop Layer light dismiss不劫持页面页内小面板伴随滚动show()或普通定位不需要抢占就别进 Top Layer要盖住一切的自定义层showModal()/showPopover()唯一两张门票z-index 洗洗睡模态框和 toast 同框时先收气泡再开框别让用户看见了但是却点不着。面试题Q1showModal()和show()有哪些区别答至少五条——①Top Layer只有 showModal 进②backdrop只有 showModal 有③:modal伪类只命中 showModal④ESC 关闭与 inert 背景只有 showModal⑤焦点管理自动聚焦关闭归还是 showModal 白送。一句话模态能力全部绑定 showModal。Q2父元素有transform子元素z-index: 99999为什么可能输给外部z-index: 2答transform 创建新层叠上下文子元素的 z-index 只在该上下文内部比较整个上下文在根层级排位不受子元素影响所以被外部 z-index: 2 整体压住。逃生方案去掉父级触发属性或用showModal()/showPopover()进 Top Layer。Q3模态框开着时弹出 toastpopover 实现用户能点到 toast 上的按钮吗答不能。toast 视觉上渲染在 backdrop 之上但模态框开启时页面其余部分 inert指针事件被 backdrop 截走——点击只会触发 toast 的 light dismiss按钮回调不执行。修复开模态框前关闭所有气泡或把 toast 放进 dialog 内部。总结Top Layer本系列三层弹层的最终答案 │ ├─ 入场券没有 CSS 属性能进 │ ├─ dialog.showModal() → 模态Top Layer backdrop inert 焦点管理 │ └─ el.showPopover() → 非模态Top Layer light dismiss │ └─ ⚠️ dialog.show() → 不进 Top Layer会被 z-index 打败 │ ├─ 为什么 z-index 大战总输 │ ├─ transform/opacity/filter/will-change → 父级封顶 │ └─ z-index 是上下文内排名不是全局排名 │ ├─ Top Layer 内部规则实测 │ ├─ 排序最新进层在最上栈 │ └─ ⚠️ 模态框开着时气泡看得见、点不着inert backdrop 截流 │ └─ 白送清单 ├─ 焦点自动聚焦 关闭归还Vol.01 的 lastFocused ├─ form methoddialog returnValue └─ ::backdrop 可加样式backdrop-filter 磨砂玻璃第四篇可以讲一下气泡终于能浮在最上层了但它该在触发按钮旁边CSS Anchor Positioning锚点定位从此告别手算坐标和 resize 监听。验证 Demo 与实测脚本已打包gzh程序员蜡笔熊回复「排位」获取。欢迎大家评论和指出意见