Ventanas emergentes: ¡Están resurgiendo!

El objetivo de la iniciativa de IU abierta es facilitar que los desarrolladores creen experiencias del usuario excelentes. Para ello, intentamos abordar los patrones más problemáticos que enfrentan los desarrolladores. Para ello, proporcionamos mejores APIs y componentes integrados en la plataforma.

Un área problemática de este tipo son las ventanas emergentes, que se describen en la IU abierta como "Ventanas emergentes".

Los pop-ups tienen una reputación bastante polarizante desde hace mucho tiempo. Esto se debe, en parte, a la forma en que se compilan y se implementan. No es un patrón fácil de crear, pero puede ser muy valioso porque dirige a los usuarios a ciertos elementos o les informa sobre el contenido de tu sitio, especialmente cuando se usa con buen gusto.

A menudo, existen dos inquietudes principales cuando se compilan los pop-ups:

  • Cómo asegurarte de que se coloque sobre el resto de tu contenido en un lugar adecuado
  • Cómo hacerlo accesible (compatible con el teclado, enfocable, etcétera)

La API de Popover integrada tiene una variedad de objetivos, todos con el mismo objetivo general de facilitar a los desarrolladores la compilación de este patrón. Entre los objetivos más destacados, se encuentran los siguientes:

  • Facilita la visualización de un elemento y sus elementos secundarios sobre el resto del documento.
  • Haz que sea accesible.
  • No requiere JavaScript para la mayoría de los comportamientos comunes (desactivación ligera, singleton, apilamiento, etcétera).

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? Al momento de escribir este artículo, es compatible con Chrome Canary detrás de la marca "Funciones experimentales de la plataforma web".

Para habilitar esa marca, abre Chrome Canary y visita chrome://flags. Luego, habilita la marca "Funciones experimentales de la plataforma web".

También hay una prueba de Origin para los desarrolladores que quieran probar esto en un entorno de producción.

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

Puedes verificar la compatibilidad con ventanas emergentes con las siguientes opciones:

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

Soluciones actuales

¿Qué puedes hacer actualmente para promocionar tu contenido por sobre todo lo demás? Si es compatible con tu navegador, puedes usar el elemento de diálogo HTML. Deberás usarla en el formulario "Modal". Para ello, se requiere JavaScript.

Dialog.showModal();

Hay algunas consideraciones de accesibilidad. Se recomienda usar a11y-dialog, por ejemplo, si se atiende a usuarios de Safari anteriores a la versión 15.4.

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

  • Agrega un contenedor al cuerpo para mostrar los pop-overs.
  • Dale un estilo para que se ubique sobre todo lo demás.
  • Crea un elemento y adjúntalo al contenedor para mostrar un cuadro flotante.
  • Para ocultarlo, quita el elemento de popover 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, incluidas las herramientas de ayuda. El objetivo es cubrir todas esas situaciones comunes, lo que evita que los desarrolladores tengan que tomar otra decisión para que puedan enfocarse en crear sus experiencias.

Tu primera ventana emergente

Eso es todo lo que necesitas.

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

Pero, ¿qué está pasando aquí?

  • No es necesario que coloques el elemento de popover en un contenedor ni nada, ya que está oculto de forma predeterminada.
  • No es necesario que escribas ningún código JavaScript para que aparezca. El atributo popovertoggletarget controla eso.
  • Cuando aparece, se promociona a la capa superior. Eso significa que se promociona por encima de document en la ventana de visualización. No tienes que administrar z-index ni preocuparte por dónde está tu popover en el DOM. Podría estar anidado en el DOM, con ancestros de recorte. También puedes ver qué elementos se encuentran actualmente en la capa superior a través de DevTools. Para obtener más información sobre la capa superior, consulta este artículo.

GIF de la demostración de compatibilidad con la capa superior de DevTools

  • La función "Desactivar con la luz" está disponible de forma predeterminada. Es decir, puedes cerrar el cuadro emergente con un indicador de cierre, como hacer clic fuera del cuadro emergente, navegar con el teclado a otro elemento o presionar la tecla Esc. Vuelve a abrirlo y pruébalo.

¿Qué más obtienes con los pop-overs? Analicemos el ejemplo con más detalle. Considera esta demostración con algunos elementos de contenido en la página.

Ese botón de acción flotante tiene una posición fija con un z-index alto.

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

El contenido del cuadro emergente está anidado en el DOM, pero cuando lo abres, se promociona por encima de ese elemento de posición fija. No es necesario que configures ningún estilo.

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

Cómo aplicar diseño a un popover

Enfoquémonos en aplicar diseño al popover. De forma predeterminada, un popover tiene una posición fija y un padding aplicado. También tiene display: none. Puedes anular esta acción para mostrar un popover. Sin embargo, eso no lo promocionaría a la capa superior.

[popover] { display: block; }

Independientemente de cómo promociones tu popover, una vez que lo promociones a la capa superior, es posible que debas diseñarlo o posicionarlo. No puedes segmentar la capa superior y hacer algo como lo siguiente:

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

De forma predeterminada, se colocará un cuadro flotante 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 del popover con la cuadrícula de CSS o flexbox, te recomendamos que lo unes en un elemento. De lo contrario, deberás declarar una regla independiente que cambie el display una vez que el popover esté en la capa superior. Si se configura de forma predeterminada, se mostrará de forma predeterminada anulando display: none.

[popover]:open {
 display: flex;
}

Si probaste esa demostración, notarás que el pop-up ahora se abre y se cierra. Puedes mostrar y ocultar los pop-overs con el pseudoselector :open. El pseudoselector :open coincide con los pop-overs que se muestran (y, por lo tanto, en la capa superior).

En este ejemplo, se usa una propiedad personalizada para controlar la transición. También puedes aplicar una transición al ::backdrop del popover.

[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 aquí es agrupar las transiciones y las animaciones en una consulta de contenido multimedia para el movimiento. Esto también puede ayudarte a mantener los 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 un popover. Para descartarlo, usaremos "Light dismiss". Sin embargo, también obtienes los atributos popovershowtarget y popoverhidetarget que puedes usar. Agreguemos un botón a un popover que lo 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ó anteriormente, 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, cuadros de herramientas, etcétera.

Algunas de esas situaciones requieren diferentes patrones de interacción. Interacciones como colocar el cursor sobre un elemento Se experimentó con el uso de un atributo popoverhovertarget, pero actualmente 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 puede configurar a través de propiedades CSS. Estas propiedades CSS definirían el período para colocar el cursor sobre un elemento y quitarlo, al que reacciona un popover. El comportamiento predeterminado con el que se experimentó tenía un cuadro emergente que se mostraba después de un 0.5s explícito de :hover. Luego, se necesitaría una anulación ligera o la apertura de otro popover para descartarlo (más información sobre esto a continuación). Esto se debía a que la duración de ocultación del popover se estableció en Infinity.

Mientras tanto, puedes usar JavaScript para realizar la polyfill 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 establecer una ventana de desplazamiento 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 establecida en 0.5s.


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


Tipos de ventanas emergentes

Analizamos el comportamiento de interacción que no es de JavaScript. Pero ¿qué sucede con el comportamiento del popover en su totalidad? ¿Qué sucede si no quieres usar la opción "Ocultar con luz"? ¿O quieres aplicar un patrón singleton a tus popovers?

La API de Popover te permite especificar tres tipos de popovers que difieren en el comportamiento.

[popover=auto]/[popover]:

  • Compatibilidad con anidación Esto no solo significa que está anidado en el DOM. La definición de un popover ancestral es la siguiente:
    • relacionados por la posición del DOM (secundarios).
    • relacionados activando atributos en elementos secundarios, como popovertoggletarget, popovershowtarget, etcétera.
    • relacionados por el atributo anchor (API de anclaje de CSS en desarrollo).
  • Descartar luz
  • La apertura descarta otros popovers que no sean popovers ancestrales. Prueba la siguiente demostración que destaca cómo funciona el anidamiento con popovers ancestrales. Observa cómo cambia la situación si cambias algunas de las instancias de popoverhidetarget/popovershowtarget a popovertoggletarget.
  • Si se rechaza una notificación de luz, se rechazan todas, pero si se rechaza una en la pila, solo se rechazan las que están encima de ella.

[popover=manual]:

  • No cierra otros popovers.
  • No se apaga la luz.
  • Requiere una anulación explícita a través del elemento activador o JavaScript.

API de JavaScript

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

Cómo mostrar una ventana emergente js popoverElement.showPopover() Cómo ocultar una ventana emergente:

popoverElement.hidePopover()

Escucha si se muestra un popover:

popoverElement.addEventListener('popovershow', doSomethingWhenPopoverShows)

Escucha si se muestra un popover y cancela su visualización:

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

Detecta cuando se oculta un popover:

popoverElement.addEventListener('popoverhide', doSomethingWhenPopoverHides)

No puedes cancelar la ocultación de un popover:

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

Verifica si hay un cuadro emergente en la capa superior:

popoverElement.matches(':open')

Esto proporciona potencia adicional para algunas situaciones menos comunes. Por ejemplo, muestra un popover después de un período de inactividad.

Esta demostración tiene ventanas emergentes con sonidos audibles, por lo que necesitaremos JavaScript para reproducir el audio. Cuando se hace clic, ocultamos el popover, reproducimos el audio y, luego, lo volvemos a mostrar.

Accesibilidad

La accesibilidad está en la vanguardia del pensamiento con la API de Popover. Las asignaciones de accesibilidad asocian el popover con su elemento activador, según sea necesario. Esto significa que no necesitas declarar atributos aria-*, como aria-haspopup, siempre que uses 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 un popover. Esto es lo mismo que para un diálogo, pero la diferencia se produce cuando se devuelve el enfoque, y eso se debe a la eliminación ligera. En la mayoría de los casos, cerrar un menú flotante restablece el enfoque en el elemento que tenía el foco anteriormente. Sin embargo, el enfoque se mueve a un elemento en el que se hizo clic cuando se descarta la luz, si puede obtener el enfoque. Consulta la sección sobre la administración de enfoque en la explicación.

Deberás abrir la "versión de pantalla completa" de esta demostración para ver cómo funciona.

En esta demostración, el elemento enfocado tiene un contorno verde. Intenta usar la tecla Tab para desplazarte por la interfaz con el teclado. Ten en cuenta dónde se muestra el enfoque cuando se cierra un menú flotante. También es posible que notes que, si presionaste Tab, se cerró el menú flotante. Eso es por diseño. Aunque los popovers tienen administración de enfoque, no lo atrapan. Además, la navegación con el teclado identifica una señal de cierre cuando el enfoque sale del popover.

Anclaje (en desarrollo)

En el caso de los popovers, un patrón difícil de abordar es fijar el elemento a su activador. Por ejemplo, si se configura una información sobre herramientas para que se muestre sobre su activador, pero se desplaza el documento. Es posible que el viewport corte esa información sobre herramientas. Existen ofertas actuales de JavaScript para abordar este problema, como "IU flotante". Recolocarán la información sobre herramientas para que esto no suceda y se base en un orden de posición deseado.

Sin embargo, queremos que puedas definir esto con tus estilos. Hay una API complementaria en desarrollo junto con la API de Popover para abordar este problema. La API de "CSS Anchor Positioning" te permitirá conectar elementos a otros y lo hará de una manera que los vuelva a posicionar para que la vista del puerto 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 de la ancla en el viewport.

Este es un fragmento del CSS que hace que esta demostración funcione. No es necesario usar 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 conoces lo que ofrece el popover y cómo funciona, analicemos algunos ejemplos.

Notificaciones

En esta demostración, se muestra una notificación "Copiar en el portapapeles".

  • Usa [popover=manual]
  • En la acción, muestra el menú flotante con showPopover.
  • Después de un tiempo de espera de 2000ms, ocúltalo con hidePopover.

Avisos

En esta demostración, se usa la capa superior para mostrar notificaciones de estilo aviso.

  • Un popover con el tipo manual actúa como contenedor.
  • Las notificaciones nuevas se adjuntan al pop-up y este se muestra.
  • Se quitan con la API de animaciones web cuando se hace clic y se quitan del DOM.
  • Si no hay notificaciones para mostrar, se oculta el popover.

Menú anidado

En esta demostración, se muestra cómo podría funcionar un menú de navegación anidado.

  • Usa [popover=auto], ya que permite popovers anidados.
  • Usa autofocus en el primer vínculo de cada menú desplegable para navegar con el teclado.
  • Este es un candidato perfecto para la API de anclaje de CSS. 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, deberá abrirse en la "vista de pantalla completa" para la navegación con el teclado.

Ventana emergente de contenido multimedia

En esta demostración, se muestra cómo puedes hacer que el contenido multimedia aparezca de forma emergente.

  • Usa [popover=auto] para descartar la luz.
  • JavaScript escucha el evento play del video y lo muestra.
  • El evento popoverhide de los pop-ups pausa el video.

Ventanas emergentes de estilo wiki

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

  • Usa [popover=auto] Si se muestra uno, se ocultan los demás porque no son ancestrales.
  • Se muestra en pointerenter con JavaScript.
  • Otro candidato perfecto para la API de CSS Anchoring.

En esta demostración, se crea un panel lateral de navegación con un popover.

  • Usa [popover=auto] para descartar la luz.
  • 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 pop-overs en los que solo deseas que sea visible un ::backdrop.

  • Usa JavaScript para mantener una lista de los popovers que están visibles.
  • Aplica un nombre de clase al pop-up más bajo de la capa superior.

Popup de cursor personalizado

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

  • Promociona canvas a la capa superior con showPopover y [popover=manual].
  • Cuando se abran otros popovers, oculta y muestra el popover de canvas para asegurarte de que esté en la parte superior.

Ventana emergente de la hoja de acciones

En esta demostración, se muestra cómo puedes usar un popover como una hoja de acciones.

  • Haz que el cuadro emergente se muestre de forma predeterminada anulando display.
  • Se abre la hoja de acciones con el activador de la ventana emergente.
  • Cuando se muestra el popover, se promueve a la capa superior y se traduce a la vista.
  • Se puede usar la anulación ligera para devolverla.

Menú flotante activado por el teclado

En esta demostración, se muestra cómo puedes usar el menú flotante para la IU de estilo de paleta de comandos.

  • Usa cmd + j para mostrar el menú flotante.
  • El input se enfoca con autofocus.
  • El cuadro combinado es un segundo popover ubicado debajo de la entrada principal.
  • El rechazo claro cierra la paleta si no hay un menú desplegable.
  • Otro candidato para la API de Anchoring

Notificación emergente programada

En esta demostración, se muestra un pop-up de inactividad después de cuatro segundos. Es un patrón de IU que se usa con frecuencia en apps que contienen información segura sobre un usuario para mostrar un diálogo de salida.

  • Usa JavaScript para mostrar el popover después de un período de inactividad.
  • Cuando se muestre el pop-up, restablece el temporizador.

Protector

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

  • Usa JavaScript para mostrar el popover después de un período de inactividad.
  • Descartar con la luz para ocultar y restablecer el temporizador
.

Seguimiento del cursor de texto

En esta demostración, se muestra cómo puedes hacer que un cuadro flotante siga un signo de intercalación de entrada.

  • Muestra el menú flotante según la selección, el evento de tecla o la entrada de caracteres especiales.
  • Usa JavaScript para actualizar la posición del cuadro emergente con propiedades personalizadas centradas.
  • Este patrón requeriría una consideración cuidadosa del contenido que se muestra y la accesibilidad.
  • A menudo, se ve en la IU de edición de texto y en las apps en las que puedes etiquetar.

Menú del botón de acción flotante

En esta demostración, se muestra cómo puedes usar el menú flotante para implementar un menú de botón de acción flotante sin JavaScript.

  • Promociona un popover de tipo manual con el método showPopover. Este es el botón principal.
  • El menú es otro popover que es el objetivo del botón principal.
  • El menú se abre con popovertoggletarget.
  • Usa autofocus para enfocar el primer elemento de menú que se muestra.
  • La anulación de la luz cierra el menú.
  • La torsión del ícono usa :has(). Puedes obtener más información sobre :has() en este artículo.

Eso es todo.

Esta es una introducción al popover, que se lanzará más adelante como parte de la iniciativa de IU abierta. Si se usa de forma sensata, será una incorporación fantástica a la plataforma web.

Asegúrate de consultar Open UI. La explicación del cuadro emergente se mantiene actualizada a medida que evoluciona la API. Y esta es la colección de todas las demostraciones.

Gracias por pasarte.


Foto de Madison Oren en Unsplash