使用 Popover API

**Popover API** 为开发人员提供了一种标准化、一致且灵活的机制,用于在其他页面内容之上显示弹出框内容。弹出框内容可以通过 HTML 属性声明式控制,也可以通过 JavaScript 控制。本文提供了有关如何使用其所有功能的详细指南。

创建声明式弹出框

最简单的形式是,通过在要包含弹出框内容的元素上添加 popover 属性来创建弹出框。还需要一个 id 来将弹出框与其控件关联。

html
<div id="mypopover" popover>Popover content</div>

**注意:**设置 popover 属性而不带值等效于设置 popover="auto"

添加此属性会导致元素在页面加载时隐藏,方法是在其上设置 display: none。要显示/隐藏弹出框,需要添加一些控制按钮。可以通过给 <button>(或 type="button"<input>)设置 popovertarget 属性将其设置为弹出框控制按钮,该属性的值应为要控制的弹出框的 ID

html
<button popovertarget="mypopover">Toggle the popover</button>
<div id="mypopover" popover>Popover content</div>

默认行为是按钮为切换按钮——反复按下它将在显示和隐藏之间切换弹出框。

如果要更改此行为,可以使用 popovertargetaction 属性——它取值为 "hide""show""toggle"。例如,要创建单独的显示和隐藏按钮,可以执行以下操作

html
<button popovertarget="mypopover" popovertargetaction="show">
  Show popover
</button>
<button popovertarget="mypopover" popovertargetaction="hide">
  Hide popover
</button>
<div id="mypopover" popover>Popover content</div>

您可以在我们的 基本声明式弹出框示例 (源代码) 中查看先前代码片段的渲染方式。

**注意:**如果省略了 popovertargetaction 属性,则 "toggle" 是控制按钮将执行的默认操作。

显示弹出框时,会从中删除 display: none 并将其置于 顶层,因此它将位于所有其他页面内容之上。

auto 状态和“轻触关闭”

当弹出框元素设置为 popoverpopover="auto"(如上所示)时,据说它具有 **auto 状态**。关于 auto 状态需要注意的两个重要行为是

  • 弹出框可以“轻触关闭”——这意味着可以通过点击弹出框外部来隐藏它。
  • 也可以使用浏览器特定的机制(例如按 Esc 键)关闭弹出框。
  • 通常,一次只能显示一个弹出框——当一个弹出框已显示时显示第二个弹出框将隐藏第一个弹出框。此规则的例外情况是嵌套的 auto 弹出框。有关更多详细信息,请参阅 嵌套弹出框 部分。

**注意:**popover="auto" 弹出框也会被文档中其他元素上的成功 HTMLDialogElement.showModal()Element.requestFullscreen() 调用关闭。但是请记住,在已显示的弹出框上调用这些方法会导致失败,因为这些行为在已显示的弹出框上没有意义。但是,您可以在当前未显示的具有 popover 属性的元素上调用它们。

当您只想一次显示一个弹出框时,auto 状态非常有用。也许您有多个要显示的教学 UI 消息,但不想让显示变得杂乱和混乱,或者您正在显示状态消息,其中新状态会覆盖任何先前状态。

您可以在我们的 多个 auto 弹出框示例 (源代码) 中查看上述行为。在显示弹出框后尝试轻触关闭它们,并查看同时尝试显示两者时会发生什么。

使用手动弹出框状态

auto 状态的替代方案是 **手动状态**,通过在弹出框元素上设置 popover="manual" 来实现

html
<div id="mypopover" popover="manual">Popover content</div>

在此状态下

  • 弹出框无法“轻触关闭”,尽管声明式显示/隐藏/切换按钮(如前所述)仍将有效。
  • 可以同时显示多个独立的弹出框。

您可以在我们的 多个手动弹出框示例 (源代码) 中查看此行为。

通过 JavaScript 显示弹出框

您还可以使用 JavaScript API 控制弹出框。

HTMLElement.popover 属性可用于获取或设置 popover 属性。这可用于通过 JavaScript 创建弹出框,也可用于功能检测。例如

js
function supportsPopover() {
  return HTMLElement.prototype.hasOwnProperty("popover");
}

同样

将这三个放在一起,您可以以编程方式设置弹出框及其控制按钮,如下所示

js
const popover = document.getElementById("mypopover");
const toggleBtn = document.getElementById("toggleBtn");

const keyboardHelpPara = document.getElementById("keyboard-help-para");

const popoverSupported = supportsPopover();

if (popoverSupported) {
  popover.popover = "auto";
  toggleBtn.popoverTargetElement = popover;
  toggleBtn.popoverTargetAction = "toggle";
} else {
  toggleBtn.style.display = "none";
}

您还有几种方法可以控制显示和隐藏

例如,您可能希望通过单击按钮或按键盘上的特定键来提供打开和关闭帮助弹出框的功能。第一个可以通过声明式实现,或者您可以使用上面所示的 JavaScript 来实现。

对于第二个,您可以创建一个事件处理程序,该处理程序对两个单独的键进行编程——一个用于打开弹出框,另一个用于再次关闭它

js
document.addEventListener("keydown", (event) => {
  if (event.key === "h") {
    if (popover.matches(":popover-open")) {
      popover.hidePopover();
    }
  }

  if (event.key === "s") {
    if (!popover.matches(":popover-open")) {
      popover.showPopover();
    }
  }
});

此示例使用 Element.matches() 以编程方式检查弹出框当前是否正在显示。:popover-open 伪类仅匹配当前正在显示的弹出框。这对于避免尝试显示已显示的弹出框或隐藏已隐藏的弹出框时抛出的错误非常重要。

或者,您可以对单个键进行编程以显示和隐藏弹出框,如下所示

js
document.addEventListener("keydown", (event) => {
  if (event.key === "h") {
    popover.togglePopover();
  }
});

请参阅我们的 切换帮助 UI 示例 (源代码),以查看弹出框 JavaScript 属性、功能检测和 togglePopover() 方法的实际应用。

嵌套弹出框

关于一次不显示多个 auto 弹出框的规则有一个例外——当它们彼此嵌套时。在这种情况下,由于它们彼此之间的关系,允许多个弹出框同时打开。此模式受支持是为了启用诸如嵌套弹出框菜单之类的用例。

有三种不同的方法可以创建嵌套弹出框

  1. 直接 DOM 后代
    html
    <div popover>
      Parent
      <div popover>Child</div>
    </div>
    
  2. 通过调用/控制元素
    html
    <div popover>
      Parent
      <button popovertarget="foo">Click me</button>
    </div>
    
    <div popover id="foo">Child</div>
    
  3. 通过 anchor 属性
    html
    <div popover id="foo">Parent</div>
    
    <div popover anchor="foo">Child</div>
    

请参阅我们的 嵌套弹出框菜单示例 (源代码) 以获取示例。您会注意到,已经使用了相当多的事件处理程序来在鼠标和键盘访问期间适当地显示和隐藏子弹出框,以及在从任一菜单中选择选项时隐藏两个菜单。根据您处理新内容加载的方式(在 SPA 或多页面网站中),其中的一些或全部可能不是必需的,但已在此演示中包含在内以用于说明目的。

设置弹出框样式

Popover API 有一些相关的 CSS 功能值得关注。

在设置弹出框本身的样式方面,您可以使用简单的属性选择器 ([popover]) 选择所有弹出框,或者可以使用新的伪类选择正在显示的弹出框——:popover-open

查看文章开头链接的前几个示例时,您可能已经注意到弹出窗口出现在视口中间。这是默认样式,在 UA 样式表中是这样实现的

css
[popover] {
  position: fixed;
  inset: 0;
  width: fit-content;
  height: fit-content;
  margin: auto;
  border: solid;
  padding: 0.25em;
  overflow: auto;
  color: CanvasText;
  background-color: Canvas;
}

要覆盖默认样式并使弹出窗口出现在视口的其他位置,您需要使用类似以下内容覆盖上述样式

css
:popover-open {
  width: 200px;
  height: 100px;
  position: absolute;
  inset: unset;
  bottom: 5px;
  right: 5px;
  margin: 0;
}

您可以在我们的 弹出窗口定位示例 (源代码) 中看到一个独立的示例。

::backdrop 伪元素是一个全屏元素,放置在 顶层中显示的弹出窗口元素的正后方,允许根据需要向弹出窗口后面的页面内容添加效果。例如,您可能希望模糊弹出窗口后面的内容,以帮助用户将注意力集中在弹出窗口上

css
::backdrop {
  backdrop-filter: blur(3px);
}

请参阅我们的 弹出窗口模糊背景示例 (源代码),了解其渲染方式。

弹出框动画

隐藏时,弹出窗口设置为 display: none;,显示时设置为 display: block;,以及从 顶层辅助功能树中移除/添加到其中。因此,要对弹出窗口进行动画处理,需要使 display 属性可动画化。支持的浏览器 使用 离散动画类型 的变体对 display 进行动画处理。具体来说,浏览器将在 nonedisplay 的另一个值之间切换,以便在整个动画持续时间内显示动画内容。例如

  • 当将 displaynone 动画化为 block(或另一个可见的 display 值)时,该值将在动画持续时间的 0% 处切换到 block,以便在整个动画过程中可见。
  • 当将 displayblock(或另一个可见的 display 值)动画化为 none 时,该值将在动画持续时间的 100% 处切换到 none,以便在整个动画过程中可见。

注意:使用 CSS 过渡 进行动画处理时,需要设置 transition-behavior: allow-discrete 以启用上述行为。使用 CSS 动画 进行动画处理时,默认情况下可以使用上述行为;不需要执行等效步骤。

弹出窗口过渡

使用 CSS 过渡为弹出窗口制作动画时,需要以下功能

@starting-style 规则

为要在弹出窗口首次显示时从其过渡的属性提供一组起始值。这是为了避免意外行为。默认情况下,CSS 过渡仅在属性在可见元素上从一个值更改为另一个值时才会发生;它们不会在元素的第一次样式更新时触发,也不会在 display 类型从 none 更改为其他类型时触发。

display 属性

display 添加到过渡列表中,以便弹出窗口在过渡期间保持为 display: block(或另一个可见的 display 值),确保其他过渡可见。

overlay 属性

overlay 包含在过渡列表中,以确保将弹出窗口从顶层移除的操作延迟到过渡完成为止,再次确保过渡可见。

transition-behavior 属性

displayoverlay 过渡(或在 transition 简写中)上设置 transition-behavior: allow-discrete,以在默认情况下不可动画化的这两个属性上启用离散过渡。

让我们来看一个示例,以便您了解其外观

HTML

HTML 包含一个通过全局 popover HTML 属性声明为弹出窗口的 <div> 元素,以及一个被指定为弹出窗口显示控件的 <button> 元素

html
<button popovertarget="mypopover">Show the popover</button>
<div popover="auto" id="mypopover">I'm a Popover! I should animate.</div>

CSS

我们要过渡的两个弹出窗口属性是 opacitytransform。我们希望弹出窗口在水平增长或缩小时淡入或淡出。为此,我们为弹出窗口元素隐藏状态(使用 [popover] 属性选择器 选择)上的这些属性设置起始状态,并为弹出窗口显示状态(通过 :popover-open 伪类选择)设置结束状态。我们还使用 transition 属性来定义要设置动画的属性和动画持续时间,因为弹出窗口将显示或隐藏。

css
html {
  font-family: Arial, Helvetica, sans-serif;
}

/* Transition for the popover itself */

[popover]:popover-open {
  opacity: 1;
  transform: scaleX(1);
}

[popover] {
  font-size: 1.2rem;
  padding: 10px;

  /* Final state of the exit animation */
  opacity: 0;
  transform: scaleX(0);

  transition:
    opacity 0.7s,
    transform 0.7s,
    overlay 0.7s allow-discrete,
    display 0.7s allow-discrete;
  /* Equivalent to
  transition: all 0.7s allow-discrete; */
}

/* Needs to be after the previous [popover]:popover-open rule
to take effect, as the specificity is the same */
@starting-style {
  [popover]:popover-open {
    opacity: 0;
    transform: scaleX(0);
  }
}

/* Transition for the popover's backdrop */

[popover]::backdrop {
  background-color: rgb(0 0 0 / 0%);
  transition:
    display 0.7s allow-discrete,
    overlay 0.7s allow-discrete,
    background-color 0.7s;
  /* Equivalent to
  transition: all 0.7s allow-discrete; */
}

[popover]:popover-open::backdrop {
  background-color: rgb(0 0 0 / 25%);
}

/* The nesting selector (&) cannot represent pseudo-elements
so this starting-style rule cannot be nested */

@starting-style {
  [popover]:popover-open::backdrop {
    background-color: rgb(0 0 0 / 0%);
  }
}

如前所述,我们还

  • @starting-style 块中为 transition 设置起始状态。
  • display 添加到过渡属性列表中,以便动画元素在弹出窗口的进入和退出动画过程中可见(设置为 display: block)。如果没有此设置,退出动画将不可见;实际上,弹出窗口只会消失。
  • overlay 添加到过渡属性列表中,以确保将元素从顶层移除的操作延迟到动画完成为止。对于像这样的基本动画,此效果可能不明显,但在更复杂的情况下,省略此属性会导致元素在过渡完成之前从覆盖层中移除。
  • 在上述过渡中的两个属性上设置 allow-discrete 以启用 离散过渡

您会注意到,我们还在弹出窗口打开时出现在其后面的 ::backdrop 上包含了一个过渡,提供了一个不错的变暗动画。

结果

代码渲染如下

注意:因为弹出窗口每次显示时都会从 display: none 更改为 display: block,所以弹出窗口每次发生进入过渡时都会从其 @starting-style 样式过渡到其 [popover]:popover-open 样式。当弹出窗口关闭时,它会从其 [popover]:popover-open 状态过渡到默认的 [popover] 状态。

在这种情况下,进入和退出时的样式过渡可能不同。请参阅我们的 起始样式使用情况演示 示例,了解此证明。

弹出窗口关键帧动画

使用 CSS 关键帧动画为弹出窗口制作动画时,需要注意一些差异

  • 您不提供 @starting-style;您在关键帧中包含“to”和“from”display 值。
  • 您无需显式启用离散动画;关键帧中没有等效于 allow-discrete 的内容。
  • 您也不需要在关键帧中设置 overlaydisplay 动画处理弹出窗口从显示到隐藏的动画。

让我们来看一个示例。

HTML

HTML 包含一个声明为弹出窗口的 <div> 元素,以及一个被指定为弹出窗口显示控件的 <button> 元素

html
<button popovertarget="mypopover">Show the popover</button>
<div popover="auto" id="mypopover">I'm a Popover! I should animate.</div>

CSS

我们定义了指定所需进入和退出动画的关键帧,以及仅用于背景的进入动画。请注意,无法对背景淡出进行动画处理——弹出窗口关闭时,背景会立即从 DOM 中移除,因此没有任何内容可以设置动画。

css
html {
  font-family: Arial, Helvetica, sans-serif;
}

[popover] {
  font-size: 1.2rem;
  padding: 10px;
  animation: fade-out 0.7s ease-out;
}

[popover]:popover-open {
  animation: fade-in 0.7s ease-out;
}

[popover]:popover-open::backdrop {
  animation: backdrop-fade-in 0.7s ease-out forwards;
}

/* Animation keyframes */

@keyframes fade-in {
  0% {
    opacity: 0;
    transform: scaleX(0);
  }

  100% {
    opacity: 1;
    transform: scaleX(1);
  }
}

@keyframes fade-out {
  0% {
    opacity: 1;
    transform: scaleX(1);
    /* display needed on the closing animation to keep the popover
    visible until the animation ends */
    display: block;
  }

  100% {
    opacity: 0;
    transform: scaleX(0);
    /* display: none not required here because it is the default value
    for a closed popover, but including it so the behavior is clear */
    display: none;
  }
}

@keyframes backdrop-fade-in {
  0% {
    background-color: rgb(0 0 0 / 0%);
  }

  100% {
    background-color: rgb(0 0 0 / 25%);
  }
}

结果

代码渲染如下