建構互動式懸浮資訊卡

當使用者對連結感興趣 (透過懸停或聚焦),懸浮資訊卡會提供內容的互動式預覽。使用者會在前往連結前看到這項資訊,這項資訊會提供使用者需要的內容,或協助確認連結是否包含他們正在尋找的內容。

如果您需要純文字、非互動式的工具提示,而非結構化連結預覽,請參閱「在懸停時顯示工具提示」。

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. 為進場和退場效果建立動畫

根據預設,鍵盤和滑鼠使用者會延遲半秒顯示和失去焦點。這樣一來,使用者在頁面上切換分頁或移動滑鼠時,就不會開啟目標彈出式視窗,而且將滑鼠從觸發條件移至彈出式視窗時,也不會關閉彈出式視窗。如要調整時間,請使用 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:如要在舊版瀏覽器中支援 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 預設定位。

Interest invoker 的備用方案和瀏覽器支援

Interest Invoker 必須使用 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);
}