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 ello, estamos tratando de abordar los patrones más problemáticos a los que se enfrentan los desarrolladores. Para ello, podemos brindar mejores APIs y componentes integrados en la plataforma.

Una de esas áreas de problema son las ventanas emergentes, que se describen en Open UI como "Ventanas emergentes".

Las ventanas emergentes 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 producir 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, hay dos grandes preocupaciones a la hora de crear ventanas emergentes:

  • Cómo asegurarte de que se coloque encima del resto del contenido en un lugar adecuado.
  • Cómo hacer que sean accesibles (compatible con el teclado, enfocable, etc.)

La API de Popover integrada tiene una variedad de objetivos, todos con el mismo objetivo general de facilitar a los desarrolladores la creación de este patrón. Estos son los objetivos más destacados:

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

Puedes ver 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 las "Funciones experimentales de la plataforma web" marca en el momento de la redacción.

Para habilitar esa marca, abre Chrome Canary y visita chrome://flags. A continuación, habilita las "Funciones experimentales de la plataforma web". marca.

Además, existe una prueba de origen para los desarrolladores que deseen 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 comprobar la compatibilidad con las ventanas emergentes con los siguientes métodos:

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

Soluciones actuales

¿Qué puedes hacer actualmente para promocionar tu contenido por sobre todo lo demás? Si tu navegador lo admite, puedes usar el elemento de diálogo HTML. Debes usarlo en la sección "Modal" formulario. 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. Muchas de ellas suelen funcionar de forma similar.

  • Agrega algún contenedor al cuerpo para mostrar las ventanas emergentes.
  • Ajusta el estilo para que quede 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 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, como la información sobre la herramienta. El objetivo es abarcar todas esas situaciones comunes y evitar que los desarrolladores tengan que tomar otra decisión para que puedan 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 tienes que colocar el elemento emergente en un contenedor ni en nada, ya que está oculto de forma predeterminada.
  • No es necesario que escribas JavaScript para que aparezca. Eso se controla con el atributo popovertoggletarget.
  • Cuando aparece, se asciende a la capa superior. Eso significa que se asciende por encima de document en el viewport. No es necesario que administres z-index ni te preocupes por la ubicación de la ventana emergente 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 mediante Herramientas para desarrolladores. Para obtener más información sobre la capa superior, consulta este artículo.

GIF de la compatibilidad de la capa superior de Herramientas para desarrolladores que se demuestra

  • Aparece el mensaje "Light Dismiss" de inmediato. Eso quiere decir que puedes cerrar la ventana emergente con una señal de cierre, por ejemplo, si haces clic fuera de ella, navegas con el teclado a otro elemento o presionas la tecla Esc. Ábrela de nuevo y pruébala.

¿Qué más obtienes con la ventana emergente? Analicemos más 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 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 la ventana emergente ahora tiene un seudoelemento ::backdrop. Todos los elementos que se encuentran en la capa superior obtienen un seudoelemento ::backdrop con diseño. 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 aplicar diseño a una ventana emergente

Pongamos nuestra atención en diseñar la ventana emergente. De forma predeterminada, una ventana emergente tiene una posición fija y algo de padding aplicado. También tiene display: none. Puedes anular esto para mostrar una ventana emergente. Sin embargo, eso no la promovería a la capa superior.

[popover] { display: block; }

Independientemente de cómo promocionas tu ventana emergente, una vez que promociones una ventana emergente en la capa superior, es posible que debas colocarla o posicionarla. No puedes orientarte a la capa superior y hacer algo como esto

: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, se recomienda que sea explícito con 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 separada que cambie el display una vez que la ventana emergente se encuentre 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 la ventana emergente ahora está entrando y saliendo. Puedes usar el seudoselector :open para realizar la transición de las ventanas emergentes hacia adentro y afuera. 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 ::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 aquí es agrupar las transiciones y animaciones en una consulta de medios para buscar 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 descartarlo, usaremos "Descarte claro". Sin embargo, también obtendrás 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 mencionamos antes, la API de Popover abarca más que solo nuestra noción histórica de ventanas emergentes. Puedes crear soluciones para todo tipo de situaciones, como notificaciones, menús, cuadros de información, etcétera.

Algunas de esas situaciones necesitan patrones de interacción diferentes. Interacciones como colocar el cursor sobre un elemento Se probó el uso de un atributo popoverhovertarget, pero aún 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 las propiedades de 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ó tuvo una ventana emergente 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 este tema). 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 hacer 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 una ventana emergente explícita es que garantiza que la acción del usuario sea intencional (por ejemplo, un usuario pasa su 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 puntos.


Tipos de ventanas emergentes

Abordamos 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 "Descarte claro"? ¿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]:

  • Centro de asistencia de Nesting. Esto no significa solo anidado en el DOM. ¿Qué significa una popover ancestral?
    • está relacionado por posición del DOM (elemento secundario).
    • relacionados activando atributos en elementos secundarios, como popovertoggletarget, popovershowtarget, etcétera.
    • relacionados con el atributo anchor (API de Anchoring de CSS en desarrollo).
  • Descartar luz.
  • La apertura descarta otras ventanas emergentes que no son 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 de algunas de las instancias popoverhidetarget/popovershowtarget a popovertoggletarget.
  • La luz que descarta uno descarta todo, pero si se descarta uno en la pila, solo se descartan en la pila los que están encima de él.

[popover=manual]:

  • No cierra otras ventanas emergentes.
  • No se puede descartar la luz.
  • Requiere un descarte explícito mediante un elemento activador o JavaScript.

API de JavaScript

Cuando necesites más control sobre tus ventanas emergentes, puedes abordar las cosas 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 una ventana emergente y cancélala:

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

Escucha si se oculta una ventana emergente:

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 una ventana emergente está en la capa superior:

popoverElement.matches(':open')

Esto proporciona 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 sonidos emergentes audibles, por lo que necesitaremos JavaScript para reproducir el audio. Cuando se hace clic, oculta la ventana emergente, reproduce el audio y, luego, lo vuelve a mostrar.

Accesibilidad

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

Para administrar el 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 Dialog, pero la diferencia aparece cuando se muestra el enfoque y se debe al descarte de luz. En la mayoría de los casos, al cerrar una ventana emergente, se vuelve a enfocar el elemento seleccionado anteriormente. Sin embargo, si se puede enfocar, se mueve el enfoque a un elemento en el que se hizo clic cuando se descarta la luz. Consulta la sección sobre la administración del enfoque en la explicación.

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

En esta demostración, el elemento enfocado obtiene un contorno verde. Intenta usar la tecla Tab para desplazarte por la interfaz con el teclado. Ten en cuenta dónde se devuelve el foco cuando se cierra una ventana emergente. 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 teclado identifica una señal de cierre cuando el enfoque se mueve fuera de la ventana emergente.

Anclaje (en desarrollo)

Cuando se trata de ventanas emergentes, un patrón difícil de satisfacer es anclar el elemento a su desencadenante. Por ejemplo, si se configura un cuadro de información para que se muestre encima de su activador, pero se desplaza el documento. Esa información podría cortarse por el viewport. Existen ofertas actuales de JavaScript para lidiar con esto, como la "IU flotante". Cambiarán la posición de la información para que esto no suceda y se basarán en el 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 esto. La opción "CSS Anchor Positioning" La API te permitirá anclar elementos a otros, y lo hará de una manera que reposiciona los elementos para 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 el viewport.

Este es un fragmento del CSS que hace funcionar esta demostración. 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 la especificación 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 el mensaje "Copiar en el portapapeles" notificación.

  • Usa [popover=manual]
  • Ventana emergente de programa en acción con showPopover
  • Después de un tiempo de espera de 2000ms, ocúltala con hidePopover.

Avisos

Esta demostración usa la capa superior para mostrar notificaciones con estilo de aviso.

  • Una ventana emergente con el tipo manual actúa como el contenedor.
  • Las notificaciones nuevas se agregan a la ventana emergente y se muestra la ventana emergente.
  • Se quitan con la API de animaciones web cuando se hace clic y se quitan del DOM.
  • Si no hay avisos para mostrar, el elemento emergente está oculto.

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.
  • Este es un candidato perfecto 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 las 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 la navegación con el teclado.

Ventana emergente de contenido multimedia

Esta demostración muestra cómo podrías mostrar contenido multimedia en una ventana emergente.

  • Usa [popover=auto] para descartar con luz.
  • JavaScript detecta el evento play del video y lo muestra en una ventana emergente.
  • El evento popoverhide de las ventanas emergentes detiene el video.

Ventanas emergentes de estilo Wiki

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

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

En esta demostración, se crea un panel lateral de navegación mediante una ventana emergente.

  • Usa [popover=auto] para descartar con luz.
  • Usa autofocus para enfocar el primer elemento de navegación.

Administración de fondos

En esta demostración, se muestra cómo administrar fondos para varias ventanas emergentes en las que solo quieres que esté visible un ::backdrop.

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

Ventana emergente del 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 aparezca en la parte superior.

Ventana emergente de la hoja de acción

Esta demostración muestra cómo usar una ventana emergente como hoja de acción.

  • Haz que la ventana emergente que se muestra anule display de forma predeterminada.
  • La hoja de acción se abre con el activador de ventana emergente.
  • Cuando se muestra la ventana emergente, se asciende a la capa superior y se muestra a la vista.
  • Puedes usar el descarte de luz para devolverlo.

Ventana emergente activada por 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.
  • input se enfoca en autofocus.
  • El cuadro combinado es un segundo popover que se posiciona debajo de la entrada principal.
  • La descarte de luz 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. 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 la ventana emergente después de un período de inactividad.
  • En un programa emergente, 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 la ventana emergente después de un período de inactividad.
  • Descarta la luz para ocultar y restablecer el temporizador.

Seguir el 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 en función de la selección, el evento clave o la entrada de un carácter especial.
  • Usa JavaScript para actualizar la posición de la ventana emergente con propiedades personalizadas con alcance.
  • Este patrón requiere una consideración considerada sobre el contenido que se muestra y la accesibilidad.
  • A menudo se encuentra 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

Esta demostración muestra cómo usar la ventana emergente para implementar un menú de botón 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 objetivo del botón principal.
  • Se abrió el menú con popovertoggletarget.
  • Usa autofocus para enfocar el primer elemento de menú en el programa.
  • La función para descartar 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 lo usamos a conciencia, será una adición fantástica a la plataforma web.

Asegúrate de consultar Abrir IU. La explicación emergente se mantiene actualizada a medida que la API evoluciona. Esta es la colección de todas las demostraciones.

Gracias por dar un chasquido.


Foto de Madison Oren en Unsplash