开放界面计划旨在让开发者更轻松地打造出色的用户体验。为此,我们正尝试解决开发者面临的更棘手的问题。我们可以通过提供更好的平台内置 API 和组件来实现这一点。
其中一个问题领域是弹出式窗口,在 Open UI 中称为“Popover”。
长期以来,弹出式窗口的声誉一直褒贬不一。这在一定程度上是因为它们在构建和部署方面的方式。虽然这种模式不易于构建,但如果使用得当,可以引导用户访问特定内容或让用户了解您网站上的内容,从而带来巨大价值。
在构建弹出式窗口时,通常需要考虑两个主要问题:
- 如何确保它放置在适当的位置,并显示在其余内容上方。
- 如何使其具有无障碍功能(支持键盘操作、可聚焦等)。
内置的 Popover API 有多种目标,但总体目标都是让开发者能够轻松构建这种模式。这些目标中值得注意的有:
- 轻松地将元素及其后代显示在文档的其余部分之上。
- 让 AI 触手可及。
- 不需要 JavaScript 即可实现最常见的行为(轻触关闭、单例、堆叠等)。
您可以在 OpenUI 网站上查看有关弹出式窗口的完整规范。
浏览器兼容性
现在,您可以在哪些位置使用内置的 Popover API?截至撰写本文时,Chrome Canary 版支持此功能,但需要启用“实验性 Web 平台功能”标志。
如需启用该标志,请打开 Chrome Canary 版并访问 chrome://flags。然后,启用“实验性 Web 平台功能”标志。
我们还为希望在生产环境中测试此功能的开发者提供了源试用。
最后,我们正在为该 API 开发 polyfill。请务必查看 github.com/oddbird/popup-polyfill 中的代码库。
您可以使用以下命令检查弹出式窗口支持:
const supported = HTMLElement.prototype.hasOwnProperty("popover");
当前解决方案
目前,您可以通过哪些方式优先推广自己的内容?如果您的浏览器支持,您可以使用 HTML 对话框元素。您需要以“模态”形式使用它。此网站需要启用 JavaScript 才能使用。
Dialog.showModal();
以下是一些无障碍方面的注意事项。建议使用 a11y-dialog,例如,如果需要满足使用低于 15.4 版 Safari 的用户的需求。
您还可以使用许多基于弹出式窗口、提醒或提示的库。其中许多的运作方式都类似。
- 将一些容器附加到正文中以显示弹出式窗口。
- 设置样式,使其位于所有其他内容之上。
- 创建一个元素并将其附加到容器以显示 popover。
- 通过从 DOM 中移除弹出式信息框元素来隐藏它。
这需要额外的依赖项,并且开发者需要做出更多决定。此外,您还需要进行研究,找到能满足您所有需求的方案。Popover API 旨在满足多种场景的需求,包括工具提示。这样做的目的是涵盖所有常见场景,让开发者无需再做出其他决定,从而专注于打造自己的体验。
您的第一个弹出式窗口
您只需执行这些操作。
<div id="my-first-popover" popover>Popover Content!</div>
<button popovertoggletarget="my-first-popover">Toggle Popover</button>
但这里发生了什么?
- 您无需将弹出框元素放入容器或其他任何位置,它默认处于隐藏状态。
- 您无需编写任何 JavaScript 即可显示该按钮。这由
popovertoggletarget属性处理。 - 当它出现时,会被提升到顶层。这意味着它会提升到视口中
document的上方。您无需管理z-index,也不必担心弹出式窗口在 DOM 中的位置。它可能深层嵌套在 DOM 中,并具有剪切祖先。您还可以通过开发者工具查看哪些元素目前位于顶层。如需详细了解顶层,请参阅这篇文章。

- 您可直接使用“轻触即关”。也就是说,您可以通过关闭信号(例如点击弹出框外部、使用键盘导航到其他元素或按 Esc 键)关闭弹出框。再次打开该应用,然后试用一下!
您还可以通过弹出式窗口获得哪些信息?我们再举一个例子。请观看此演示,其中包含网页上的一些内容。
该悬浮操作按钮具有固定的位置和较高的 z-index。
.fab {
position: fixed;
z-index: 99999;
}
弹出式窗口内容嵌套在 DOM 中,但当您打开弹出式窗口时,它会提升到该固定位置元素之上。您无需设置任何样式。
您可能还会注意到,弹出式窗口现在具有 ::backdrop 伪元素。顶层中的所有元素都会获得可设置样式的 ::backdrop 伪元素。此示例使用降低了 alpha 值的背景颜色和背景滤镜来设置 ::backdrop 的样式,从而模糊处理底层内容。
设置弹出式窗口的样式
接下来,我们来了解如何设置弹出式窗口的样式。默认情况下,弹出式窗口具有固定位置和一些已应用的内边距。它还具有 display: none。您可以替换此设置以显示弹出式界面。但这样不会将其提升到顶层。
[popover] { display: block; }
无论您以何种方式宣传弹出式窗口,一旦将弹出式窗口宣传到顶层,您可能需要对其进行布局或定位。您无法定位到顶层并执行以下操作
:open {
display: grid;
place-items: center;
}
默认情况下,弹出式窗口将使用 margin: auto 在视口的中心进行布局。不过,在某些情况下,您可能需要明确指定位置。例如:
[popover] {
top: 50%;
left: 50%;
translate: -50%;
}
如果您想使用 CSS 网格或 flexbox 在弹出式窗口内布局内容,最好将内容封装在元素中。否则,您需要声明一个单独的规则,以便在弹出式窗口位于顶层时更改 display。如果默认设置此属性,则会默认显示该元素,从而替换 display: none。
[popover]:open {
display: flex;
}
如果您试用了该演示,会发现弹出式信息框现在会过渡显示和隐藏。您可以使用 :open 伪选择器使弹出式窗口显示和隐藏。:open 伪选择器用于匹配正在显示(因此位于顶层)的弹出式提示框。
此示例使用自定义属性来驱动过渡。您还可以为弹出式窗口的 ::backdrop 应用过渡效果。
[popover] {
--hide: 1;
transition: transform 0.2s;
transform: translateY(calc(var(--hide) * -100vh))
scale(calc(1 - var(--hide)));
}
[popover]::backdrop {
transition: opacity 0.2s;
opacity: calc(1 - var(--hide, 1));
}
[popover]:open::backdrop {
--hide: 0;
}
一个技巧是,将过渡效果和动画效果归入运动媒体查询中。这也有助于保持您的跑步节奏。这是因为您无法通过自定义属性在 popover 和 ::backdrop 之间共享值。
@media(prefers-reduced-motion: no-preference) {
[popover] { transition: transform 0.2s; }
[popover]::backdrop { transition: opacity 0.2s; }
}
到目前为止,您已经了解了如何使用 popovertoggletarget 显示弹出式窗口。为了关闭它,我们使用了“轻触即关”。不过,您还可以使用 popovershowtarget 和 popoverhidetarget 属性。我们来向弹出式窗口添加一个按钮,用于隐藏该窗口,并将切换按钮更改为使用 popovershowtarget。
<div id="code-popover" popover>
<button popoverhidetarget="code-popover">Hide Code</button>
</div>
<button popovershowtarget="code-popover">Reveal Code</button>
如前所述,Popover API 不仅涵盖我们之前所说的弹出式窗口。您可以针对所有类型的场景(例如通知、菜单、提示等)进行构建。
其中一些场景需要不同的互动模式。悬停等互动。我们曾尝试使用 popoverhovertarget 属性,但目前尚未实现。
<div popoverhovertarget="hover-popover">Hover for Code</div>
其理念是,当您将鼠标悬停在某个元素上时,系统会显示目标。此行为可通过 CSS 属性进行配置。这些 CSS 属性将定义悬停在元素上和离开元素时弹出式窗口的响应时间窗口。实验中使用的默认行为是在明确点击 :hover 次 0.5s 后显示弹出式窗口。然后,它需要通过轻触关闭或打开另一个弹出式窗口来关闭(稍后会详细介绍)。这是因为弹出式窗口的隐藏时长设置为 Infinity。
在此期间,您可以使用 JavaScript 来填充该功能。
let hoverTimer;
const HOVER_TRIGGERS = document.querySelectorAll("[popoverhovertarget]");
const tearDown = () => {
if (hoverTimer) clearTimeout(hoverTimer);
};
HOVER_TRIGGERS.forEach((trigger) => {
const popover = document.querySelector(
`#${trigger.getAttribute("popoverhovertarget")}`
);
trigger.addEventListener("pointerenter", () => {
hoverTimer = setTimeout(() => {
if (!popover.matches(":open")) popover.showPopover();
}, 500);
trigger.addEventListener("pointerleave", tearDown);
});
});
设置明确的悬停窗口的好处在于,它可以确保用户的操作是有意为之(例如,用户将指针移到目标上)。除非用户有意显示该弹出式窗口,否则我们不希望显示它。
不妨试用此演示,其中您可以将窗口设置为 0.5s,然后将鼠标悬停在目标上。
在探讨一些常见的使用场景和示例之前,我们先来了解一些事项。
弹出式窗口的类型
我们已介绍过非 JavaScript 互动行为。但从整体来看,Popover 行为又如何呢?如果您不想使用“轻触即关闭”功能,该怎么办?或者您想将单例模式应用于弹出式窗口?
借助 Popover API,您可以指定三种行为不同的弹出式窗口。
[popover=auto]/[popover]:
- 嵌套支持。这不仅意味着嵌套在 DOM 中。祖代弹出式窗口的定义是:
- 按 DOM 位置(子级)相关联。
- 通过子元素(例如
popovertoggletarget、popovershowtarget等)上的触发属性相关联。 - 通过
anchor属性相关联(正在开发的 CSS Anchoring API)。
- 轻关闭。
- 打开时会关闭其他非祖先 popover 的 popover。不妨试用下面的演示,了解如何使用祖先弹出式窗口进行嵌套。了解将部分
popoverhidetarget/popovershowtarget实例更改为popovertoggletarget会带来哪些变化。 - 如果关闭一个通知,系统会关闭所有通知;但如果关闭堆栈中的一个通知,系统只会关闭堆栈中位于该通知上方的通知。
[popover=manual]:
- 不关闭其他弹出式窗口。
- 无灯光关闭。
- 需要通过触发元素或 JavaScript 明确关闭。
JavaScript API
如果您需要更好地控制弹出式窗口,可以使用 JavaScript 来实现。您将获得 showPopover 和 hidePopover 方法。您还可以监听 popovershow 和 popoverhide 事件:
显示弹出式窗口
js
popoverElement.showPopover()
隐藏弹出式窗口:
popoverElement.hidePopover()
监听正在显示的弹出式窗口:
popoverElement.addEventListener('popovershow', doSomethingWhenPopoverShows)
监听正在显示的 popover 并取消其显示:
popoverElement.addEventListener('popovershow',event => {
event.preventDefault();
console.warn(‘We blocked a popover from being shown’);
})
监听弹出式窗口是否被隐藏:
popoverElement.addEventListener('popoverhide', doSomethingWhenPopoverHides)
您无法取消正在隐藏的弹出式窗口:
popoverElement.addEventListener('popoverhide',event => {
event.preventDefault();
console.warn("You aren't allowed to cancel the hiding of a popover");
})
检查弹出式窗口是否位于顶层:
popoverElement.matches(':open')
这可为一些不太常见的场景提供额外的动力。例如,在用户闲置一段时间后显示弹出式提示框。
此演示包含带有可听到的爆破音的弹出式窗口,因此我们需要使用 JavaScript 来播放音频。点击时,我们会隐藏弹出式窗口,播放音频,然后再次显示弹出式窗口。
无障碍
Popover API 的设计理念是将无障碍功能放在首位。无障碍功能映射可根据需要将弹出式窗口与其触发元素相关联。这意味着,如果您使用 popovertoggletarget 等触发属性,则无需声明 aria-* 属性(例如 aria-haspopup)。
对于焦点管理,您可以使用 autofocus 属性将焦点移至弹出框内的元素。这与对话框相同,但返回焦点时有所不同,这是因为轻触即可关闭。在大多数情况下,关闭弹出式窗口会将焦点返回到之前获得焦点的元素。但如果点击的元素可以获得焦点,则在轻触关闭时,焦点会移至该元素。请参阅说明中的焦点管理部分。
您需要打开此演示的“全屏版本”才能看到它的运行效果。
在此演示中,获得焦点的元素会显示绿色轮廓。尝试使用键盘在界面中切换。请注意,当 popover 关闭时,焦点会返回到哪里。您可能还会注意到,如果您按 Tab 键四处移动,弹出式窗口会关闭。这是设计所致。尽管弹出式窗口具有焦点管理功能,但它们不会捕获焦点。当焦点移出弹出式窗口时,键盘导航会识别到关闭信号。
锚定(开发中)
对于弹出式窗口,需要考虑的一个棘手模式是将元素锚定到其触发器。例如,如果工具提示设置为显示在其触发器上方,但文档被滚动。该提示可能会被视口截断。目前有 JavaScript 产品可用于处理此问题,例如“Floating UI”。它们会重新定位提示,以防止这种情况发生,并依赖于所需的定位顺序。
不过,我们希望您能够通过样式来定义这一点。我们正在开发一个与 Popover API 并行的配套 API 来解决此问题。借助“CSS Anchor Positioning”API,您可以将元素绑定到其他元素,并且会以重新定位元素的方式来确保元素不会被视口截断。
此演示使用处于当前状态的 Anchoring API。船的位置会根据锚点在视口中的位置而变化。
以下是使此演示正常运行的 CSS 代码段。无需 JavaScript。
.anchor {
--anchor-name: --anchor;
}
.anchored {
position: absolute;
position-fallback: --compass;
}
@position-fallback --compass {
@try {
bottom: anchor(--anchor top);
left: anchor(--anchor right);
}
@try {
top: anchor(--anchor bottom);
left: anchor(--anchor right);
}
}
您可以点击此处查看规范。此外,还将为此 API 提供 Polyfill。
示例
现在,您已经了解了弹出式窗口的功能和使用方法,接下来我们来深入了解一些示例。
通知
此演示显示了“复制到剪贴板”通知。
- 使用
[popover=manual]。 - 在操作时显示包含
showPopover的弹出式窗口。 - 在
2000ms超时后,使用hidePopover将其隐藏。
消息框
此演示使用顶层来显示消息框样式的通知。
- 一个类型为
manual的弹出式窗口充当容器。 - 新通知会附加到弹出式窗口,并显示该弹出式窗口。
- 它们会在点击时通过 Web 动画 API 移除,并从 DOM 中移除。
- 如果没有要显示的 Toast,则隐藏弹出式窗口。
嵌套菜单
此演示展示了嵌套导航菜单的运作方式。
- 使用
[popover=auto],因为它支持嵌套的弹出式信息框。 - 在每个下拉菜单的第一个链接上使用
autofocus,以便通过键盘进行导航。 - 这非常适合使用 CSS Anchoring API。不过,在此演示中,您可以使用少量 JavaScript 通过自定义属性更新位置。
const ANCHOR = (anchor, anchored) => () => {
const { top, bottom, left, right } = anchor.getBoundingClientRect();
anchored.style.setProperty("--top", top);
anchored.style.setProperty("--right", right);
anchored.style.setProperty("--bottom", bottom);
anchored.style.setProperty("--left", left);
};
PRODUCTS_MENU.addEventListener("popovershow", ANCHOR(PRODUCT_TARGET, PRODUCTS_MENU));
请注意,由于此演示使用 autofocus,因此需要以“全屏视图”打开,才能使用键盘进行导航。
媒体弹出式窗口
此演示展示了如何弹出媒体。
- 使用
[popover=auto]进行轻触关闭。 - JavaScript 会监听视频的
play事件,并弹出视频。 - 弹出式窗口的
popoverhide事件会暂停视频。
Wiki 风格的弹出式提示框
此演示展示了如何创建包含媒体的内嵌内容提示。
- 使用
[popover=auto]。显示一个会隐藏其他,因为它们不是祖先。 - 使用 JavaScript 在
pointerenter上显示。 - CSS Anchoring API 的另一个完美候选对象。
抽屉式导航栏
此演示使用弹出式窗口创建抽屉式导航栏。
- 使用
[popover=auto]进行轻触关闭。 - 使用
autofocus将焦点放在第一个导航项上。
管理背景
此演示展示了如何管理多个弹出框的背景,其中您只希望一个 ::backdrop 可见。
- 使用 JavaScript 维护可见的弹出式窗口的列表。
- 将类名称应用于顶层中的最低弹出式窗口。
自定义光标弹出式窗口
此演示展示了如何使用 popover 将 canvas 提升到顶层,并使用它来显示自定义光标。
- 使用
showPopover和[popover=manual]将canvas提升到顶层。 - 当其他弹出式窗口打开时,隐藏并显示
canvas弹出式窗口,以确保它位于顶部。
操作表弹出式窗口
此演示展示了如何将弹出式窗口用作操作表。
- 默认显示弹出式窗口,覆盖
display。 - 使用弹出式窗口触发器打开操作表单。
- 当弹出式窗口显示时,它会被提升到顶层并平移到视图中。
- 可以使用轻触关闭手势返回。
键盘已激活的弹出式窗口
此演示展示了如何将弹出式窗口用于命令面板样式的界面。
- 使用 cmd + j 显示弹出式窗口。
- 使用
autofocus聚焦input。 - 组合框是位于主输入下方的第二个
popover。 - 如果不存在下拉菜单,则轻触关闭会关闭调色板。
- 锚定 API 的另一个候选对象
定时浮层
此演示会在 4 秒后显示闲置弹出式窗口。一种界面模式,通常用于包含用户安全信息的应用,以显示退出模式。
- 使用 JavaScript 在一段时间不活动后显示弹出式提示框。
- 在显示弹出式窗口时,重置计时器。
屏保
与之前的演示版类似,您可以在网站上添加一些奇思妙想,并添加屏保。
- 使用 JavaScript 在一段时间不活动后显示弹出式提示框。
- 轻触关闭即可隐藏并重置计时器。
光标跟随
此演示展示了如何让弹出式窗口跟随输入光标。
- 根据选择、按键事件或特殊字符输入显示弹出式窗口。
- 使用 JavaScript 通过范围限定的自定义属性更新弹出式窗口位置。
- 此模式需要仔细考虑所显示的内容和无障碍功能。
- 在文本编辑界面和可添加标记的应用中,此功能非常常见。
悬浮操作按钮菜单
此演示展示了如何使用 popover 实现不含 JavaScript 的悬浮操作按钮菜单。
- 使用
showPopover方法推广manual类型弹出式窗口。这是主按钮。 - 菜单是另一个弹出式界面,也是主按钮的目标。
- 使用
popovertoggletarget打开菜单。 - 使用
autofocus将焦点置于显示中的第一个菜单项上。 - 轻触关闭菜单。
- 图标扭曲使用
:has()。如需详细了解:has(),请参阅这篇文章。
大功告成!
以上是有关弹出式窗口的简介,该功能即将推出,是开放式界面计划的一部分。如果合理使用,它将成为 Web 平台的绝佳补充。
请务必查看开放界面。随着 API 的发展,弹出式信息说明也会随之更新。以下是所有演示的集合。
感谢您的光临!