Open UI 计划的目标是让开发者更轻松地提供出色的用户体验。为此,我们正尝试解决开发者面临的更多问题模式。我们可以通过提供更出色的平台内置 API 和组件来实现这一点。
弹出式窗口就属于这类问题,在“打开界面”中称为“弹出式窗口”。
长久以来,Popover 的声誉相当分化。这在一定程度上取决于它们的构建和部署方式。这种模式并非易事,但它们能带来很多价值,比如引导用户查看某些内容,或让他们知道您网站上的内容,尤其是在巧妙使用方式时。
构建弹出式窗口时,通常有两个主要问题:
- 如何确保其在适当位置展示在其他内容之上。
- 如何使其具有无障碍功能(适合键盘操作、可聚焦等)。
内置的 Popover API 有各种目标,所有这些目标都具有相同的总体目标,那就是让开发者能够轻松构建此模式。值得注意的这些目标包括:
- 可让您轻松地在文档其余部分的上方显示元素及其后代元素。
- 让网站可供访问。
- 无需 JavaScript 即可实现大多数常见行为(轻度关闭、单例、堆叠等)。
您可以在 OpenUI 网站上查看弹出式窗口的完整规范。
浏览器兼容性
现在您可以在哪里使用内置的 Popover API?Chrome Canary 版中的“实验性网络平台功能”支持此功能标记。
若要启用该标记,请打开 Chrome Canary 并访问 chrome://flags
。然后启用“实验性 Web 平台功能”标志。
如果开发者希望在生产环境中测试此功能,还可以通过源试用。
最后,该 API 还有正在开发的 polyfill。请务必查看 github.com/oddbird/popup-polyfill 代码库。
您可通过以下途径查看是否支持弹出式广告:
const supported = HTMLElement.prototype.hasOwnProperty("popover");
当前解决方案
您目前可以采取哪些措施来宣传自己的内容?如果您的浏览器支持此类元素,您可以使用 HTML 对话框元素。您需要在“模态”中使用表单。这需要 JavaScript 才能使用。
Dialog.showModal();
以下是一些无障碍功能注意事项。例如,如果想迎合版本低于 15.4 的 Safari 用户,建议使用 a11y-dialog。
您也可以使用众多基于弹出式窗口、提醒或提示的库。其中许多功能的工作原理大致相同。
- 向正文附加一些容器,以显示弹出式窗口。
- 设置样式,使其位于其他所有内容之上。
- 创建元素并将其附加到容器,以显示弹出式窗口。
- 您可以通过从 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 对该功能执行 polyfill 操作。
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 API 允许您指定三种在行为上不同的弹出窗口。
[popover=auto]/[popover]
:
- 嵌套支持。这不仅意味着嵌套在 DOM 中。古代弹出窗口的定义是:
<ph type="x-smartling-placeholder">
- </ph>
- 与 DOM 位置(子节点)相关。
- 来传递子元素上的属性,例如
popovertoggletarget
、popovershowtarget
等。 - 与
anchor
属性相关(正在开发 CSS Anchoring API)。
- 轻微关闭。
- 打开操作会关闭非祖先弹出式窗口的其他弹出式窗口。试用下面的演示,重点了解使用古代弹出式窗口进行嵌套的工作原理。了解将某些
popoverhidetarget
/popovershowtarget
实例更改为popovertoggletarget
会带来怎样的变化。 - 轻度关闭其中一个对象会关闭所有对象,但关闭堆栈中的另一个对象只会关闭堆栈中在其之上的内容。
[popover=manual]
:
- 不会关闭其他弹出式窗口。
- 无法轻易关闭。
- 需要通过触发器元素或 JavaScript 明确关闭。
JavaScript API
如果您需要对弹出式窗口进行更多控制,可以使用 JavaScript 进行处理。您会获得 showPopover
和 hidePopover
方法。您还需要监听 popovershow
和 popoverhide
事件:
显示弹出式窗口
js
popoverElement.showPopover()
隐藏弹出式窗口:
popoverElement.hidePopover()
监听出现的弹出式窗口:
popoverElement.addEventListener('popovershow', doSomethingWhenPopoverShows)
监听显示的弹出式窗口并取消显示:
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 的首要考虑因素。无障碍映射会根据需要将弹出式窗口与其触发器元素相关联。这意味着您无需声明 aria-*
属性(如 aria-haspopup
),假设您使用 popovertoggletarget
等触发属性之一。
对于焦点管理,您可以使用 autofocus 属性将焦点移到弹出式窗口内的某个元素上。这与对话框的相同,但不同之处在于返回焦点时,这是因为轻型关闭造成的。在大多数情况下,关闭弹出式窗口会使焦点返回到先前聚焦的元素。但是,如果被点击的元素可以获得焦点,那么在浅色关闭时,焦点会移动到所点击的元素。请查看解说中关于焦点管理的部分。
您将需要打开“全屏版本”看看能否正常运行
在此演示中,获得聚焦的元素会显示绿色轮廓。请尝试使用键盘在界面中按 Tab 键。注意关闭弹出式窗口后焦点会返回的位置。您可能还会注意到,如果按 Tab 键,弹出式窗口会关闭。这是由设计来决定的。虽然弹出式窗口具有焦点管理功能,但它们不会限制焦点。当焦点移出弹出式窗口时,键盘导航会识别关闭信号。
锚定(开发中)
对于弹出式窗口,一个需要迎合的棘手模式是将元素锚定到其触发器。例如,如果提示被设置为显示在其触发器上方,但文档被滚动了。该提示可能会被视口截断。当前有 JavaScript 服务可以解决这个问题,例如“浮动界面”。它们会调整提示的位置,以阻止此操作发生,并依赖所需的排名顺序。
不过,我们希望您能够通过自己的样式来定义这一点。除了 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 中移除这些元素。
- 如果没有可显示的消息框,则隐藏弹出式窗口。
嵌套菜单
此演示展示了嵌套导航菜单的运作方式。
- 使用
[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
弹出式窗口,以确保其位于顶部。
Actionsheet 弹出式窗口
此演示展示了如何将弹出式窗口用作操作表。
- 默认显示弹出式窗口并替换
display
。 - Actionsheet 使用弹出式窗口触发器打开。
- 弹出式窗口显示后,会提升到顶层并转换为视图。
- 可使用轻度关闭功能返回。
键盘激活的弹出式窗口
此演示展示了如何将弹出式窗口用于命令面板样式界面。
- 使用 cmd + j 显示弹出式窗口。
- 使用
autofocus
聚焦于input
。 - 组合框是位于主输入下方的第二个
popover
。 - 如果下拉菜单不存在,轻度关闭会关闭调色板。
- Anchoring API 的另一个候选版本
定时弹出式窗口
此演示展示了 4 秒后出现不活跃的弹出式窗口。一种界面模式,通常在应用中使用,其中包含用户的安全信息以显示退出模态。
- 使用 JavaScript 在一段时间不活动后显示弹出式窗口。
- 在弹出式窗口节目中,重置计时器。
屏保
与上一个演示类似,您可以为自己的网站添加少许新奇内容,并添加屏保。
- 使用 JavaScript 在一段时间不活动后显示弹出式窗口。
- 轻微关闭以隐藏并重置计时器。
脱字符跟随
此演示展示了如何让弹出式窗口跟随输入插入符号移动。
- 根据所选内容、按键事件或特殊字符输入显示弹出式窗口。
- 使用 JavaScript 通过限定范围的自定义属性更新弹出式窗口位置。
- 这种模式需要仔细考虑要展示的内容和无障碍功能。
- 它通常出现在文本编辑界面和可添加标签的应用中。
悬浮操作按钮菜单
此演示展示了如何在不使用 JavaScript 的情况下使用弹出式窗口实现悬浮操作按钮菜单。
- 使用
showPopover
方法提升manual
类型的弹出式窗口。这是主按钮。 - 该菜单是另一个弹出窗口,也是主按钮的目标。
- 菜单已按
popovertoggletarget
打开。 - 使用
autofocus
将焦点移至第一个菜单项。 - 轻微关闭会关闭菜单。
- 图标扭曲使用
:has()
。如需详细了解:has()
,请参阅这篇文章。
大功告成!
以上是对弹出窗口的介绍,即将作为开放界面计划的一部分推出。如果使用得当,它将成为网络平台的绝佳补充。
请务必查看打开界面。弹出式窗口解释器会随着 API 的发展而与时更新。您还可以点击此处查看所有演示。
感谢你的“爆红”!
摄影:Madison Oren,来源:Unspin