Ventanas emergentes: ¡Están resurgiendo!

El objetivo de la iniciativa de IU abierta es facilitar a los desarrolladores la creación de experiencias del usuario excelentes. Para lograrlo, estamos tratando de abordar los patrones más problemáticos a los que se enfrentan los desarrolladores. Podemos hacerlo proporcionando mejores componentes y APIs integrados en la plataforma.

Una de estas áreas problemáticas son las ventanas emergentes, que se describen en Open UI como "Ventanas emergentes".

Los popovers tienen una reputación bastante polarizante durante mucho tiempo. Esto se debe, en parte, a la forma en que se compilan y se implementan. No son un patrón fácil de desarrollar bien, pero pueden generar mucho valor al dirigir a los usuarios a ciertas cosas o al dar a conocer el contenido de tu sitio, especialmente cuando se usan con buen gusto.

A menudo, existen dos inquietudes importantes cuando se crean ventanas emergentes:

  • Cómo asegurarte de que se coloque encima del resto de tu contenido en un lugar adecuado.
  • Cómo hacerla accesible (opción optimizada para teclado, enfocable, etc.)

La API de Popover integrada tiene varios objetivos, todos con el mismo objetivo general de facilitar la creación de este patrón a los desarrolladores. Algunos de esos objetivos son los siguientes:

  • Facilita la visualización de un elemento y sus elementos subordinados sobre el resto del documento.
  • Haz que sea accesible.
  • No requiere JavaScript para los comportamientos más comunes (descarte ligero, singleton, apilado, etc.).

Puedes consultar la especificación completa de las ventanas emergentes en el sitio de OpenUI.

Compatibilidad del navegador

¿Dónde puedes usar la API de Popover integrada ahora? Es compatible con Chrome Canary detrás de la marca "Funciones de plataforma web experimental" en el momento de la redacción.

Para habilitar esa función experimental, abre Chrome Canary y visita chrome://flags. Luego, habilita la marca “Funciones de plataforma web experimental”.

También hay una prueba de origen para los desarrolladores que quieran probarla en un entorno de producción.

Por último, hay un polyfill en desarrollo para la API. Asegúrate de consultar el repo en github.com/oddbird/popup-polyfill.

Puedes usar los siguientes recursos para comprobar la compatibilidad con ventanas emergentes:

const supported = HTMLElement.prototype.hasOwnProperty("popover");

Soluciones actuales

¿Qué puedes hacer actualmente para promocionar tu contenido por encima de todo lo demás? Si es compatible con tu navegador, puedes usar el elemento de diálogo HTML. Deberías usarlo en formato "Modal". Para esto, se requiere JavaScript.

Dialog.showModal();

Hay algunas consideraciones de accesibilidad. Por ejemplo, se recomienda usar a11y-dialog si apuntas a contenido para usuarios de Safari con versiones anteriores a la 15.4.

También puedes usar una de las muchas bibliotecas basadas en ventanas emergentes, alertas o información sobre la herramienta que existen. Muchos de ellos suelen funcionar de manera similar.

  • Agrega algún contenedor al cuerpo para mostrar las ventanas emergentes.
  • Pédale un estilo que se sitúe por encima de todo lo demás.
  • Crea un elemento y agrégalo al contenedor para mostrar una ventana emergente.
  • Para ocultarlo, quita el elemento de ventana emergente del DOM.

Esto requiere una dependencia adicional y más decisiones para los desarrolladores. También requiere investigación para encontrar una oferta que proporcione todo lo que necesitas. El objetivo de la API de Popover es satisfacer muchas situaciones, incluidos los cuadros de información. El objetivo es abarcar todas esas situaciones comunes y evitar que los desarrolladores tengan que tomar otra decisión para poder enfocarse en crear sus experiencias.

Tu primera ventana emergente

Esto es todo lo que necesitas.

<div id="my-first-popover" popover>Popover Content!</div>
<button popovertoggletarget="my-first-popover">Toggle Popover</button>

Pero, ¿qué sucede aquí?

  • No es necesario que coloques el elemento de ventana emergente en un contenedor ni nada, ya que está oculto de forma predeterminada.
  • No es necesario que escribas nada para que aparezca. Eso se controla con el atributo popovertoggletarget.
  • Cuando aparece, se traslada a la capa superior. Eso significa que se promueve por encima del document en el viewport. No es necesario que administres z-index ni te preocupes por dónde se encuentra la ventana emergente en el DOM. Podría estar profundamente anidada en el DOM, con antecesores de recorte. También puede ver qué elementos se encuentran actualmente en la capa superior a través de Herramientas para desarrolladores. Para obtener más información sobre la capa superior, consulta este artículo.

GIF de compatibilidad con la capa superior de Herramientas para desarrolladores que se muestra

  • Obtienes la opción de "descarte ligero" de inmediato. Con esto queremos decir que puedes cerrar la ventana emergente con un indicador de cierre, como hacer clic fuera de la ventana emergente, navegar con el teclado a otro elemento o presionar la tecla Esc. Ábrelo de nuevo y pruébalo.

¿Qué más se obtiene con popover? Avancemos con el ejemplo. Considera esta demostración con algo de contenido en la página.

Ese botón de acción flotante tiene un posicionamiento fijo con una z-index alta.

.fab {
  position: fixed;
  z-index: 99999;
}

El contenido de la ventana emergente está anidado en el DOM, pero cuando lo abres, se promueve sobre ese elemento de posición fija. No es necesario establecer ningún estilo.

También puedes notar que la ventana emergente ahora tiene un seudoelemento ::backdrop. Todos los elementos que se encuentran en la capa superior obtienen un seudoelemento ::backdrop con estilo. En este ejemplo, se diseña ::backdrop con un color de fondo alfa reducido y un filtro de fondo, que desenfoca el contenido subyacente.

Cómo diseñar una ventana emergente

Concentremos nuestra atención en el estilo del popover. De forma predeterminada, un elemento emergente tiene una posición fija y aplica padding. También cuenta con display: none. Podrías anular esta opción para mostrar una ventana emergente. Sin embargo, no se incluiría en la capa superior.

[popover] { display: block; }

Independientemente de la forma en que promociones una ventana emergente, una vez que la hagas en la capa superior, es posible que debas organizarla o posicionarla. No puedes orientarte a la capa superior y hacer algo como lo siguiente:

:open {
  display: grid;
  place-items: center;
}

De forma predeterminada, se mostrará una ventana emergente en el centro del viewport con margin: auto. Sin embargo, en algunos casos, es posible que desees ser explícito sobre el posicionamiento. Por ejemplo:

[popover] {
  top: 50%;
  left: 50%;
  translate: -50%;
}

Si deseas diseñar el contenido dentro de la ventana emergente con la cuadrícula de CSS o flexbox, te recomendamos unirlo en un elemento. De lo contrario, deberás declarar una regla separada que cambie el elemento display una vez que la ventana emergente esté en la capa superior. Si se establece de forma predeterminada, se mostrará de forma predeterminada anulando display: none.

[popover]:open {
 display: flex;
}

Si probaste esa demostración, notarás que la ventana emergente ahora aparece y desaparece. Puedes hacer la transición de las ventanas emergentes hacia adentro y afuera mediante el seudoselector :open. El seudoselector :open coincide con las ventanas emergentes que se muestran (y, por lo tanto, en la capa superior).

En este ejemplo, se usa una propiedad personalizada para impulsar la transición. También puedes aplicar una transición al elemento ::backdrop de la ventana emergente.

[popover] {
  --hide: 1;
  transition: transform 0.2s;
  transform: translateY(calc(var(--hide) * -100vh))
            scale(calc(1 - var(--hide)));
}

[popover]::backdrop {
  transition: opacity 0.2s;
  opacity: calc(1 - var(--hide, 1));
}


[popover]:open::backdrop  {
  --hide: 0;
}

Una sugerencia es agrupar las transiciones y animaciones en una consulta de medios para el movimiento. Esto también puede ayudarte a mantener tus tiempos. Esto se debe a que no puedes compartir valores entre popover y ::backdrop a través de la propiedad personalizada.

@media(prefers-reduced-motion: no-preference) {
  [popover] { transition: transform 0.2s; }
  [popover]::backdrop { transition: opacity 0.2s; }
}

Hasta este punto, viste el uso de popovertoggletarget para mostrar una ventana emergente. Para descartarla, usaremos la opción para descartarlo ligeramente. Sin embargo, también obtienes los atributos popovershowtarget y popoverhidetarget que puedes usar. Agreguemos un botón a una ventana emergente que la oculte y cambiemos el botón de activación para usar popovershowtarget.

<div id="code-popover" popover>
  <button popoverhidetarget="code-popover">Hide Code</button>
</div>
<button popovershowtarget="code-popover">Reveal Code</button>

Como se mencionó antes, la API de Popover abarca más que solo nuestra noción histórica de ventanas emergentes. Puedes compilar para todo tipo de situaciones, como notificaciones, menús, información sobre la herramienta, etcétera.

Algunas de esas situaciones necesitan patrones de interacción diferentes. Las interacciones, como colocar el cursor sobre un elemento. Se experimentó el uso de un atributo popoverhovertarget, pero todavía no está implementado.

<div popoverhovertarget="hover-popover">Hover for Code</div>

La idea es que coloques el cursor sobre un elemento para mostrar el objetivo. Este comportamiento se podría configurar a través de las propiedades de CSS. Estas propiedades de CSS definirían el período durante el cual se colocará el cursor sobre un elemento al que reacciona una ventana emergente. El comportamiento predeterminado con el que se experimentó tuvo un programa de ventana emergente después de un elemento 0.5s explícito de :hover. Luego, se necesitaría un descarte ligero o la apertura de otra ventana emergente para descartar (más detalles a continuación). Esto se debió a que la duración de la ocultación de las ventanas emergentes se estableció en Infinity.

Mientras tanto, puedes usar JavaScript para polyfills de esa funcionalidad.

let hoverTimer;
const HOVER_TRIGGERS = document.querySelectorAll("[popoverhovertarget]");
const tearDown = () => {
  if (hoverTimer) clearTimeout(hoverTimer);
};
HOVER_TRIGGERS.forEach((trigger) => {
  const popover = document.querySelector(
    `#${trigger.getAttribute("popoverhovertarget")}`
  );
  trigger.addEventListener("pointerenter", () => {
    hoverTimer = setTimeout(() => {
      if (!popover.matches(":open")) popover.showPopOver();
    }, 500);
    trigger.addEventListener("pointerleave", tearDown);
  });
});

El beneficio de configurar algo en una ventana emergente explícita es que garantiza que la acción del usuario sea intencional (por ejemplo, un usuario pasa el puntero sobre un objetivo). No queremos mostrar la ventana emergente a menos que esa sea su intención.

Prueba esta demostración en la que puedes colocar el cursor sobre el objetivo con la ventana configurada en 0.5s.


Antes de explorar algunos casos de uso y ejemplos comunes, repasemos algunos aspectos.


Tipos de ventanas emergentes

Abordamos el comportamiento de interacciones que no son de JavaScript. Pero ¿qué sucede con el comportamiento de las ventanas emergentes en su conjunto? ¿Qué sucede si no quieres usar la opción "Descartar a la ligera"? ¿O quieres aplicar un patrón singleton a tus ventanas emergentes?

La API de Popover te permite especificar tres tipos de ventanas emergentes que difieren en comportamiento.

[popover=auto]/[popover]:

  • Compatibilidad con la anidación Esto no significa que solo esté anidado en el DOM. La definición de popover ancestral es uno de los siguientes:
    • relacionadas por la posición del DOM (elemento secundario).
    • relacionadas activando atributos en elementos secundarios, como popovertoggletarget, popovershowtarget, etcétera
    • relacionadas por el atributo anchor (API de CSS Anchoring en desarrollo).
  • Descarte ligero.
  • Cuando lo abres, se descartan otras ventanas emergentes que no son popovers ancestrales. Prueba la siguiente demostración que destaca cómo funciona la anidación con popovers ancestrales. Observa cómo cambiar algunas de las instancias popoverhidetarget/popovershowtarget a popovertoggletarget cambia las cosas.
  • Cuando descartas uno ligero, se descartan todos, pero si se descarta uno de la pila, solo se descartan aquellos que están por encima de él en la pila.
.

[popover=manual]:

  • No cierra otras ventanas emergentes.
  • Sin descarte ligero.
  • Se requiere el descarte explícito mediante el elemento activador o JavaScript.

API de JavaScript

Cuando necesites tener más control sobre las ventanas emergentes, puedes utilizar JavaScript. Obtienes un método showPopover y hidePopover. También tienes eventos popovershow y popoverhide para escuchar:

Mostrar una ventana emergente js popoverElement.showPopover() Oculta una ventana emergente:

popoverElement.hidePopover()

Escucha que se muestre una ventana emergente:

popoverElement.addEventListener('popovershow', doSomethingWhenPopoverShows)

Escucha que se muestre una ventana emergente y cancélala:

popoverElement.addEventListener('popovershow',event => {
  event.preventDefault();
  console.warn(‘We blocked a popover from being shown’);
})

Escuchar si se oculta una ventana emergente:

popoverElement.addEventListener('popoverhide', doSomethingWhenPopoverHides)

No puedes cancelar una ventana emergente que se oculta:

popoverElement.addEventListener('popoverhide',event => {
  event.preventDefault();
  console.warn("You aren't allowed to cancel the hiding of a popover");
})

Verifica si una ventana emergente se encuentra en la capa superior:

popoverElement.matches(':open')

De esta manera, obtendrás energía adicional para algunas situaciones menos comunes. Por ejemplo, muestra una ventana emergente después de un período de inactividad.

Esta demostración tiene ventanas emergentes con elementos emergentes audibles, por lo que necesitaremos JavaScript para reproducir el audio. Cuando se hace clic, se oculta la ventana emergente, se reproduce el audio y se vuelve a mostrar.

Accesibilidad

La accesibilidad es la prioridad principal con la API de Popover. Las asignaciones de accesibilidad asocian la ventana emergente con su elemento activador, según sea necesario. Esto significa que no es necesario declarar atributos aria-*, como aria-haspopup, suponiendo que usas uno de los atributos de activación como popovertoggletarget.

Para la administración del enfoque, puedes usar el atributo de enfoque automático para mover el enfoque a un elemento dentro de una ventana emergente. Esto es lo mismo que para un diálogo, pero la diferencia se produce cuando se muestra el enfoque y se debe al descarte ligero. En la mayoría de los casos, al cerrar una ventana emergente, se muestra el foco en el elemento que tenía anteriormente. Sin embargo, el enfoque se mueve a un elemento en el que se hizo clic cuando se descarta a la luz, si puede obtener el foco. Consulta la sección sobre administración del enfoque en la explicación.

Para ver cómo funciona, deberás abrir la "versión en pantalla completa" de esta demostración.

En esta demostración, el elemento enfocado tiene un contorno verde. Prueba desplazarte por la interfaz con el teclado. Observa dónde se muestra el enfoque cuando se cierra una ventana emergente. También es posible que notes que si usaste pestañas, la ventana emergente se cerró. Es así de diseño. Aunque las ventanas emergentes tienen control del enfoque, no atrapan el enfoque. Además, la navegación con teclado identifica una señal de cierre cuando el enfoque se mueve del elemento emergente.

Anclaje (en desarrollo)

Cuando se trata de ventanas emergentes, un patrón complicado que debes satisfacer es fijar el elemento a su activador. Por ejemplo, si se configura un cuadro de información para que aparezca por encima de su activador, pero se desplaza el documento. El viewport podría cortar la información sobre la herramienta. Hay ofertas actuales de JavaScript para abordar esto, como la “IU flotante”. Reubicarán el cuadro de información para evitar que esto suceda y dependerán del orden de posición deseado.

Pero queremos que puedas definir esto con tus estilos. Hay una API complementaria en desarrollo junto con la API de Popover para abordar esto. La API de “CSS Anchor Positioning” te permitirá anclar elementos a otros elementos de una manera que reposiciona los elementos de modo que el viewport no los corte.

En esta demostración, se usa la API de Anchoring en su estado actual. La posición del barco responde a la posición del ancla en la viewport.

Este es un fragmento del CSS que hace que esta demostración funcione. No se requiere JavaScript.

.anchor {
  --anchor-name: --anchor;
}
.anchored {
  position: absolute;
  position-fallback: --compass;
}
@position-fallback --compass {
  @try {
    bottom: anchor(--anchor top);
    left: anchor(--anchor right);
  }
  @try {
    top: anchor(--anchor bottom);
    left: anchor(--anchor right);
  }
}

Puedes consultar las especificaciones aquí. También habrá un polyfill para esta API.

Ejemplos

Ahora que ya sabes qué puede ofrecer el popover y cómo hacerlo, analicemos algunos ejemplos.

Notificaciones

Esta demostración muestra una notificación de “Copiar en el portapapeles”.

  • Usa [popover=manual]
  • On Action, muestra una ventana emergente con showPopover.
  • Después de un tiempo de espera de 2000ms, ocúltalo con hidePopover.

Avisos

Esta demostración usa la capa superior para mostrar notificaciones de avisos.

  • Como contenedor, hay una ventana emergente con el tipo manual.
  • Se agregan nuevas notificaciones a la ventana emergente y esta se muestra.
  • Se quitan con la API de animaciones web cuando se hace clic y se quitan del DOM.
  • Si no hay avisos para mostrar, se oculta la ventana emergente.

Menú anidado

Esta demostración muestra cómo podría funcionar un menú de navegación anidado.

  • Usa [popover=auto], ya que permite ventanas emergentes anidadas.
  • Usa autofocus en el primer vínculo de cada menú desplegable para navegar con el teclado.
  • Esta es una candidata perfecta para la API de CSS Anchoring. Sin embargo, para esta demostración puedes usar una pequeña cantidad de JavaScript para actualizar las posiciones con propiedades personalizadas.
const ANCHOR = (anchor, anchored) => () => {
  const { top, bottom, left, right } = anchor.getBoundingClientRect();
  anchored.style.setProperty("--top", top);
  anchored.style.setProperty("--right", right);
  anchored.style.setProperty("--bottom", bottom);
  anchored.style.setProperty("--left", left);
};

PRODUCTS_MENU.addEventListener("popovershow", ANCHOR(PRODUCT_TARGET, PRODUCTS_MENU));

Recuerda que, como esta demostración usa autofocus, se deberá abrir en "vista de pantalla completa" para la navegación con el teclado.

Ventana emergente de medios

En esta demostración, se muestra cómo se pueden crear ventanas emergentes de contenido multimedia.

  • Usa [popover=auto] para el descarte ligero.
  • JavaScript escucha el evento play del video y muestra el video en una ventana emergente.
  • El evento popoverhide de ventanas emergentes pausa el video.
.

Ventanas emergentes al estilo Wiki

En estas demostraciones, se muestra cómo puedes crear información sobre la herramienta de contenido intercalado que tenga contenido multimedia.

  • Usa [popover=auto] Mostrar uno oculta a los demás porque no son ancestrales.
  • Se muestra en pointerenter con JavaScript.
  • Otra candidata perfecta para la API de CSS Anchoring.

Esta demostración crea un panel lateral de navegación mediante una ventana emergente.

  • Usa [popover=auto] para el descarte ligero.
  • Usa autofocus para enfocar el primer elemento de navegación.

Cómo administrar fondos

En esta demostración, se muestra cómo puedes administrar fondos para varios elementos emergentes en los que solo quieres que se vea un ::backdrop.

  • Usa JavaScript para mantener una lista de las ventanas emergentes visibles.
  • Aplica un nombre de clase a la ventana emergente más baja de la capa superior.
.

Ventana emergente de cursor personalizado

En esta demostración, se muestra cómo usar popover para promover un canvas a la capa superior y usarlo para mostrar un cursor personalizado.

  • Asciende canvas a la capa superior con showPopover y [popover=manual].
  • Cuando se abran otras ventanas emergentes, oculta y muestra la ventana emergente canvas para asegurarte de que esté en la parte superior.

Ventana emergente de la hoja de acción

Esta demostración muestra cómo se puede usar una ventana emergente como hoja de acciones.

  • Haz que la ventana emergente se muestre de forma predeterminada anulando display.
  • La hoja de acciones está abierta con el activador de ventana emergente.
  • Cuando se muestra la ventana emergente, se traslada a la capa superior y se traduce a la vista.
  • Puedes usar el descarte ligero para devolverlo.

Ventana emergente activada por teclado

En esta demostración, se muestra cómo podrías usar una ventana emergente para la IU de estilo de paleta de comandos.

  • Usa cmd + j para mostrar la ventana emergente.
  • La input se enfoca con autofocus.
  • El cuadro combinado es un segundo popover ubicado debajo de la entrada principal.
  • El descarte claro cierra la paleta si el menú desplegable no está presente.
  • Otro candidato para la API de Anchoring
.

Ventana emergente programada

En esta demostración, se muestra una ventana emergente de inactividad después de cuatro segundos. Patrón de IU que se usa a menudo en apps que contienen información segura sobre un usuario para mostrar una ventana modal de salida.

  • Usa JavaScript para mostrar la ventana emergente después de un período de inactividad.
  • Restablece el temporizador en el programa emergente.
.

Protector

Al igual que en la demostración anterior, puedes agregar un toque de extravagancia a tu sitio y un protector de pantalla.

  • Usa JavaScript para mostrar la ventana emergente después de un período de inactividad.
  • Descarte de luz para ocultar y restablecer el temporizador

Seguimiento del signo de intercalación

En esta demostración, se muestra cómo hacer que una ventana emergente siga un signo de intercalación de entrada.

  • Muestra la ventana emergente según la selección, el evento clave o la entrada de caracteres especiales.
  • Usa JavaScript para actualizar la posición de la ventana emergente con propiedades personalizadas con alcance.
  • Este patrón requeriría un pensamiento considerado con respecto al contenido que se muestra y la accesibilidad.
  • Se suele ver en la IU de edición de texto y en las apps en las que puedes etiquetar.

Menú de botones de acción flotantes

En esta demostración, se muestra cómo usar una ventana emergente para implementar un menú de botones de acción flotante sin JavaScript.

  • Promueve una ventana emergente de tipo manual con el método showPopover. Este es el botón principal.
  • El menú es otra ventana emergente que es el destino del botón principal.
  • Se abre el menú con popovertoggletarget.
  • Usa autofocus para enfocar el primer elemento del menú que se muestra.
  • Si descartas la luz, se cerrará el menú.
  • El giro del ícono usa :has(). Puedes obtener más información sobre :has() en este artículo.

Listo.

Esta es una introducción a los anuncios emergentes, que se incorporarán en el camino como parte de la iniciativa de Open UI. Si se usa como corresponde, será una adición fantástica a la plataforma web.

Asegúrate de consultar Abrir IU. La explicación de ventana emergente se mantiene actualizada a medida que evoluciona la API. Y aquí está la colección de todas las demostraciones.

Gracias por acompañarnos


Foto de Madison Oren en Unsplash