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. Usehintpara evitar o fechamento de menus popover não relacionados que possam estar abertos. - Adicione o atributo
interestforao link de acionamento, apontando para oiddo 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 detooltip. NÃO definarole="tooltip"por conta própria. - Associação implícita: o navegador associa implicitamente o elemento de origem ao destino usando
aria-describedbyquando o destino é texto simples ouaria-detailsquando o destino contém conteúdo interativo. NÃO adicionearia-describedbyouaria-detailsao gatilho. - Conteúdo interativo: como a associação muda para
aria-detailsquando 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-sourcee:interest-targetreais. Por isso, ele aplica as classes.interest-sourcee.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-starteinterest-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);
}