全屏搜索层必须用 且为 直接子元素,配合 showmodal() 和 open 属性实现语义、焦点管理与无障碍;全局快捷键需在 document 监听 keydown 并 preventdefault();动画应使用 transform: scale() 与 will-change: transform 优化性能。

全屏搜索层必须用 <dialog></dialog>,不是 <div> + <code>position: fixed
<dialog></dialog> 是唯一能同时满足语义、焦点管理、模态阻断和无障碍的方案。用 <div> 手动实现,哪怕加了 <code>inert 和 aria-modal="true",也大概率在 Safari 或旧版 Edge 里漏掉背景点击关闭、Tab 键跳出浮层、或屏幕阅读器无法识别“这是搜索模态框”。
常见错误:
- 把
<dialog></dialog>套在<header></header>或<main></main>里 —— 它必须是的直接子元素,否则showModal()可能失效或触发异常滚动 - 用
display: none控制显隐 —— 这会让<dialog></dialog>失去原生焦点捕获能力,且动画无法触发 - 忘记写
open属性或调用showModal()—— 单靠 CSS 显示不等于模态行为激活
正确结构示例:
<header>...</header><main>...</main><dialog id="search-dialog"><form>
<input type="search" name="q" autofocus><button type="submit">搜索</button>
<button type="submit" formmethod="dialog">✕</button>
</form>
</dialog>
document.addEventListener('keydown') 监听位置和修饰键判断不能错
全局快捷键(如 Cmd/Ctrl+K)必须在 document 上监听 keydown,而不是绑在按钮或输入框上。用户可能在任意位置按组合键,监听点错了就完全失效。
关键细节:
- 用
keydown,不用keyup或keypress—— 后者已废弃,且不暴露metaKey/ctrlKey - 必须调用
e.preventDefault(),否则 Chrome 会聚焦地址栏,Safari 可能触发系统 Spotlight - 要排除编辑中状态:
!e.target.matches('input, textarea, [contenteditable]'),否则用户正在打字时弹出浮层会打断输入流 - 判断逻辑写成
e.key === 'k' && (e.metaKey || e.ctrlKey),别写反顺序,避免短路失败
搜索框聚焦后,焦点限制和 ESC 行为要闭环
<dialog></dialog> 自带焦点环限制,但前提是它真正处于 open 状态且没被 JS 干扰。如果手动调用了 focus() 却没等 showModal() 完成,焦点可能落在 body 上。
容易踩的坑:
-
searchInput.focus()写在showModal()之前 —— 浏览器还没准备好模态上下文,焦点会丢失 - 按 ESC 后只
blur()输入框,没调close()—— 浮层视觉隐藏但open属性仍为 true,焦点没归还,键盘仍被拦截 - 关闭后没恢复原焦点 ——
<dialog></dialog>默认支持returnFocus,但如果你手动移除或重置了它,就得自己存document.activeElement并 restore
移动端额外注意:iOS Safari 的 <dialog></dialog> backdrop 点击有时不冒泡,建议给 backdrop 层加 onclick="this.closest('dialog').close()" 保底。
动画性能选 transform,但别忽略 transform-origin 和 will-change
全屏展开动画若用 width/height 过渡,会在低端安卓机上明显卡顿,因为每次帧都要重排布局。改用 transform: scale() 是唯一靠谱选择。
实操要点:
- 初始状态设
transform: scale(0.95); opacity: 0;,展开时到scale(1); opacity: 1; - 必须显式设
transform-origin: top left—— 默认中心缩放会导致顶部内容先露、底部后到,视觉撕裂 - 在动画元素上加
will-change: transform,尤其对 iOS Safari 和低端 Android 有首帧优化效果 - 过渡属性写全:
transition: transform 0.3s cubic-bezier(0.22, 0.61, 0.36, 1), opacity 0.3s,别用all,否则 color 或其他属性意外参与过渡
复杂点往往藏在细节里:比如 transform-origin 值写成 0 0 在某些浏览器里不生效,必须用 top left;又比如 will-change 加在父容器而非动画元素本身,会导致合成层错位。











