当用户对某个链接表示兴趣(通过悬停或聚焦)时,悬停卡片会提供内容的互动式预览。此内容会在用户导航之前显示,既能为用户提供所需内容,又能帮助验证链接是否包含用户正在寻找的内容。
如果您需要纯文本、非交互式提示,而不是结构化链接预览,请参阅悬停时显示提示。
1. 触发悬停卡片
使用 Interest Invokers API 触发悬停卡片。这样可以确保卡片在悬停和聚焦时都会显示,并自动处理无障碍功能连接。
- 向卡片容器添加
popover="hint"属性。使用hint可避免关闭可能已打开的不相关弹出式菜单。 - 向触发器链接添加
interestfor属性,并指向卡片的id。
<a href="/article" interestfor="article-preview">Article title</a>
<!-- The `id` must match the `interestfor` value on the trigger. -->
<div id="article-preview" popover="hint" class="hovercard">
<h2>The buzz on hummingbirds</h2>
<p class="byline">By Delphi Aguilar</p>
<div>
<img src="bird.jpg" alt="A vibrant hummingbird">
<p>Brief summary of the article...</p>
</div>
</div>
您无需在悬停卡片中添加指向内容的额外链接,因为这与触发链接重复。
interestfor 内置无障碍功能
当您使用 interestfor 时,浏览器会为您处理辅助技术连接:
- 隐式语义:具有
popover="hint"的目标会获得tooltip的隐式最低角色。请勿自行设置role="tooltip"。 - 隐式关联:当目标为纯文本时,浏览器会通过
aria-describedby将源元素与目标隐式关联;当目标包含互动内容时,浏览器会通过aria-details将源元素与目标隐式关联。请勿向触发器添加aria-describedby或aria-details。 - 互动内容:由于关联会在需要时切换到
aria-details,因此目标允许包含互动内容(例如“兴趣卡片”内的链接)。
2. 使用锚点定位进行定位
使用 CSS 锚点定位,相对于触发器定位悬停卡片。使用 interestfor 时,触发器会成为弹出式窗口的隐式锚点。
[popover].hovercard {
/* Position the card below the trigger, aligned with its inline-start edge
and spanning toward the inline-end, flipping if it overflows the viewport. */
position-area: block-end span-inline-end;
position-try-fallbacks: flip-block, flip-inline;
}
3. 为进入和退出添加动画效果
默认情况下,对于键盘和鼠标用户,兴趣会在延迟半秒后显示和消失。这样可以防止在通过 Tab 键浏览网页或移动鼠标时打开目标弹出式窗口,以及在将鼠标从触发器移至弹出式窗口时关闭弹出式窗口。如需调整时间,请使用 interest-delay CSS 属性。
[interestfor]{
/* Shorthand for interest-delay-start and interest-delay-end */
interest-delay: 0.2s 0.1s;
}
如果用户可能会快速连续触发多个触发器,请在兴趣触发的弹出式窗口已打开的情况下,将初始延迟设置为 0s。
:root:has(:interest-source) [interestfor] {
interest-delay-start: 0s;
}
使用 @starting-style 定义进入动画,并使用 transition-behavior: allow-discrete(以及 display 和 overlay)为从顶层退出的过程添加动画效果,从而在 @media (prefers-reduced-motion: no-preference) 后面控制过渡。
[popover].hovercard {
opacity: 0;
transform-origin: top;
}
[popover].hovercard:popover-open {
opacity: 1;
transform: translateY(0) scale(1);
}
@starting-style {
[popover].hovercard:popover-open {
opacity: 0;
transform: translateY(-10px) scale(0.95);
}
}
/* MANDATORY: Respect user preference for reduced motion by selectively applying transitions. */
@media (prefers-reduced-motion: no-preference) {
[popover].hovercard {
transition:
opacity,
transform,
display,
overlay;
transition-duration: 0.4s;
transition-behavior: allow-discrete;
}
}
4. 可选:无缝过渡到下一页
您可以选择使用跨文档视图过渡,在悬停卡片和目标网页之间创建无缝的“变形”动画。如需了解实现方面的指导,请参阅一致的跨文档过渡。
通过为悬停卡片中的元素及其在下一页中的对应元素分配匹配的 view-transition-name 值,浏览器将在导航过程中为它们添加动画效果。您可以为多个元素(例如标题、图片、署名)命名,以创建复杂的多元素变形效果。使用 :popover-open 将 view-transition-name 分配给打开的弹出式窗口中的元素,以避免创建重复的 view-transition-name 或需要特定于链接的 view-transition-name。
/* Opt-in to cross-document transitions on both pages */
@view-transition {
navigation: auto;
}
/* Don't show transitions when loading this page */
::view-transition-group(*) {
animation-duration: 0s;
}
/* Assign names ONLY when the popover is open to ensure only the correct elements are selected. */
[popover]:popover-open {
view-transition-name: --hovercard;
view-transition-class: morph;
h2 {
view-transition-name: --title;
view-transition-class: morph;
}
img {
view-transition-name: --image;
view-transition-class: morph;
}
}
强制性要求:为确保过渡稳定且不闪烁,目标网页应使用 blocking="render" 和 <link rel="expect"> 等待关键内容解析完毕,然后再渲染第一帧。
<!-- On the destination page: -->
<head>
<!-- Block rendering until the hero image and title are parsed,
ensuring the view transition snapshot is taken at the right time. -->
<link rel="expect" href="#main-content" blocking="render">
</head>
<body>
<main id="main-content">
<h1>The buzz on hummingbirds</h1>
<img src="bird.jpg">
</main>
</body>
/* On the destination page: */
main {
view-transition-name: --hovercard;
view-transition-class: morph;
}
h1 {
view-transition-name: --title;
view-transition-class: morph;
}
img {
view-transition-name: --image;
view-transition-class: morph;
}
/* By default no transition */
::view-transition-group(*) {
animation-duration: 0s;
}
/* Short transition for morphing elements */
::view-transition-group(.morph) {
animation-duration: 0.4s;
}
/* Disable transition if no `old` view */
::view-transition-new(.morph):only-child {
animation-duration: 0s;
}
后备策略
由于触发器是标准 <a href="..."> 链接,因此如果您选择将悬停卡片视为渐进增强,不支持该功能的浏览器仍可在点击时进行导航。如果需要跨浏览器悬停卡片支持,请根据需要有条件地加载 polyfill。
Popover 的回退和浏览器支持
Popover API 主要是一种渐进增强技术,但其核心行为(顶层提升、轻触关闭和 popovertarget 调用)没有仅限 CSS 的等效项。旧版浏览器需要 polyfill,如果您不想提供 polyfill,则需要手动后备方案。
Polyfill:如需在旧版浏览器中支持 popover 属性,请有条件地加载 @oddbird/popover-polyfill。强制性要求:通过检查 HTMLElement.prototype 上的 popover 属性来检测功能,并且仅在缺少原生支持时加载 polyfill,请勿无条件加载。
使用打包工具或导入映射:
// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
import("@oddbird/popover-polyfill");
}
如果没有打包器,请从 <script type="module"> 内的 CDN 导入:
<script type="module">
if (!("popover" in HTMLElement.prototype)) {
import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
}
</script>
样式注意事项:polyfill 无法定义真正的 :popover-open 伪类,因此它会改为应用 .\:popover-open 类。强制性:使用 :is() 或 :where() 将两者结合,否则缺少 :popover-open 的浏览器会舍弃整个规则:
[popover]:is(:popover-open, .\:popover-open) {
display: block;
}
或者,对于没有 polyfill 的旧版后备方案,请使用 position: fixed 并通过 getBoundingClientRect() 手动计算坐标,或依赖 inset: auto 的默认定位(如果该用例可以接受)。
兴趣调用器的回退和浏览器支持
必须使用 NPM 中的 interestfor polyfill 软件包有条件地对兴趣调用器进行 polyfill。建议捆绑 polyfill,而不是使用 CDN。
强制要求:通过检查 HTMLButtonElement.prototype 上的 interestForElement 属性来检测功能,并且仅在缺少原生支持时加载 polyfill。
<script type="module">
if (!HTMLButtonElement.prototype.hasOwnProperty("interestForElement")) {
// CDN link only used for example, prefer bundling.
await import("https://unpkg.com/interestfor@latest");
}
</script>
CSS Polyfill 注意事项
使用 polyfill 时,CSS API 会略有变化,以弥补原生伪类和属性支持的不足:
- 伪类:polyfill 无法定义真正的
:interest-source和:interest-target伪类,因此它会改为应用.interest-source和.interest-target类。强制性:将它们与:is()结合使用,以同时支持原生状态和 polyfill 状态。
/* Styles to apply when the effect is being previewed */
:is(:interest-source, .interest-source) { /* ... */ }
:is(:interest-target, .interest-target) { /* ... */ }
- CSS 属性:对于
interest-delay、interest-delay-start和interest-delay-end,polyfill 需要使用 CSS 变量,因为它无法在所有浏览器中直接读取自定义属性。
/* Adjust the start and end delay for interest invokers */
[interestfor] {
--interest-delay-start: 0.2s;
interest-delay-start: var(--interest-delay-start);
--interest-delay-end: 0.1s;
interest-delay-end: var(--interest-delay-end);
}