Cómo compilar un componente de aviso

Descripción general básica de cómo compilar un componente de notificación adaptable y accesible.

En esta publicación, quiero compartir mis ideas sobre cómo crear un componente de notificación. Prueba la demostración.

Demo

Si prefieres un video, aquí tienes una versión de este artículo en YouTube:

Descripción general

Los mensajes Toast son mensajes cortos, pasivos, asíncronos y no interactivos para los usuarios. Por lo general, se usan como un patrón de comentarios de la interfaz para informar al usuario sobre los resultados de una acción.

Interacciones

A diferencia de las notificaciones, las alertas y los mensajes, los mensajes emergentes no son interactivos, no están diseñados para descartarse ni para persistir. Las notificaciones son para información más importante, mensajes síncronos que requieren interacción o mensajes a nivel del sistema (a diferencia de los mensajes a nivel de la página). Los mensajes Toast son más pasivos que otras estrategias de avisos.

Marca

El elemento <output> es una buena opción para el mensaje emergente porque se anuncia a los lectores de pantalla. El HTML correcto proporciona una base segura para que la mejoremos con JavaScript y CSS, y habrá mucho JavaScript.

Un brindis

<output class="gui-toast">Item added to cart</output>

Puede ser más inclusivo si se agrega role="status". Esto proporciona una alternativa si el navegador no les otorga a los elementos <output> el rol implícito según la especificación.

<output role="status" class="gui-toast">Item added to cart</output>

Un contenedor de mensajes Toast

Se puede mostrar más de una notificación emergente a la vez. Para organizar varias notificaciones emergentes, se usa un contenedor. Este contenedor también controla la posición de los mensajes emergentes en la pantalla.

<section class="gui-toast-group">
  <output role="status">Wizard Rose added to cart</output>
  <output role="status">Self Watering Pot added to cart</output>
</section>

Diseños

Decidí fijar las notificaciones emergentes en la inset-block-end de la ventana gráfica y, si se agregan más notificaciones emergentes, se apilan desde ese borde de la pantalla.

Contenedor de la GUI

El contenedor de mensajes emergentes realiza todo el trabajo de diseño para presentar los mensajes emergentes. Se encuentra fixed en la ventana gráfica y usa la propiedad lógica inset para especificar a qué bordes fijar, además de un poco de padding desde el mismo borde block-end.

.gui-toast-group {
  position: fixed;
  z-index: 1;
  inset-block-end: 0;
  inset-inline: 0;
  padding-block-end: 5vh;
}

Captura de pantalla con el tamaño de la caja y el relleno de Herramientas para desarrolladores superpuestos en un elemento .gui-toast-container.

Además de posicionarse dentro del viewport, el contenedor de mensajes emergentes es un contenedor de cuadrícula que puede alinear y distribuir mensajes emergentes. Los elementos se centran como un grupo con justify-content y se centran individualmente con justify-items. Agrega un poco de gap para que las tostadas no se toquen.

.gui-toast-group {
  display: grid;
  justify-items: center;
  justify-content: center;
  gap: 1vh;
}

Captura de pantalla con la superposición de la cuadrícula CSS en el grupo de mensajes emergentes, esta vez destacando el espacio y los espacios entre los elementos secundarios de los mensajes emergentes.

Toast de la GUI

Un mensaje individual tiene algo de padding, algunas esquinas más suaves con border-radius y una función min() para ayudar con el tamaño en dispositivos móviles y computadoras de escritorio. El tamaño responsivo en el siguiente CSS evita que los mensajes emergentes se expandan más del 90% del viewport o 25ch.

.gui-toast {
  max-inline-size: min(25ch, 90vw);
  padding-block: .5ch;
  padding-inline: 1ch;
  border-radius: 3px;
  font-size: 1rem;
}

Captura de pantalla de un solo elemento .gui-toast, con el padding y el radio de borde que se muestran.

Estilos

Con el diseño y el posicionamiento establecidos, agrega CSS que ayude a adaptarse a la configuración y las interacciones del usuario.

Contenedor de tostadas

Los mensajes Toast no son interactivos, por lo que presionarlos o deslizar el dedo sobre ellos no hace nada, pero actualmente consumen eventos de puntero. Evita que los mensajes emergentes roben clics con el siguiente código CSS.

.gui-toast-group {
  pointer-events: none;
}

Toast de la GUI

Proporciona a los mensajes emergentes un tema adaptable claro u oscuro con propiedades personalizadas, HSL y una consulta de medios de preferencia.

.gui-toast {
  --_bg-lightness: 90%;

  color: black;
  background: hsl(0 0% var(--_bg-lightness) / 90%);
}

@media (prefers-color-scheme: dark) {
  .gui-toast {
    color: white;
    --_bg-lightness: 20%;
  }
}

Animación

Debería aparecer una nueva notificación emergente con una animación a medida que ingresa a la pantalla. Para adaptarse al movimiento reducido, se configuran los valores de translate como 0 de forma predeterminada, pero se actualiza el valor de movimiento a una longitud en una consulta de medios de preferencia de movimiento . Todos los usuarios ven alguna animación, pero solo algunos ven el desplazamiento de la notificación emergente a una distancia.

Estos son los fotogramas clave que se usan para la animación del mensaje emergente. El CSS controlará la entrada, la espera y la salida de la notificación emergente, todo en una sola animación.

@keyframes fade-in {
  from { opacity: 0 }
}

@keyframes fade-out {
  to { opacity: 0 }
}

@keyframes slide-in {
  from { transform: translateY(var(--_travel-distance, 10px)) }
}

Luego, el elemento de notificación configura las variables y coordina los fotogramas clave.

.gui-toast {
  --_duration: 3s;
  --_travel-distance: 0;

  will-change: transform;
  animation: 
    fade-in .3s ease,
    slide-in .3s ease,
    fade-out .3s ease var(--_duration);
}

@media (prefers-reduced-motion: no-preference) {
  .gui-toast {
    --_travel-distance: 5vh;
  }
}

JavaScript

Con los estilos y el HTML accesible para lectores de pantalla listos, se necesita JavaScript para coordinar la creación, la adición y la destrucción de mensajes emergentes basados en eventos del usuario. La experiencia del desarrollador del componente de notificación debe ser mínima y fácil de usar, como se muestra a continuación:

import Toast from './toast.js'

Toast('My first toast')

Cómo crear el grupo y los mensajes emergentes

Cuando el módulo de mensajes emergentes se carga desde JavaScript, debe crear un contenedor de mensajes emergentes y agregarlo a la página. Decidí agregar el elemento antes de body, lo que hará que los problemas de apilamiento de z-index sean poco probables, ya que el contenedor está por encima del contenedor de todos los elementos del cuerpo.

const init = () => {
  const node = document.createElement('section')
  node.classList.add('gui-toast-group')

  document.firstElementChild.insertBefore(node, document.body)
  return node
}

Captura de pantalla del grupo de mensajes emergentes entre las etiquetas head y body.

La función init() se llama internamente al módulo y almacena el elemento como Toaster:

const Toaster = init()

La creación de elementos HTML de mensajes emergentes se realiza con la función createToast(). La función requiere algo de texto para la notificación, crea un elemento <output>, lo adorna con algunas clases y atributos, establece el texto y devuelve el nodo.

const createToast = text => {
  const node = document.createElement('output')
  
  node.innerText = text
  node.classList.add('gui-toast')
  node.setAttribute('role', 'status')

  return node
}

Administra una o varias notificaciones emergentes

Ahora, JavaScript agrega un contenedor al documento para incluir mensajes emergentes y está listo para agregar los mensajes emergentes creados. La función addToast() coordina el control de una o varias notificaciones emergentes. Primero, se verifica la cantidad de mensajes emergentes y si el movimiento es adecuado. Luego, se usa esta información para agregar el mensaje emergente o realizar una animación sofisticada para que los otros mensajes emergentes parezcan "dejar espacio" para el nuevo mensaje.

const addToast = toast => {
  const { matches:motionOK } = window.matchMedia(
    '(prefers-reduced-motion: no-preference)'
  )

  Toaster.children.length && motionOK
    ? flipToast(toast)
    : Toaster.appendChild(toast)
}

Cuando se agrega el primer mensaje emergente, Toaster.appendChild(toast) agrega un mensaje emergente a la página que activa las animaciones CSS: animar entrada, esperar 3s y animar salida. Se llama a flipToast() cuando hay mensajes emergentes existentes, y se emplea una técnica llamada FLIP por Paul Lewis. La idea es calcular la diferencia en las posiciones del contenedor, antes y después de que se agregue la nueva notificación. Piensa en ello como marcar dónde está el tostador ahora, dónde estará y, luego, animar desde dónde estaba hasta dónde está.

const flipToast = toast => {
  // FIRST
  const first = Toaster.offsetHeight

  // add new child to change container size
  Toaster.appendChild(toast)

  // LAST
  const last = Toaster.offsetHeight

  // INVERT
  const invert = last - first

  // PLAY
  const animation = Toaster.animate([
    { transform: `translateY(${invert}px)` },
    { transform: 'translateY(0)' }
  ], {
    duration: 150,
    easing: 'ease-out',
  })
}

La cuadrícula CSS se encarga del diseño. Cuando se agrega una notificación nueva, la cuadrícula la coloca al principio y la espacia con las demás. Mientras tanto, se usa una animación web para animar el contenedor desde la posición anterior.

Cómo combinar todo el código JavaScript

Cuando se llama a Toast('my first toast'), se crea un mensaje emergente, se agrega a la página (quizás incluso se anime el contenedor para adaptarse al nuevo mensaje emergente), se devuelve una promesa y se observa el mensaje emergente creado para la finalización de la animación CSS (las tres animaciones de fotogramas clave) para la resolución de la promesa.

const Toast = text => {
  let toast = createToast(text)
  addToast(toast)

  return new Promise(async (resolve, reject) => {
    await Promise.allSettled(
      toast.getAnimations().map(animation => 
        animation.finished
      )
    )
    Toaster.removeChild(toast)
    resolve() 
  })
}

Creo que la parte confusa de este código se encuentra en la función Promise.allSettled() y la asignación de toast.getAnimations(). Como usé varias animaciones de fotogramas clave para el mensaje emergente, para saber con certeza que todas finalizaron, cada una debe solicitarse desde JavaScript y se debe observar la finalización de cada una de sus promesas de finished. allSettled funciona para nosotros y se resuelve como completa una vez que se cumplen todas sus promesas. Usar await Promise.allSettled() significa que la siguiente línea de código puede quitar el elemento con confianza y suponer que el mensaje emergente completó su ciclo de vida. Por último, la llamada a resolve() cumple con la promesa de Toast de alto nivel para que los desarrolladores puedan limpiar o realizar otro trabajo una vez que se haya mostrado el toast.

export default Toast

Por último, la función Toast se exporta desde el módulo para que otros scripts la importen y usen.

Cómo usar el componente Toast

Para usar la notificación o la experiencia del desarrollador de la notificación, importa la función Toast y llámala con una cadena de mensaje.

import Toast from './toast.js'

Toast('Wizard Rose added to cart')

Si el desarrollador quiere realizar tareas de limpieza o lo que sea después de que se muestre el mensaje, puede usar await y async.

import Toast from './toast.js'

async function example() {
  await Toast('Wizard Rose added to cart')
  console.log('toast finished')
}

Conclusión

Ahora que sabes cómo lo hice, ¿cómo lo harías tú? 🙂

Diversifiquemos nuestros enfoques y aprendamos todas las formas de crear contenido en la Web. Crea una demostración, envíame por Twitter los vínculos y la agregaré a la sección de remixes de la comunidad que se encuentra a continuación.

Remixes de la comunidad