应使用 标签替代 实现弹窗,因其原生支持背景不可交互、ESC 关闭、焦点捕获;必须调用 showModal() 而非仅设 open 属性,关闭用 close() 并通过 returnValue 获取结果;表单须用 包裹,配合语义化 和原生校验 API,遮罩用 ::backdrop 定制,动画避免重排。

用 <dialog></dialog> 替代 <div> 弹窗容器
<p>直接用 <code><dialog></dialog> 标签作为弹窗最外层,不是为了“新潮”,而是它天然解决三个硬伤:背景不可交互、ESC 键关闭、焦点自动 trap。手写 <div class="modal"> 时,你得自己监听 <code>click 遮罩、手动 focus() 到第一个输入框、加 inert 或遍历禁用背景元素——稍一遗漏,键盘用户就可能 tab 出弹窗,屏幕阅读器会读错层级。
常见错误现象:<dialog></dialog> 写了但没调 showModal(),结果弹窗不模态;或用了 open 属性却忘了移除,导致刷新后仍显示。
- 必须用
dialog.showModal()触发,而非仅设open属性(后者是非模态的) - 关闭时优先用
dialog.close(),不要直接删 DOM 或设open = false - 需要返回值(比如“确认”还是“取消”)?用
dialog.returnValue,它在close()后自动赋值
表单结构必须用 <form></form> 包裹,哪怕只做前端校验
别图省事把输入框直接塞进 <dialog></dialog> 里。没有 <form></form>,回车无法提交、label 关联失效、Tab 键顺序错乱、屏幕阅读器识别为普通文本流——这些不是“体验差”,是 WCAG 直接不合规。
实操建议:
立即学习“前端免费学习笔记(深入)”;
-
<form></form>可以无action和method,空标签也保有全部语义能力 - 每个
<input>必须有对应<label for="xxx"></label>,或用包裹式<label>用户名<input name="username"></label> - 禁用
onsubmit="return false"这种粗暴拦截,改用event.preventDefault()+ 自定义逻辑
校验逻辑优先走原生 API,别急着写正则
浏览器自带的 checkValidity()、setCustomValidity() 和 reportValidity() 足够覆盖 80% 场景,比手写 input 事件监听更可靠,且自动触发 UI 提示(如红色边框、气泡提示)。
容易踩的坑:
-
type="email"或type="url"不校验空值,需额外加required -
setCustomValidity('')才代表“通过”,传字符串即视为失败 - 移动端 iOS Safari 对
type="number"的小数点支持弱,加inputmode="decimal"更稳
遮罩层和动画要分层控制,别混在同一个 <div>
<p><code><dialog></dialog> 的遮罩是浏览器自动生成的,用 ::backdrop 伪元素定制样式即可,不需要额外写一个 <div class="mask">。否则会出现两个遮罩叠加、z-index 冲突、点击穿透等问题。
<p>性能影响明显:</p>
<ul>
<li>用 <code>dialog::backdrop { background: rgba(0,0,0,0.5); },别用 JS 动态插入遮罩 DOM
transition + transform,避免触发布局重排;CSS 动画结束再调 dialog.remove()
::backdrop 不支持动画,需 fallback 到 JS 控制 opacity真正麻烦的从来不是“怎么弹出来”,而是“弹出来之后,用户能不能用键盘走到密码框、能不能回车提交、关掉后焦点回哪、屏幕阅读器怎么念这个弹窗”。这些细节藏在 HTML 语义和浏览器默认行为里,不是靠 JS 补丁能打平的。



















