Las tarjetas flotantes proporcionan una vista previa interactiva del contenido cuando un usuario expresa interés en un vínculo (colocando el cursor sobre él o enfocándolo). Se muestra a los usuarios antes de que naveguen y les proporciona el contenido que necesitan o ayuda a verificar que el vínculo contendrá el contenido que buscan.
Si necesitas información sobre herramientas de texto sin formato no interactiva en lugar de vistas previas de vínculos estructuradas, consulta Muestra información sobre herramientas cuando se coloca el cursor sobre un elemento.
1. Cómo activar la tarjeta emergente
Usa la API de Interest Invokers para activar la tarjeta emergente. Esto garantiza que la tarjeta aparezca tanto al colocar el cursor sobre ella como al enfocarla, y controla automáticamente la conexión de accesibilidad.
- Agrega el atributo
popover="hint"al contenedor de tu tarjeta. Usahintpara evitar cerrar menús emergentes no relacionados que puedan estar abiertos. - Agrega el atributo
interestforal vínculo activador, que apunta alidde la tarjeta.
<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>
No es necesario que incluyas un vínculo adicional al contenido dentro de la tarjeta emergente, ya que es redundante con el vínculo de activación.
Accesibilidad integrada en interestfor
Cuando usas interestfor, el navegador controla el cableado de la tecnología de accesibilidad por ti:
- Semántica implícita: Un destino con
popover="hint"obtiene un rol mínimo implícito detooltip. NO configuresrole="tooltip"por tu cuenta. - Asociación implícita: El navegador asocia de forma implícita el elemento de origen con el destino a través de
aria-describedbycuando el destino es texto sin formato o a través dearia-detailscuando el destino contiene contenido interactivo. NO agreguesaria-describedbyniaria-detailsal activador. - Contenido interactivo: Debido a que la asociación cambia a
aria-detailscuando es necesario, el destino SÍ puede contener contenido interactivo (p.ej., un vínculo dentro de una "tarjeta de interés").
2. Posicionamiento con Anchor Positioning
Posiciona la tarjeta flotante en relación con su activador usando el posicionamiento de anclaje de CSS. Cuando se usa interestfor, el activador se convierte en un ancla implícita para el cuadro emergente.
[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. Cómo animar la entrada y la salida
De forma predeterminada, el interés se muestra y se pierde para los usuarios de teclado y mouse con un retraso de medio segundo. Esto evita que se abra el cuadro emergente de destino mientras navegas por una página o mueves el mouse, y que se cierre mientras mueves el mouse del activador al cuadro emergente. Para ajustar el tiempo, usa la propiedad de CSS interest-delay.
[interestfor]{
/* Shorthand for interest-delay-start and interest-delay-end */
interest-delay: 0.2s 0.1s;
}
Si es probable que los usuarios pasen por varios activadores en una sucesión rápida, establece el retraso inicial en 0s si ya hay un popover activado por interés abierto.
:root:has(:interest-source) [interestfor] {
interest-delay-start: 0s;
}
Usa @starting-style para definir la animación de entrada y transition-behavior: allow-discrete (junto con display y overlay) para animar la salida de la capa superior, lo que restringe las transiciones detrá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: Transición fluida a la siguiente página
De manera opcional, puedes usar las transiciones de vistas entre documentos para crear una animación de "transformación" fluida entre la tarjeta emergente y la página de destino. Consulta Transiciones coherentes entre documentos para obtener orientación sobre la implementación.
Si asignas valores de view-transition-name coincidentes a los elementos de la tarjeta emergente y a sus contrapartes en la página siguiente, el navegador los animará durante la navegación. Puedes nombrar varios elementos (p.ej., títulos, imágenes, firmas) para crear un efecto de transformación complejo con varios elementos. Asigna el view-transition-name a los elementos del cuadro emergente abierto con :popover-open para evitar crear view-transition-name duplicados o requerir view-transition-name específicos del vínculo.
/* 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;
}
}
OBLIGATORIO: Para garantizar que la transición sea estable y no parpadee, la página de destino debe usar blocking="render" y <link rel="expect"> para esperar a que se analice el contenido crítico antes de renderizar el primer fotograma.
<!-- 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;
}
Estrategias de resguardo
Debido a que el activador es un vínculo <a href="..."> estándar, los navegadores sin compatibilidad aún pueden navegar cuando se hace clic si eliges tratar la tarjeta emergente como una mejora progresiva. Cuando se requiera compatibilidad con tarjetas emergentes en varios navegadores, carga polyfills de forma condicional según sea necesario.
Resguardos y compatibilidad de navegadores para Popover
La API de Popover es, en su mayor parte, una mejora progresiva, pero sus comportamientos definitorios (promoción de la capa superior, cierre rápido y la invocación de popovertarget) no tienen un equivalente solo de CSS. Los navegadores más antiguos necesitan un polyfill o un resguardo manual si prefieres no enviar uno.
Polyfill: Para admitir el atributo popover en navegadores más antiguos, carga @oddbird/popover-polyfill de forma condicional. OBLIGATORIO: Detecta la función verificando la propiedad popover en HTMLElement.prototype y carga el polyfill solo cuando no haya compatibilidad nativa. NO lo cargues de forma incondicional.
Con un empaquetador o un mapa de importación:
// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
import("@oddbird/popover-polyfill");
}
Sin un empaquetador, importa desde una CDN dentro de 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>
Advertencia de diseño: El polyfill no puede definir la pseudoclase :popover-open real, por lo que aplica una clase .\:popover-open en su lugar. OBLIGATORIO: Combina las dos con :is() o :where(); de lo contrario, los navegadores que no tengan :popover-open descartarán toda la regla:
[popover]:is(:popover-open, .\:popover-open) {
display: block;
}
Como alternativa, para un resguardo heredado sin un polyfill, usa position: fixed y calcula manualmente las coordenadas a través de getBoundingClientRect() o confía en el posicionamiento predeterminado con inset: auto si eso es aceptable para el caso de uso.
Resguardos y compatibilidad con navegadores para los invocadores de interés
Los activadores de interés deben tener un polyfill condicional con el paquete de polyfill interestfor de NPM. Se prefiere agrupar el polyfill en lugar de usar la CDN.
OBLIGATORIO: Detecta la función verificando la propiedad interestForElement en HTMLButtonElement.prototype y carga el polyfill solo cuando no haya compatibilidad nativa.
<script type="module">
if (!HTMLButtonElement.prototype.hasOwnProperty("interestForElement")) {
// CDN link only used for example, prefer bundling.
await import("https://unpkg.com/interestfor@latest");
}
</script>
Advertencias sobre el polyfill de CSS
Cuando se usa el polyfill, la API de CSS cambia un poco para compensar la falta de compatibilidad con las pseudoclases y propiedades nativas:
- Pseudoclases: El polyfill no puede definir pseudoclases
:interest-sourcey:interest-targetreales, por lo que aplica las clases.interest-sourcey.interest-targeten su lugar. OBLIGATORIO: Combínalas con:is()para admitir los estados nativos y con polyfill.
/* Styles to apply when the effect is being previewed */
:is(:interest-source, .interest-source) { /* ... */ }
:is(:interest-target, .interest-target) { /* ... */ }
- Propiedades CSS: Para
interest-delay,interest-delay-startyinterest-delay-end, el polyfill requiere el uso de variables CSS, ya que no puede leer las propiedades personalizadas directamente en todos los 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);
}