Native Overlay
Native Dialog (showModal)
使用 <dialog>.showModal()。顶层渲染 ✓,Escape 关闭 ✓,滚动锁定 ✓,::backdrop 伪元素。
发现: 滚动锁定开箱即用。
没有锚点定位,只能居中弹窗,不能做 dropdown/menu/popover。
::backdrop 进场动画正常,退场无法动画——浏览器在 close() 时立即销毁 ::backdrop,这是 API 限制。没有锚点定位,只能居中弹窗,不能做 dropdown/menu/popover。
floating-ui + Native Popover (left flip)
left 方向,floating-ui 自动计算位置。HTML 里通过 popovertarget 关联。
Left Positioned
floating-ui computePosition + flip + shift。
左侧空间不够时自动翻转到右边。
发现: 定位和 Light Dismiss 都解决了。
但 滚动锁定仍然没有 — 打开后背景继续滚。
但 滚动锁定仍然没有 — 打开后背景继续滚。
Native Popover (popover attribute)
使用浏览器原生 popover 属性。顶层渲染 ✓,light dismiss ✓,锚点定位(需要 CSS anchor 或 JS)。
popover + floating-ui
浏览器自动 Top Layer + Light Dismiss。
floating-ui 做锚点定位 + 碰撞检测。
Top Placement
placement=top,滚动到页面底部打开试试翻转。
发现: 原生 popover 不阻止背景滚动。打开上面的 popover 然后滚动页面试试 — body 照样滚。
CSS anchor 定位完全不需要 floating-ui,但 兼容性太差(Chrome only),且没有碰撞检测。
总结对比
| 能力 | popover | dialog.showModal | popover + floating-ui | ark/zag + Overlay |
|---|---|---|---|---|
| 顶层渲染 | ✓ | ✓ | ✓ | ✓ (Portal) |
| 锚点定位 | △ (CSS anchor, 受限) | ✗ | ✓ | ✓ |
| 碰撞检测 | △ (position-try) | ✗ | ✓ | ✓ |
| Light Dismiss | ✓ | ✓ (Escape) | ✓ | ✓ (机器状态管理) |
| 滚动锁定 | ✗ | ✓ | ✗ | ✓ (preventBodyScroll) |
| Android 返回手势 | ? | ? | ? | ✓ (CloseWatcher) |
| Nested 弹层 | △ (popover 是全局的) | △ (不支持嵌套) | △ | ✓ (overlay stack) |
结论:没有一种原生 API 能完整覆盖下拉弹层的全部需求。
popover最接近,但缺 滚动锁定、定位引擎、复杂状态管理dialog.showModal()有滚动锁定但 不能做锚点定位,只适合模态弹窗- ark-ui/zag-js + 我们的 Overlay 是当前唯一覆盖所有格子的方案
未来的可能性:如果浏览器给 popover 加上 lock-scroll 属性(有提案),
再把 @position-try, anchor(), position-area 做到 floating-ui 同级的鲁棒性,
那 ark/zag 的定位层就可以被替代。但状态机层(嵌套管理、open/close 转换、键盘导航)仍然需要。