Criar um card de informações interativo

Os cards de passagem mostram uma prévia interativa do conteúdo quando um usuário demonstra interesse em um link (passando o cursor ou focando nele). Isso é mostrado aos usuários antes de eles navegarem e fornece o conteúdo de que precisam ou ajuda a verificar se o link contém o conteúdo que estão procurando.

Se você precisar de dicas de texto simples e não interativas em vez de prévias de link estruturadas, consulte Mostrar uma dica ao passar o cursor.

1. Como acionar o cartão de informações

Use a API Interest Invokers para acionar o card de informações. Isso garante que o card apareça ao passar o cursor e ao focar, além de lidar automaticamente com a acessibilidade.

  • Adicione o atributo popover="hint" ao contêiner do cartão. Use hint para evitar o fechamento de menus popover não relacionados que possam estar abertos.
  • Adicione o atributo interestfor ao link de acionamento, apontando para o id do card.
<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>

Não é necessário incluir um link adicional para o conteúdo dentro do card de informações, já que ele é redundante em relação ao link de acionamento.

Acessibilidade integrada ao interestfor

Quando você usa interestfor, o navegador lida com a fiação da tecnologia adaptativa para você:

  • Semântica implícita: um destino com popover="hint" ganha um papel mínimo implícito de tooltip. NÃO defina role="tooltip" por conta própria.
  • Associação implícita: o navegador associa implicitamente o elemento de origem ao destino usando aria-describedby quando o destino é texto simples ou aria-details quando o destino contém conteúdo interativo. NÃO adicione aria-describedby ou aria-details ao gatilho.
  • Conteúdo interativo: como a associação muda para aria-details quando necessário, o destino pode conter conteúdo interativo (por exemplo, um link dentro de um "card de interesse").

2. Posicionamento com posicionamento de âncora

Posicione o card de informações em relação ao acionador usando o posicionamento de âncora CSS. Ao usar interestfor, o gatilho se torna uma âncora implícita para o 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. Como animar a entrada e a saída

Por padrão, o interesse é mostrado e perdido para usuários de teclado e mouse com um atraso de meio segundo. Isso impede que o popover segmentado seja aberto ao navegar por uma página ou mover o mouse, e que seja fechado ao mover o mouse do acionador para o popover. Para ajustar o tempo, use a propriedade CSS interest-delay.

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

Se os usuários provavelmente passarem por vários acionadores em rápida sucessão, defina o atraso inicial como 0s se um popover acionado por interesse já estiver aberto.

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

Use @starting-style para definir a animação de entrada e transition-behavior: allow-discrete (com display e overlay) para animar a saída da camada de cima, controlando as transições por trás de @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. OPCIONAL: transição perfeita para a próxima página

Se quiser, use as transições de visualização entre documentos para criar uma animação de "transformação" perfeita entre o card de informações e a página de destino. Consulte Transições consistentes entre documentos para orientações sobre a implementação.

Ao atribuir valores de view-transition-name correspondentes aos elementos no card de informações e às contrapartes deles na próxima página, o navegador vai animá-los durante a navegação. Você pode nomear vários elementos (por exemplo, títulos, imagens, assinaturas) para criar um efeito de transição complexo e com vários elementos. Atribua o view-transition-name aos elementos no popover aberto usando :popover-open para evitar a criação de view-transition-names duplicados ou a necessidade de view-transition-names específicos para links.

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

OBRIGATÓRIO: para garantir que a transição seja estável e não pisque, a página de destino precisa usar blocking="render" e <link rel="expect"> para aguardar a análise do conteúdo crítico antes de renderizar o primeiro 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;
}

Estratégias de fallback

Como o acionador é um link <a href="..."> padrão, os navegadores sem suporte ainda podem navegar ao clicar se você optar por tratar o hovercard como um aprimoramento progressivo. Quando o suporte a cards de informações em vários navegadores for necessário, carregue polyfills condicionalmente conforme necessário.

Alternativas e suporte do navegador para Popover

A API Popover é, em grande parte, um aprimoramento progressivo, mas os comportamentos que a definem (promoção de camada superior, fechamento leve e invocação de popovertarget) não têm equivalente somente em CSS. Os navegadores mais antigos precisam de um polyfill ou de um substituto manual, se você preferir não enviar um.

Polyfill: para oferecer suporte ao atributo popover em navegadores mais antigos, carregue condicionalmente @oddbird/popover-polyfill. OBRIGATÓRIO: detecte o recurso verificando a propriedade popover em HTMLElement.prototype e carregue o polyfill somente quando não houver suporte nativo. NÃO o carregue incondicionalmente.

Com um bundler ou mapa de importação:

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

Sem um bundler, importe de uma CDN dentro de um <script type="module">:

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

Observação sobre o estilo: o polyfill não pode definir a pseudoclasse :popover-open real. Por isso, ele aplica uma classe .\:popover-open. OBRIGATÓRIO: combine os dois com :is() ou :where(). Caso contrário, os navegadores que não têm :popover-open vão descartar toda a regra:

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

Como alternativa, para um substituto legado sem um polyfill, use position: fixed e calcule manualmente as coordenadas usando getBoundingClientRect() ou use o posicionamento padrão com inset: auto, se isso for aceitável para o caso de uso.

Alternativas e suporte do navegador para invocadores de interesse

Os invocadores de interesse precisam ser preenchidos condicionalmente usando o pacote de polyfill interestfor do NPM. Prefira agrupar o polyfill em vez de usar a CDN.

OBRIGATÓRIO: detecte o recurso verificando a propriedade interestForElement em HTMLButtonElement.prototype e carregue o polyfill somente quando não houver suporte 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>

Limitações do polyfill de CSS

Ao usar o polyfill, a API CSS muda um pouco para compensar a falta de suporte nativo a pseudoclasses e propriedades:

  • Pseudoclasses: o polyfill não pode definir pseudoclasses :interest-source e :interest-target reais. Por isso, ele aplica as classes .interest-source e .interest-target. OBRIGATÓRIO: combine-os com :is() para oferecer suporte aos estados nativos e polyfilled.
/* Styles to apply when the effect is being previewed */
:is(:interest-source, .interest-source) { /* ... */ }
:is(:interest-target, .interest-target) { /* ... */ }
  • Propriedades CSS: para interest-delay, interest-delay-start e interest-delay-end, o polyfill exige o uso de variáveis CSS, já que não pode ler as propriedades personalizadas diretamente em todos os navegadores.
/* 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);
}