How to Create Accessible Modals and Pop-ups Using HTML, CSS, and Minimal JavaScript
TL;DR · AI 摘要
本文详解如何用HTML的<dialog>和 popover 属性创建可访问的模态框,强调语义化标签与键盘交互的重要性。
核心要点
- 使用 <dialog> 标签可自动处理模态框焦点管理
- popover 属性能实现 ESC 键关闭和点击外部关闭功能
- ::backdrop 伪类可定制遮罩层样式
结构提纲
按章节快速跳转。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- 创建可访问模态框
- HTML技术
- <dialog>标签
- popover属性
- CSS技巧
- ::backdrop伪类
- 交互注意事项
- 键盘事件处理
- 屏幕阅读器兼容
金句 / Highlights
值得收藏与分享的关键句。
<dialog> 标签可自动管理焦点,减少JavaScript代码量
使用 popover 属性可实现ESC键关闭和轻量级Dismiss功能
::backdrop 伪类允许为遮罩层单独设置样式和交互
如何使用 HTML、CSS 和少量 JavaScript 创建无障碍模态框和弹出窗口
2026年7月17日
/
#anchor css
jabo Landry
在网站上创建弹出窗口和模态框可能是一个复杂的过程,通常需要大量样板代码才能开始。
但真正的挑战出现在你想要使该模态框或弹出窗口具有无障碍功能时。
今天,我将向你展示如何使用 <dialog> 标签,通过 HTML、CSS 和 JavaScript 以最小的配置创建一个无障碍的模态框/弹出窗口。
前提条件
在深入之前,如果你对以下基础知识有一定的了解会有所帮助:
- CSS 基础:你应该熟悉常见的 CSS 术语和概念(选择器、属性、定位等)。
- HTML 结构:了解 DOM 中元素的组织方式,将使示例更容易理解。
- JavaScript DOM 基础:虽然本指南仅使用少量 JavaScript,但了解如何查询和操作 DOM 元素将使你在实验时更加自信。
这些就是你所需要的全部内容。不需要框架,不需要繁重的样板代码。只需要掌握核心网络技术的基础知识。
目录
- 前提条件
- 如何使用 popover 创建弹出窗口
- 如何使用 dialog 标签创建模态框
- 需要注意的陷阱
- ::backdrop 伪类
- 最后思考
如何使用 popover 创建弹出窗口
使用 HTML、CSS 和 JavaScript 从零开始创建弹出窗口可能相当具有挑战性。弹出窗口是一种小的对话框,出现在屏幕上以显示额外信息或请求输入。由于其设计是临时性的,用户期望在完成交互后(例如按下 Esc 键或点击外部区域)它会消失。
通过使用 HTML 的 popover 和 popovertarget 属性,你可以获得一些内置的无障碍和交互行为,如 Esc 键关闭和轻量级关闭,但你仍然需要选择合适的语义元素并测试键盘和屏幕阅读器的行为。
使用 HTML 设置弹出窗口
为了更具实用性,让我们创建一个常见的示例和网页上弹出窗口的使用场景。我们将创建一个导航栏元素,当用户点击汉堡菜单或菜单列表时显示。
首先,你将在弹出窗口容器上设置 popover 属性。这会告诉 HTML 将包含块视为弹出窗口,并默认将其从屏幕上隐藏。
然后,你在触发弹出窗口的元素(如按钮元素或其他元素)上设置 popovertarget 属性,通过 popover 属性来隐藏该元素。
#### 示例:
<button popovertarget="navbar-menu" id='nav-btn'>open</button>
<nav id="navbar-menu" popover>
<a href="#">Home</a>
<a href="#">About</a>
<a href="#">Address</a>
</nav>
``通过上述设置,你拥有一个具有有用内置交互行为的弹出窗口,包括 Esc 键关闭和轻量级关闭。你可以通过在键盘上按下 ESC 键或点击页面上的其他任何地方(只要不在弹出窗口区域内)来将其从屏幕上隐藏。
请记住,仅 popover 属性本身不会自动使弹出窗口变得无障碍。你仍然需要使用适当的语义元素,在需要的地方提供无障碍标签,并测试键盘和屏幕阅读器的行为。
如何对齐弹出窗口
现在你希望对弹出框进行定位并将其放置在想要的位置。默认情况下,使用 popover 属性或 dialog 标签创建的弹出框(或模态框)会居中显示在页面上。
这是因为默认情况下,具有 popover 属性的元素的定位方式为 fixed,且内边距(inset)为 0,这会将弹出框居中显示,并设置自动外边距。
注意:inset 是元素页面位置的 top、bottom、left 和 right 的简写形式。如果希望元素在所有边距上具有相同尺寸,可以使用 inset。
如果你不希望弹出框居中显示,可以首先将具有 popover 属性的元素的定位方式设置为 absolute,以将其从页面流中隔离出来:
#navbar-menu {
position: absolute;
}然后可以将外边距设为 0,并将定位(inset)设为 unset:
#navbar-menu {
position: absolute;
margin: 0;
inset: unset;
}完成这些操作后,就可以将 popover 元素放置在页面的任意一侧。
如何使用 position-anchor 属性
注意:CSS 锚点定位是一项较新的功能。在生产环境中依赖它之前,请先检查浏览器兼容性,并为尚不支持该功能的浏览器提供回退方案。
下一步是将元素定位在触发它的元素附近。为此,我们可以使用 CSS 的 position-anchor 属性。
CSS 的 position-anchor 属性指定了一个默认的锚点元素,绝对定位或固定定位的元素会“绑定”或吸附到该锚点元素上。它允许你仅使用 CSS 就将一个浮动目标(例如此处的弹出框)链接到页面上的另一个元素。
在我们的示例中,我们有一个菜单列表图标(或汉堡菜单),它会作为弹出框打开和关闭导航元素。我们希望导航栏菜单能够连接到/靠近打开它的菜单列表。
因此,你可以向菜单图标添加 anchor-name 属性。名称必须以双连字符(--)作为前缀(名称可以是任意你想要的名称)。
#nav-btn {
anchor-name: --nav;
}position-anchor 属性允许你将一个元素连接到由锚点名称标识的另一个元素。一旦连接,你可以使用 anchor() 函数将元素相对于该锚点进行定位,例如将其与锚点的顶部、底部或中心对齐。
#navbar-menu {
position: absolute;
margin: 0;
inset: auto;
position-anchor: --nav;
top: anchor(bottom);
right: anchor(right);
}将你为锚点指定的锚点名称作为 position-anchor 的值,即可将导航元素(在示例中为 #navbar-menu)在页面上围绕锚点名称(在示例中为 #nav-btn)进行定位。
然后,anchor() 函数会将导航元素的顶部对齐到锚点的底部,并将右侧对齐到菜单的右侧。
如何使用 dialog 标签创建模态框
通过在按钮上使用 command 和 commandfor 属性,你可以声明性地控制 <dialog>。例如,command="show-modal" 会以模态方式打开它,而 command="close" 会将其关闭。
弹出模态框和普通弹出框有所不同:当弹出框处于活动状态时,你仍然可以与页面进行交互。但使用模态框或弹出模态框时,你无法与页面进行交互——模态框会锁定屏幕,直到被忽略或确认。
当 <dialog> 以模态方式打开时,它会被放置在浏览器的顶层,使其显示在页面其余内容之上。页面其余部分将变为不可交互状态,这意味着模态框打开时用户无法与之交互。浏览器还会在模态框后面创建一个 ::backdrop,你可以对其进行样式设置以提供视觉覆盖层。
以下是示例:
<button command="show-modal" commandfor="contact-dialog">
open modal
</button>
<dialog id="contact-dialog">
<button command="close" commandfor="contact-dialog" aria-label="close modal">
close modal
</button>
<!--modal contents goes here-->
</dialog>可以通过 command 属性控制模态框的显示和关闭,通过 commandfor 属性引用目标模态框的 ID。
command 属性可以接受以下选项:
- show-modal:用于触发模态框或对话框打开的元素。
- close:传递给元素后,当模态框处于打开状态时将其关闭。
在代码片段示例中,可以看到带有 "open modal" 标签的按钮具有 command 属性的 show-modal 选项,以及通过 ID 引用 dialog 元素的 commandfor 属性。
关闭按钮使用 command 属性的 close 选项在点击时关闭模态框,同时使用 commandfor 指示点击关闭按钮时应关闭哪个模态框。
下面是一个使用 command 和 commandfor 属性创建的模态框演示:
在上方的 Code Pen 中点击 "click to pop a modal!" 按钮,即可看到仅通过在 dialog 标签上使用 command 和 commandfor 属性即可创建的模态框。
使用 showModal() 创建模态框
command API 可能在较旧的浏览器中不受支持。作为替代方案,可以使用 JavaScript 的 showModal() 方法,因为相比 command 和 commandfor,它在更多浏览器中广泛可用。
const btn = document.querySelector("button");
const dialog = document.querySelector("dialog");
btn.addEventListener("click", () => {
dialog.showModal();
});使用 show() 方法打开非模态对话框
有时你可能需要一个类似模态框的元素,但保持交互性且不阻止其他页面内容的显示,这与 command 和 commandfor 属性的行为不同。为此,可以使用 JavaScript 在 dialog 上调用 show() 方法。
const btn = document.querySelector("button");
const dialog = document.querySelector("dialog");
btn.addEventListener("click", () => {
dialog.show();
});上述代码片段在对话框打开时保持页面其余部分的交互性。这与使用 showModal() 或 command="show-modal" 打开对话框不同,后者会将对话框作为模态框打开,并使文档其余部分变为非交互状态。
注意:请记住,show() 不被视为模态框,而更像是一个不会阻止与页面其他部分交互的对话框元素。
需要注意的陷阱
当你使用 element popover、command 和 commandfor 属性或 show() 方法时,有一些“陷阱”需要特别注意。关注这些内容将有助于你遵循最佳实践。
避免使用 Flex 或 Grid
在具有 popover 属性的元素和模态框元素上直接应用 Flex 或 Grid 等布局相关样式是强烈不推荐的。
默认情况下,具有 popover 属性、command 和 commandfor 按钮属性以及 show() 的元素的 display 为 none。这意味着弹出窗口或模态框在屏幕上是隐藏的。
当在带有 popover 属性的元素或模态元素上直接添加 flex 或 grid 布局时,您实际上是在重写模态框或弹出框元素的默认行为,这会导致它们始终在屏幕上可见。这意味着您将无法从屏幕上隐藏模态框。
在 CodePen 演示中查看以下示例:
#navbar-menu {
display: grid;
/* 其他样式定义 */
}从示例中可以看出,即使我们点击菜单按钮再次尝试隐藏导航元素,该元素仍然始终可见。
#### 建议方案
在这种情况下,您可以使用带有 popover 属性元素上的 popover-open 伪类,或者在 dialog 元素上使用 dialog[open]。
伪类会根据元素与页面交互时的状态来应用样式。在这种情况下,我们希望在弹出框或模态框处于打开状态时,为其应用不同的布局。
示例:
/* 带有 popover 属性的元素 */
#navbar-menu:popover-open {
display: grid;
gap: 2rem;
}
/* 使用 dialog 元素 */
dialog[open] {
display: grid;
gap: 2rem;
}背景滚动
使用 <dialog> 元素时需要考虑的另一个问题是背景滚动。根据浏览器和平台的不同,当模态对话框打开时,底层页面可能仍然可以滚动。如果您希望阻止这种行为,可以在对话框打开时显式禁用滚动。
查看这个 CodePen 示例:
当您点击按钮显示模态框时,您仍然可以看到页面上的滚动条,并且可以滚动页面。
为了解决这个问题,我们可以使用 has 伪类。has 可以根据子元素的状态选择父元素。在根元素或 HTML 元素上,您可以检查是否存在打开的模态框。如果存在,可以将根元素或 HTML 元素的 overflow 设置为 hidden。
例如:
html:has(dialog[open]) {
overflow: hidden;
}当模态框打开时,这将隐藏页面上的滚动条。请注意,您也可以使用任何父元素配合 has 伪类来包裹 dialog 标签,不一定要使用根元素或 HTML 元素。
::backdrop 伪类
如果您想在弹出框元素或模态元素上使用自定义的遮罩颜色,可以使用 ::backdrop 伪类来自定义模态遮罩的颜色外观。
dialog::backdrop {
background: rgba(43, 50, 200, 0.4);
}这将应用定义的 RGB 颜色和 0.4 的透明度到遮罩层上,使遮罩层具有一定的透明效果。
最后想法
希望您从本文中获得了一些新的知识,并且这些技巧能帮助您在项目中开始应用。
我是开发者 Prototype
如果您读到这里,请感谢作者以表达您的支持。说声谢谢
免费学习编程。freeCodeCamp 的开源课程已帮助超过 40,000 人成为开发者。立即开始
ADVERTISEMENT