Membuat kartu informasi interaktif

Kartu pengarah memberikan pratinjau konten interaktif saat pengguna menunjukkan minat pada link (melalui pengarahan kursor atau fokus). Informasi ini ditampilkan kepada pengguna sebelum mereka membuka link, dan memberikan konten yang mereka butuhkan atau membantu memverifikasi bahwa link tersebut akan berisi konten yang mereka cari.

Jika Anda memerlukan tooltip teks biasa non-interaktif, bukan pratinjau link terstruktur, lihat Menampilkan tooltip saat mengarahkan kursor.

1. Memicu kartu informasi

Gunakan API Interest Invokers untuk memicu kartu informasi. Hal ini memastikan kartu muncul saat kursor diarahkan dan difokuskan, serta menangani pengkabelan aksesibilitas secara otomatis.

  • Tambahkan atribut popover="hint" ke penampung kartu Anda. Gunakan hint untuk menghindari penutupan menu popover yang tidak terkait yang mungkin terbuka.
  • Tambahkan atribut interestfor ke link pemicu, yang mengarah ke id kartu.
<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>

Anda tidak perlu menyertakan link tambahan ke konten di dalam kartu informasi, karena link tersebut sudah ada di link pemicu.

Aksesibilitas bawaan di interestfor

Saat Anda menggunakan interestfor, browser akan menangani pengaitan teknologi pendukung untuk Anda:

  • Semantik implisit: Target dengan popover="hint" mendapatkan peran minimum implisit tooltip. JANGAN tetapkan role="tooltip" sendiri.
  • Pengaitan implisit: Browser secara implisit mengaitkan elemen sumber dengan target melalui aria-describedby jika targetnya adalah teks biasa, atau melalui aria-details jika targetnya berisi konten interaktif. JANGAN menambahkan aria-describedby atau aria-details ke pemicu.
  • Konten interaktif: Karena pengaitan beralih ke aria-details jika diperlukan, target diizinkan untuk berisi konten interaktif (misalnya, link di dalam "kartu minat").

2. Penentuan Posisi dengan Anchor Positioning

Posisikan kartu pengarah relatif terhadap pemicunya menggunakan CSS Anchor Positioning. Saat menggunakan interestfor, pemicu akan menjadi anchor implisit untuk popover.

[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. Menganimasikan masuk dan keluar

Secara default, minat ditampilkan dan hilang untuk pengguna keyboard dan mouse dengan penundaan setengah detik. Tindakan ini mencegah popover yang ditargetkan terbuka saat berpindah-pindah halaman atau menggerakkan mouse, dan menutup saat menggerakkan mouse dari pemicu ke popover. Untuk menyesuaikan pengaturan waktu, gunakan properti CSS interest-delay.

[interestfor]{
  /* Shorthand for interest-delay-start and interest-delay-end */
  interest-delay: 0.2s 0.1s;
}

Jika pengguna cenderung berpindah di antara beberapa pemicu secara berurutan, tetapkan penundaan awal ke 0s jika popover yang dipicu minat sudah terbuka.

:root:has(:interest-source) [interestfor] {
  interest-delay-start: 0s;
}

Gunakan @starting-style untuk menentukan animasi masuk dan transition-behavior: allow-discrete (bersama dengan display dan overlay) untuk menganimasikan keluar dari lapisan atas, yang membatasi transisi di belakang @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. OPSIONAL: Berubah dengan mulus ke halaman berikutnya

Anda dapat menggunakan Transisi Tampilan Lintas Dokumen untuk membuat animasi "perubahan" yang lancar antara kartu informasi dan halaman tujuan. Lihat Transisi Lintas Dokumen yang Konsisten untuk mendapatkan panduan tentang penerapannya.

Dengan menetapkan nilai view-transition-name yang cocok ke elemen di kartu informasi dan padanannya di halaman berikutnya, browser akan menganimasikannya di seluruh navigasi. Anda dapat memberi nama beberapa elemen (misalnya judul, gambar, nama penulis) untuk membuat efek perubahan bentuk multi-elemen yang kompleks. Tetapkan view-transition-name ke elemen di popover yang terbuka menggunakan :popover-open, untuk menghindari pembuatan view-transition-name duplikat atau memerlukan view-transition-name khusus link.

/* 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;
  }
}

WAJIB: Untuk memastikan transisi stabil dan tidak berkedip, halaman tujuan harus menggunakan blocking="render" dan <link rel="expect"> untuk menunggu konten penting diuraikan sebelum merender frame pertama.

<!-- 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;
}

Strategi penggantian

Karena pemicunya adalah link <a href="..."> standar, browser yang tidak mendukung masih dapat membuka link saat diklik jika Anda memilih untuk memperlakukan hovercard sebagai progressive enhancement. Jika dukungan hovercard lintas browser diperlukan, muat polyfill secara kondisional sesuai kebutuhan.

Penggantian & dukungan browser untuk Popover

Popover API sebagian besar merupakan progressive enhancement, tetapi perilaku penentuannya — promosi lapisan atas, penutupan ringan, dan pemanggilan popovertarget — tidak memiliki padanan khusus CSS. Browser lama memerlukan polyfill, atau penggantian manual jika Anda tidak ingin mengirimkannya.

Polyfill: Untuk mendukung atribut popover di browser lama, muat @oddbird/popover-polyfill secara bersyarat. WAJIB: Deteksi fitur dengan memeriksa properti popover di HTMLElement.prototype, dan muat polyfill hanya jika dukungan native tidak ada — JANGAN memuatnya tanpa syarat.

Dengan bundler atau peta impor:

// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
  import("@oddbird/popover-polyfill");
}

Tanpa bundler, impor dari CDN di dalam <script type="module">:

<script type="module">
  if (!("popover" in HTMLElement.prototype)) {
    import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
  }
</script>

Peringatan gaya: Polyfill tidak dapat menentukan pseudo-class :popover-open yang sebenarnya, sehingga menerapkan class .\:popover-open. WAJIB: Gabungkan keduanya dengan :is() atau :where(). Jika tidak, browser yang tidak memiliki :popover-open akan menghapus seluruh aturan:

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

Atau, untuk penggantian lama tanpa polyfill, gunakan position: fixed dan hitung koordinat secara manual melalui getBoundingClientRect() atau andalkan pemosisian default dengan inset: auto jika hal itu dapat diterima untuk kasus penggunaan.

Penggantian & dukungan browser untuk Interest invoker

Pemanggil minat harus di-polyfill secara bersyarat menggunakan paket polyfill interestfor dari NPM. Lebih baik memaketkan polyfill daripada menggunakan CDN.

WAJIB: Deteksi fitur dengan memeriksa properti interestForElement di HTMLButtonElement.prototype, dan muat polyfill hanya jika dukungan native tidak ada.

<script type="module">
  if (!HTMLButtonElement.prototype.hasOwnProperty("interestForElement")) {
    // CDN link only used for example, prefer bundling.
    await import("https://unpkg.com/interestfor@latest");
  }
</script>

Peringatan Polyfill CSS

Saat menggunakan polyfill, CSS API sedikit berubah untuk memperhitungkan kurangnya dukungan properti dan pseudo-class native:

  • Pseudo-class: Polyfill tidak dapat menentukan pseudo-class :interest-source dan :interest-target yang sebenarnya, sehingga menerapkan class .interest-source dan .interest-target. WAJIB: Gabungkan dengan :is() untuk mendukung status native dan polyfill.
/* Styles to apply when the effect is being previewed */
:is(:interest-source, .interest-source) { /* ... */ }
:is(:interest-target, .interest-target) { /* ... */ }
  • Properti CSS: Untuk interest-delay, interest-delay-start, dan interest-delay-end, polyfill memerlukan penggunaan variabel CSS karena tidak dapat membaca properti kustom secara langsung di semua browser.
/* 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);
}