使用 Popover API

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

创建声明式弹出框

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

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

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

添加此属性会导致元素在页面加载时通过设置 display: none 而隐藏。要显示/隐藏弹出框,您需要添加至少一个控制按钮(也称为弹出框调用者)。您可以通过为其提供 popovertarget 属性来将 <button>(或 type="button"<input>)设置为弹出框控制按钮,该属性的值应为要控制的弹出框的 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 并被放置到顶层,因此它将位于所有其他页面内容之上。

commandcommandfor

commandforcommand 属性提供了与 popovertargetpopovertargetaction 非常相似的功能,但设计更为通用,旨在提供弹出框命令之外的其他功能,包括自定义命令。

前面的代码片段可以这样重写:

html
<button commandfor="mypopover" command="show-popover">Show popover</button>
<button commandfor="mypopover" command="hide-popover">Hide popover</button>
<div id="mypopover" popover>Popover content</div>

自动状态和“轻量级关闭”

当如上所示设置了 popoverpopover="auto" 的弹出框元素时,它就被认为是具有自动状态。关于自动状态需要注意的两个重要行为是:

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

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

当您只想一次显示一个弹出框时,自动状态很有用。也许您有多个教学 UI 消息要显示,但不希望显示变得杂乱和令人困惑,或者您正在显示状态消息,其中新状态会覆盖任何以前的状态。

您可以在我们的多个自动弹出框示例)中查看上述行为的实际应用。尝试在显示弹出框后轻量级关闭它们,并查看当您尝试同时显示两者时会发生什么。

弹出框辅助功能

当通过 popovertarget 属性在弹出框及其控件(调用者)之间建立关系时,API 会自动对环境进行另外两项更改,以使键盘和辅助技术 (AT) 用户更容易与弹出框交互:

  • 当弹出框显示时,键盘焦点导航顺序会更新,以便弹出框在序列中紧随其后:例如,当按下按钮显示弹出框时,弹出框内的任何按钮将是选项卡顺序中的下一个(通过按 Tab 键聚焦)。相反,当通过键盘关闭弹出框时(通常通过 Esc 键),焦点会移回调用者。
  • 为了让屏幕阅读器等 AT 理解调用者和弹出框之间的关系,它们之间建立了隐式的 aria-detailsaria-expanded 关系。

以这种方式在弹出框与其控件之间建立关系还会在这两者之间创建隐式锚点引用 — 有关更多详细信息,请参阅弹出框锚点定位

设置弹出框-调用者关系的其他方式

除了使用 popovertarget 属性之外,您还可以通过其他方式设置弹出框-调用者关系:

  • 使用 HTMLElement.showPopover()HTMLElement.togglePopover() 方法的 source 选项。请记住,在这种情况下,只进行了焦点导航顺序更改,而不是隐式的 ARIA 关系。这是因为 source 选项可以设置为任何类型的元素,而不仅仅是 <button> 元素,并且无法保证这种关系有意义。
  • <select> 元素及其下拉选择器之间,当通过 appearance 属性 base-select 值选择启用可定制选择元素功能时。在这种情况下,两者之间会创建隐式的弹出框-调用者关系。

使用手动弹出框状态

自动状态的另一种选择是手动状态,通过在您的弹出框元素上设置 popover="manual" 来实现。

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

在这种状态下:

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

您可以在我们的多个手动弹出框示例)中查看此行为的实际应用。

beforetoggletoggle 事件

您可以使用 beforetoggletoggle 事件来响应弹出框的显示或隐藏。

  • beforetoggle 在弹出框显示或隐藏之前触发。例如,这可以用于阻止弹出框显示或隐藏(使用 Event.preventDefault()),为弹出框添加动画类以进行动画处理,或在使用后清除弹出框的状态。
  • toggle 在弹出框显示或隐藏之后触发。这通常用于在弹出框切换状态更改时运行其他代码。

这两个事件都具有 ToggleEvent 事件对象。除了从默认 Event 对象继承的功能外,此事件还具有以下功能:

  • oldStatenewState 属性指示弹出框刚刚从哪个状态转换到哪个状态,允许您专门响应弹出框的打开或关闭。
  • source 属性包含对启动切换的 HTML 弹出框控制元素的引用,允许您根据哪个控件启动了切换来运行不同的代码以响应切换事件。

典型用法可能如下所示:

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

popover.addEventListener("toggle", (e) => {
  console.log(e.newState);
});

有关更多信息和示例,请参阅前面的参考链接。

通过 JavaScript 显示弹出框

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

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

js
function supportsPopover() {
  return Object.hasOwn(HTMLElement.prototype, "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() 方法的实际应用。

嵌套弹出框

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

创建嵌套弹出框有三种不同的方式:

  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="hint" 来指定。hint 弹出框在显示时不会关闭 auto 弹出框,但会关闭其他 hint 弹出框。它们可以轻量级关闭并响应关闭请求。

这对于以下情况很有用:例如,您有可以按下以显示 UI 弹出框的工具栏按钮,但您还希望在鼠标悬停在按钮上时显示工具提示,而不会关闭 UI 弹出框。

hint 弹出框倾向于响应非点击 JavaScript 事件(例如 mouseover/mouseoutfocus/blur)进行显示和隐藏。单击按钮以打开 hint 弹出框将导致打开的 auto 弹出框轻量级关闭。

请参阅我们的弹出框提示演示),获取一个行为完全如上所述的示例。该演示具有一个按钮栏;按下时,按钮会显示 auto 弹出子菜单,其中可以进一步选择选项。但是,当鼠标悬停或聚焦时,按钮还会显示工具提示(hint 弹出框),以让用户了解每个按钮的作用,这些工具提示不会隐藏当前显示的子菜单。

在下面的部分中,我们将详细介绍代码的所有重要部分。

注意:可以hint 弹出框与 manual 弹出框一起使用,尽管这样做的理由不多。它们旨在规避 auto 弹出框的一些限制,从而实现本节中详述的用例。

另请注意,在不支持的浏览器中,popover="hint" 会回退到 popover="manual"

使用 popover="auto" 创建子菜单

弹出子菜单是声明式创建的,使用 auto 弹出框。

首先,控制按钮:

html
<section id="button-bar">
  <button popovertarget="submenu-1" popovertargetaction="toggle" id="menu-1">
    Menu A
  </button>

  <button popovertarget="submenu-2" popovertargetaction="toggle" id="menu-2">
    Menu B
  </button>

  <button popovertarget="submenu-3" popovertargetaction="toggle" id="menu-3">
    Menu C
  </button>
</section>

现在,弹出框本身:

html
<div id="submenu-1" popover="auto">
  <button>Option A</button><br /><button>Option B</button>
</div>
<div id="submenu-2" popover="auto">
  <button>Option A</button><br /><button>Option B</button>
</div>
<div id="submenu-3" popover="auto">
  <button>Option A</button><br /><button>Option B</button>
</div>

使用 popover="hint" 创建工具提示

子菜单弹出框工作正常,在按下工具栏按钮时打开,但我们如何也在按钮悬停/聚焦时显示工具提示呢?首先,我们在 HTML 中创建工具提示,使用 hint 弹出框:

html
<div id="tooltip-1" class="tooltip" popover="hint">Tooltip A</div>
<div id="tooltip-2" class="tooltip" popover="hint">Tooltip B</div>
<div id="tooltip-3" class="tooltip" popover="hint">Tooltip C</div>

注意:在演示的源代码中,工具提示嵌套在弹出框控制按钮内。这是因为在不支持 CSS 锚点定位的浏览器中,它提供了更好的回退 — hint 弹出框会出现在其关联的控制按钮旁边,而不是完全在其他地方。

要控制显示/隐藏,我们需要使用 JavaScript。首先,我们使用 Document.querySelectorAll() 获取对 hint 弹出框和控制按钮在两个单独的 NodeList 中的引用:

js
const tooltips = document.querySelectorAll(".tooltip");
const btns = document.querySelectorAll("#button-bar button");

接下来,我们创建一个函数 addEventListeners(),它在给定 <button> 上设置四个事件监听器(通过 EventTarget.addEventListener()),该按钮通过抓取 btns NodeList 中特定索引值的 <button> 来选择。这些函数作用于 tooltips NodeList 中相同索引值的 hint 弹出框,使我们能够保持按钮和工具提示同步 — 在与按钮交互时显示/隐藏正确的工具提示。

事件监听器在 mouseoverfocus显示弹出框,并在 mouseoutblur隐藏弹出框,这意味着工具提示可以通过鼠标和键盘访问。

js
function addEventListeners(i) {
  btns[i].addEventListener("mouseover", () => {
    tooltips[i].showPopover({ source: btns[i] });
  });

  btns[i].addEventListener("mouseout", () => {
    tooltips[i].hidePopover();
  });

  btns[i].addEventListener("focus", () => {
    tooltips[i].showPopover({ source: btns[i] });
  });

  btns[i].addEventListener("blur", () => {
    tooltips[i].hidePopover();
  });
}

最后,我们使用 for 循环遍历 btns NodeList 中的 <buttons>,为每个按钮调用 addEventListeners() 函数,以便所有按钮都设置了所需的事件监听器。

js
for (let i = 0; i < btns.length; i++) {
  addEventListeners(i);
}

弹出框样式

本节介绍了一些与弹出框相关的 CSS 选择和定位技术。

选择弹出框

您可以使用简单的属性选择器选择所有弹出框:

css
[popover] {
  /* Declarations here */
}

或者,您可以通过在属性选择器中包含值来选择特定类型的弹出框:

css
[popover="auto"] {
  /* Declarations here */
}

您可以使用 :popover-open 伪类选择仅显示中的弹出框:

css
:popover-open {
  /* Declarations here */
}

为弹出框背景设置样式

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

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

请参阅我们的弹出框模糊背景示例)以了解其渲染效果。

定位弹出框

在查看文章开头链接的几个示例时,您可能已经注意到弹出框出现在视口中间,环绕其内容,并具有黑色边框。这是默认样式,通过 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;
}

您可以在我们的弹出框定位示例)中看到一个独立的示例。

弹出框锚点定位

Popover API 提供了另一个有用的定位选项。如果您想相对于其调用者而不是视口或定位的祖先定位弹出框,您可以利用弹出框及其调用者具有隐式锚点引用的事实。

将任何类型的弹出框与其调用者关联都会在两者之间创建隐式锚点引用。这会使调用者成为弹出框的锚点元素,这意味着您可以使用 CSS 锚点定位将其相对于它进行定位。

由于弹出框和调用者之间的关联是隐式的,因此不需要使用 anchor-nameposition-anchor 属性进行显式关联。但是,您仍然需要指定定位 CSS。

例如,您可以使用在嵌入属性上设置的 anchor() 函数值和在对齐属性上设置的 anchor-center 值的组合:

css
.my-popover {
  margin: 0;
  inset: auto;
  bottom: calc(anchor(top) + 20px);
  justify-self: anchor-center;
}

或者您可以使用 position-area 属性:

css
.my-popover {
  margin: 0;
  inset: auto;
  position-area: top;
}

在使用 position-areaanchor() 定位弹出框时,请注意 弹出框的默认样式可能会与您尝试实现的位置冲突。通常的罪魁祸首是 margininset 的默认样式,因此建议重置这些样式,如上面的示例所示。CSS 工作组正在寻找避免需要此变通方法的方法

有关关联锚点和定位元素以及相对于其锚点定位元素的更多详细信息,请参阅使用 CSS 锚点定位

注意:有关使用此隐式关联的示例,请参阅我们的弹出框提示演示)。如果您查看 CSS 代码,您会看到没有使用 anchor-nameposition-anchor 属性进行显式锚点关联。

注意:如果您想删除隐式锚点引用以阻止弹出框锚定到其调用者,您可以通过将弹出框的 position-anchor 属性设置为当前文档中不存在的锚点名称(例如 --not-an-anchor-name)来实现。另请参阅删除锚点关联

弹出框动画

弹出框在隐藏时设置为 display: none;,在显示时设置为 display: block;,并且会被从顶层辅助功能树中移除/添加。因此,为了使弹出框具有动画效果,display 属性需要是可动画的。支持的浏览器会通过离散动画类型的变体来动画 display。具体来说,浏览器会在 none 和另一个 display 值之间切换,以便动画内容在整个动画持续时间内显示。例如:

  • 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 包含一个 <div> 元素,通过全局 popover HTML 属性声明为弹出框,以及一个指定为弹出框显示控件的 <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: "Helvetica", "Arial", 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: transparent;
  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: transparent;
  }
}

如前所述,我们还:

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

您会注意到,我们还在弹出框打开时,弹出框后面出现的 ::backdrop 上添加了过渡,提供了一个漂亮的变暗动画。

结果

代码渲染如下:

备注: 因为 popover 每次显示时都会从 display: none 变为 display: block,所以每次进入过渡发生时,popover 都会从其 @starting-style 样式过渡到其 [popover]:popover-open 样式。当 popover 关闭时,它会从其 [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: "Helvetica", "Arial", 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: transparent;
  }

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

结果

代码渲染如下: