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 son un patrón fácil de crear, pero pueden generar mucho valor dirigiendo a los usuarios a ciertos elementos o haciéndoles conocer el contenido de tu sitio, especialmente cuando se usan 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 del 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 esos objetivos, se destacan los siguientes:

  • Facilita la visualización de un elemento y sus elementos subordinados por encima del 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 lo siguiente:

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. Debes usarlo en formato "Modal". Para usar esto se requiere JavaScript.

Dialog.showModal();

Hay algunas consideraciones de accesibilidad. Se recomienda usar a11y-dialog, por ejemplo, si ofrece servicios de catering 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 herramientas 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 por encima de 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 JavaScript para que aparezca. El atributo popovertoggletarget se encarga de 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 profundamente anidado en el DOM, con principales recortados. 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 popover con un indicador de cierre, como hacer clic fuera del popover, navegar con el teclado a otro elemento o presionar la tecla Esc. Ábrela de nuevo y pruébala.

¿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 un posicionamiento fijo con un z-index alto.

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

El contenido de la ventana emergente está anidado en el DOM, pero cuando abres la ventana emergente, se asciende por encima del elemento de posición fija. No es necesario establecer 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 una ventana emergente

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 opción para mostrar un popover. Sin embargo, eso no la promoverí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 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 distribuir contenido dentro de tu ventana emergente con una cuadrícula de CSS o flexbox, te recomendamos unirlo a 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 lo estableces de forma predeterminada, se mostrará de forma predeterminada y anulará 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 es agrupar las transiciones y las animaciones en una consulta de contenido multimedia 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 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 una ventana emergente 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 de CSS definirían el período para desplazarse sobre un elemento al que reacciona una ventana emergente. 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, necesitaría un cierre ligero o la apertura de otra ventana emergente para descartar (más adelante hablaremos de esto). Esto se debió a que la duración de la ocultación de elementos emergentes 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é ocurre con el comportamiento de las ventanas emergentes en su conjunto? ¿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]:

  • Centro de asistencia de Nesting. Esto no significa solo anidado en el DOM. La definición de un popover ancestral es la siguiente:
    • relacionados por la posición del DOM (secundarios).
    • relacionados mediante el disparo de atributos en elementos secundarios, como popovertoggletarget, popovershowtarget, etcétera.
    • relacionados por el atributo anchor (API de anclaje de CSS en desarrollo).
  • Descartar con luz.
  • La apertura descarta otros popovers que no sean popovers ancestrales. Diviértete con 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 puede descartar 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 puedes escuchar los siguientes eventos de popovershow y popoverhide:

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

popoverElement.hidePopover()

Escucha si se muestra una ventana emergente:

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 una ventana emergente:

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

Verifica si hay un cuadro flotante 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 el teclado para recorrer la interfaz con la tecla Tab. Ten en cuenta dónde se muestra el enfoque cuando se cierra un menú flotante. También puedes notar que si hablaste con tabulaciones, la ventana emergente se cerró. Eso es por diseño. Aunque las ventanas emergentes tienen gestión del enfoque, no atrapan el enfoque. Además, la navegación con el teclado identifica una señal de cierre cuando el enfoque sale del popover.

Anclaje (en desarrollo)

Cuando se trata de ventanas emergentes, un patrón complicado de satisfacer es ancla el elemento en su desencadenante. 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 lidiar con esto, como la "IU flotante". Recolocarán la información sobre herramientas para que esto no suceda y se base en un orden de posición deseado.

Pero queremos que puedas definirlo 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 ya conoces lo que los anuncios emergentes tienen para ofrecer y cómo lo ofrecen, profundicemos en 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 popover con showPopover.
  • Después de un tiempo de espera de 2000ms, ocúltala 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

Esta demostración 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 "vista de pantalla completa" para navegar 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 con luz.
  • JavaScript escucha el evento play del video y lo muestra.
  • El evento popoverhide de los pop-overs 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.
.

Administración de 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 otras ventanas emergentes, oculta y muestra la ventana emergente canvas para asegurarte de que aparezca en la parte superior.

Ventana emergente de la hoja de acción

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

Esta demostración muestra cómo usar la ventana emergente para la IU de estilo de paleta de comandos.

  • Usa cmd + j para mostrar la ventana emergente.
  • 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. Un 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 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 extravagancia a tu sitio y 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 popover 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 destino del botón principal.
  • El menú se abre con popovertoggletarget.
  • Usa autofocus para enfocar el primer elemento de menú en el programa.
  • La anulación de la luz cierra el menú.
  • El giro del ícono usa :has(). Puedes obtener más información sobre :has() en este artículo.

Eso es todo.

Esta es una introducción a la ventana emergente, que se presentará como parte de la iniciativa Open UI. Si se usa de forma sensata, será una incorporación fantástica a la plataforma web.

Asegúrate de consultar Abrir IU. 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