Crea una scheda popup interattiva

Le schede popup forniscono un'anteprima interattiva dei contenuti quando un utente esprime interesse per un link (tramite passaggio del mouse o stato attivo). Questo viene mostrato agli utenti prima che inizino la navigazione e fornisce loro i contenuti di cui hanno bisogno o li aiuta a verificare che il link contenga i contenuti che stanno cercando.

Se hai bisogno di tooltip di testo normale non interattivi anziché di anteprime dei link strutturate, consulta Mostra un tooltip al passaggio del mouse.

1. Attivazione della scheda popup

Utilizza l'API Interest Invokers per attivare la scheda popup. In questo modo, la scheda viene visualizzata sia al passaggio del mouse sia quando l'elemento è attivo e i collegamenti di accessibilità vengono gestiti automaticamente.

  • Aggiungi l'attributo popover="hint" al contenitore della carta. Utilizza hint per evitare di chiudere i menu popup non correlati che potrebbero essere aperti.
  • Aggiungi l'attributo interestfor al link di attivazione, che rimanda all'id della scheda.
<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>

Non è necessario includere un link aggiuntivo ai contenuti all'interno della scheda popup, in quanto è ridondante rispetto al link di attivazione.

Accessibilità integrata in interestfor

Quando utilizzi interestfor, il browser gestisce il collegamento delle tecnologie per la disabilità per te:

  • Semantica implicita: un target con popover="hint" ottiene un ruolo minimo implicito di tooltip. NON impostare role="tooltip" autonomamente.
  • Associazione implicita. Il browser associa implicitamente l'elemento di origine alla destinazione tramite aria-describedby quando la destinazione è in testo normale o tramite aria-details quando la destinazione contiene contenuti interattivi. NON aggiungere aria-describedby o aria-details all'attivatore.
  • Contenuti interattivi: poiché l'associazione passa a aria-details quando necessario, il target PUÒ contenere contenuti interattivi (ad es. un link all'interno di una "scheda di interesse").

2. Posizionamento con Anchor Positioning

Posiziona l'hovercard rispetto al relativo trigger utilizzando il posizionamento degli ancoraggi CSS. Quando utilizzi interestfor, l'attivatore diventa un ancoraggio implicito per il 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. Animazione di entrata e uscita

Per impostazione predefinita, l'interesse viene mostrato e perso per gli utenti di tastiera e mouse con un ritardo di mezzo secondo. In questo modo, il popover di destinazione non si apre quando scorri una pagina o sposti il mouse e non si chiude quando sposti il mouse dall'attivatore al popover. Per regolare la tempistica, utilizza la proprietà CSS interest-delay.

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

Se è probabile che gli utenti passino rapidamente da un trigger all'altro, imposta il ritardo iniziale su 0s se un popover attivato da un interesse è già aperto.

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

Utilizza @starting-style per definire l'animazione di entrata e transition-behavior: allow-discrete (insieme a display e overlay) per animare l'uscita dal livello superiore, limitando le transizioni dietro @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. FACOLTATIVO: transizione fluida alla pagina successiva

Se vuoi, puoi utilizzare le transizioni di visualizzazione tra documenti per creare un'animazione di "morphing" fluida tra la scheda popup e la pagina di destinazione. Consulta la sezione Transizioni tra documenti coerenti per indicazioni sull'implementazione.

Assegnando valori view-transition-name corrispondenti agli elementi della scheda popup e alle loro controparti nella pagina successiva, il browser li animerà durante la navigazione. Puoi assegnare un nome a più elementi (ad es. titoli, immagini, firme) per creare un effetto di morphing complesso con più elementi. Assegna l'view-transition-name agli elementi del popover aperto utilizzando :popover-open, per evitare di creare view-transition-name duplicati o di richiedere view-transition-name specifici per i 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;
  }
}

OBBLIGATORIO: per garantire che la transizione sia stabile e non sfarfalli, la pagina di destinazione deve utilizzare blocking="render" e <link rel="expect"> per attendere l'analisi dei contenuti critici prima di eseguire il rendering del primo frame.

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

Strategie di riserva

Poiché l'attivatore è un link <a href="..."> standard, i browser che non lo supportano possono comunque navigare al clic se scegli di trattare la scheda popup come un potenziamento progressivo. Quando è richiesto il supporto di hovercard cross-browser, carica i polyfill in modo condizionale in base alle esigenze.

Alternative e supporto del browser per Popover

L'API Popover è per lo più un potenziamento progressivo, ma i suoi comportamenti distintivi (promozione al livello superiore, chiusura con un clic e attivazione di popovertarget) non hanno un equivalente solo CSS. I browser meno recenti richiedono un polyfill o un fallback manuale se preferisci non fornirne uno.

Polyfill: per supportare l'attributo popover nei browser meno recenti, carica @oddbird/popover-polyfill in modo condizionale. OBBLIGATORIO: esegui il rilevamento delle funzionalità controllando la proprietà popover su HTMLElement.prototype e carica il polyfill solo quando manca il supporto nativo; NON caricarlo incondizionatamente.

Con un bundler o una mappa di importazione:

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

Senza un bundler, importa da una CDN all'interno di un <script type="module">:

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

Avvertenza sullo stile: il polyfill non può definire la pseudo-classe :popover-open reale, quindi applica una classe .\:popover-open. OBBLIGATORIO: combina i due elementi con :is() o :where(), altrimenti i browser che non dispongono di :popover-open ignorano l'intera regola:

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

In alternativa, per un fallback legacy senza un polyfill, utilizza position: fixed e calcola manualmente le coordinate tramite getBoundingClientRect() o affidati al posizionamento predefinito con inset: auto se è accettabile per il caso d'uso.

Opzioni di riserva e supporto del browser per gli attivatori di interesse

Gli attivatori di interesse devono essere sottoposti a polyfill condizionale utilizzando il pacchetto di polyfill interestfor di NPM. Preferisci l'aggregazione del polyfill all'utilizzo della CDN.

OBBLIGATORIO: esegui il rilevamento delle funzionalità controllando la proprietà interestForElement su HTMLButtonElement.prototype e carica il polyfill solo quando manca il supporto nativo.

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

Avvertenze sui polyfill CSS

Quando utilizzi il polyfill, l'API CSS cambia leggermente per tenere conto della mancanza di supporto nativo per pseudo-classi e proprietà:

  • Pseudo-classi: il polyfill non può definire pseudo-classi :interest-source e :interest-target reali, quindi applica le classi .interest-source e .interest-target. OBBLIGATORIO: combinali con :is() per supportare sia gli stati nativi che quelli con polyfill.
/* Styles to apply when the effect is being previewed */
:is(:interest-source, .interest-source) { /* ... */ }
:is(:interest-target, .interest-target) { /* ... */ }
  • Proprietà CSS: per interest-delay, interest-delay-start e interest-delay-end, il polyfill richiede l'utilizzo di variabili CSS, in quanto non può leggere le proprietà personalizzate direttamente in tutti i 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);
}