インタラクティブなホバーカードを作成する

ホバーカードは、ユーザーがリンクに関心を示したとき(ホバーまたはフォーカス)に、コンテンツのインタラクティブなプレビューを表示します。これは、ユーザーが移動する前に表示され、ユーザーが必要とするコンテンツを提供するか、リンクに探しているコンテンツが含まれていることを確認するのに役立ちます。

構造化されたリンク プレビューではなく、書式なしテキストの非インタラクティブなツールチップが必要な場合は、カーソルを合わせたときにツールチップを表示するをご覧ください。

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. 開始と終了のアニメーション

デフォルトでは、キーボードとマウスのユーザーに対して、0.5 秒の遅延で関心が表示され、失われます。これにより、ページをタブ移動したり、マウスを動かしたりしているときに、ターゲットのポップオーバーが開くのを防ぐことができます。また、トリガーからポップオーバーにマウスを移動しているときに、ポップオーバーが閉じるのを防ぐことができます。タイミングを調整するには、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="..."> リンクであるため、ホバーカードをプログレッシブ エンハンスメントとして扱う場合、サポートされていないブラウザでもクリック時に移動できます。クロスブラウザのホバーカードのサポートが必要な場合は、必要に応じて条件付きでポリフィルを読み込みます。

Popover のフォールバックとブラウザ サポート

Popover API は主にプログレッシブ エンハンスメントですが、その定義動作である最上位レイヤのプロモーション、ライトディスミス、popovertarget 呼び出しには、CSS のみの同等の機能がありません。古いブラウザでは、ポリフィルが必要です。ポリフィルを配布したくない場合は、手動でフォールバックする必要があります。

ポリフィル: 古いブラウザで popover 属性をサポートするには、条件付きで @oddbird/popover-polyfill を読み込みます。必須: HTMLElement.prototype の popover プロパティをチェックして機能を検出し、ネイティブ サポートがない場合にのみポリフィルを読み込みます。無条件に読み込まないでください。

バンドラまたはインポート マップを使用する場合:

// 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>

スタイリングに関する注意事項: ポリフィルは実際の :popover-open 疑似クラスを定義できないため、代わりに .\:popover-open クラスを適用します。必須: :is() または :where() を使用して 2 つを結合します。そうしないと、:popover-open がないブラウザではルール全体が破棄されます。

[popover]:is(:popover-open, .\:popover-open) {
  display: block;
}

または、ポリフィルを使用しないレガシー フォールバックの場合は、position: fixed を使用して getBoundingClientRect() で座標を手動で計算するか、ユースケースで許容される場合は inset: auto でデフォルトの配置を使用します。

インタレスト インボーカーのフォールバックとブラウザ サポート

インタレスト インボーカーは、NPM の interestfor ポリフィル パッケージを使用して条件付きでポリフィルする必要があります。CDN を使用するよりも、ポリフィルをバンドルすることをおすすめします。

必須: HTMLButtonElement.prototype の interestForElement プロパティをチェックして機能を検出し、ネイティブ サポートがない場合にのみポリフィルを読み込みます。

<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 ポリフィルの注意事項

ポリフィルを使用する場合、ネイティブの疑似クラスとプロパティのサポートがないため、CSS API が若干変更されます。

  • 疑似クラス: ポリフィルは実際の :interest-source 疑似クラスと :interest-target 疑似クラスを定義できないため、代わりに .interest-source クラスと .interest-target クラスを適用します。必須: :is() と組み合わせて、ネイティブ状態とポリフィル状態の両方をサポートします。
/* 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 の場合、ポリフィルはすべてのブラウザでカスタム プロパティを直接読み取ることができないため、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);
}