Una descripción general fundamental de cómo compilar un componente de aviso adaptable y accesible.
En esta publicación, quiero compartir algunas ideas sobre cómo crear un componente de aviso. Prueba la demostración.
Si prefieres ver un video, aquí tienes una versión de YouTube de esta publicación:
Descripción general
Los avisos son mensajes cortos no interactivos, pasivos y asíncronos para los usuarios. Por lo general, se usan como patrón de comentarios de la interfaz para informar al usuario sobre los resultados de una acción.
Interacciones
Los avisos no son como las notificaciones, las alertas y las mensajes porque no son interactivos y no están destinados a descartarse ni conservar. Las notificaciones contienen información más importante, mensajes síncronos que requieren interacción o mensajes a nivel del sistema (a diferencia del nivel de la página). Los avisos son más pasivos que otras estrategias de avisos.
Marca
El elemento <output>
es una buena opción para el aviso porque se anuncia a los lectores de pantalla. El HTML correcto nos brinda una base segura para mejorar con JavaScript y CSS, y habrá mucho JavaScript.
Un aviso
<output class="gui-toast">Item added to cart</output>
Puedes agregar role="status"
para ser más inclusivo. Esto proporciona un resguardo en caso de que el navegador no les otorgue a los elementos <output>
la función implícita según la especificación.
<output role="status" class="gui-toast">Item added to cart</output>
Un contenedor de avisos
Se puede mostrar más de un aviso a la vez. Para organizar varios avisos, se usa un contenedor. Este contenedor también controla la posición de los avisos 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
Elegí fijar avisos en el inset-block-end
del viewport y, si se agregan más avisos, se apilan desde ese borde de la pantalla.
Contenedor de la GUI
El contenedor de avisos hace todo el trabajo de diseño para presentar los avisos. Es fixed
para el viewport y usa la propiedad lógica inset
para especificar en qué bordes fijar, además de un poco de padding
del mismo borde block-end
.
.gui-toast-group {
position: fixed;
z-index: 1;
inset-block-end: 0;
inset-inline: 0;
padding-block-end: 5vh;
}
Además de posicionarse dentro del viewport, el contenedor de avisos es un contenedor de cuadrícula que puede alinear y distribuir avisos. Los elementos se centran como un grupo con justify-content
y se centran de forma individual con justify-items
.
Agrégale un poco de gap
para que los avisos no se toquen.
.gui-toast-group {
display: grid;
justify-items: center;
justify-content: center;
gap: 1vh;
}
Aviso de la GUI
Un aviso individual tiene algunos padding
, algunas esquinas más suaves con border-radius
y una función min()
para ayudar a ajustar el tamaño de dispositivos móviles y computadoras de escritorio. El tamaño responsivo en el siguiente CSS
evita que los avisos se expandan a un valor superior al 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;
}
Estilos
Con el diseño y la posición establecidos, agrega CSS que ayude a adaptarte a la configuración y las interacciones del usuario.
Contenedor de avisos
Los avisos no son interactivos. Presionarlos o deslizarlos no hace nada, pero, por el momento, consumen eventos de puntero. Evita que los avisos roben clics con el siguiente CSS.
.gui-toast-group {
pointer-events: none;
}
Aviso de la GUI
Aplica a los avisos un tema adaptable oscuro o claro 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
Se debería mostrar un aviso nuevo con una animación cuando entra en la pantalla.
Para adaptar el movimiento reducido, se deben establecer los valores translate
en 0
de forma predeterminada, pero se actualiza el valor de movimiento a una longitud en una consulta de medios de preferencia de movimiento . Todo el mundo tiene animación, pero solo algunos usuarios tienen el aviso de recorrer una distancia.
Estos son los fotogramas clave utilizados para la animación del aviso. CSS controlará la entrada, la espera y la salida del aviso, 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 aviso configura las variables y organiza 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 del lector de pantalla, se necesita JavaScript para organizar la creación, adición y destrucción de avisos basados en eventos del usuario. La experiencia del desarrollador con el componente de aviso debe ser mínima y fácil de comenzar, de la siguiente manera:
import Toast from './toast.js'
Toast('My first toast')
Creando el grupo de avisos y los avisos
Cuando el módulo de avisos se carga desde JavaScript, debe crear un contenedor de avisos y agregarlo a la página. Elegí agregar el elemento antes de body
. Esto hará que los problemas de apilamiento de z-index
sean poco probables, ya que el contenedor está por encima del contenedor para 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
}
Se llama a la función init()
de forma interna al módulo y se almacena el elemento como Toaster
:
const Toaster = init()
La creación del elemento HTML del aviso se realiza con la función createToast()
. La función requiere texto para el aviso, crea un elemento <output>
, lo adorna con algunas clases y atributos, establece el texto y muestra el nodo.
const createToast = text => {
const node = document.createElement('output')
node.innerText = text
node.classList.add('gui-toast')
node.setAttribute('role', 'status')
return node
}
Cómo controlar uno o varios avisos
JavaScript ahora agrega un contenedor al documento para contener los avisos y está listo para agregar los avisos creados. La función addToast()
organiza el control de uno o varios avisos. Primero, verifica la cantidad de avisos y si el movimiento está bien. Luego, usa esta información para agregar el aviso o hacer una animación sofisticada para que los otros avisos parezcan "hacer lugar" para el nuevo aviso.
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 aviso, Toaster.appendChild(toast)
agrega un aviso a la página que activa las animaciones de CSS: animación de entrada, espera 3s
y animación de salida.
Se llama a flipToast()
cuando hay avisos existentes, mediante una técnica llamada FLIP de Paul Lewis. La idea es calcular la diferencia en las posiciones del contenedor, antes y después de agregar el nuevo aviso.
Piensa en eso como marcar dónde está la Toaster ahora, dónde va a estar, y luego
animar desde donde estaba hasta donde 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 de CSS realiza la elevación del diseño. Cuando se agrega un nuevo aviso, la cuadrícula lo coloca al inicio y lo coloca en un espacio con los demás. Mientras tanto, se usa una animación web para animar el contenedor desde la posición anterior.
Cómo juntar todo el código JavaScript
Cuando se llama a Toast('my first toast')
, se crea un aviso y se agrega a la página (tal vez incluso se anima el contenedor para adaptarse al nuevo aviso), se muestra una promesa y se mira el aviso creado para completar la animación de 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()
})
}
Me pareció que la parte confusa de este código se encuentra en la función Promise.allSettled()
y en la asignación toast.getAnimations()
. Como usé varias animaciones de fotogramas clave en el aviso, para saber con confianza que todas terminaron, cada una debe solicitarse desde JavaScript y se debe observar cada una de sus promesas finished
para completarse.
allSettled
hace el trabajo por nosotros y se resuelve como completo una vez que se cumplen todas sus promesas. El uso de await Promise.allSettled()
significa que la siguiente línea de código puede quitar el elemento de manera confiable y asumir que el aviso completó su ciclo de vida. Por último, llamar a resolve()
cumple con la promesa de alto nivel de los avisos, de modo que los desarrolladores puedan realizar una limpieza o realizar otro trabajo una vez que se muestre el aviso.
export default Toast
Por último, la función Toast
se exporta desde el módulo para que otras secuencias de comandos se importen y usen.
Cómo usar el componente de aviso
Para usar el aviso o su experiencia del desarrollador, 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 desea realizar un trabajo de limpieza o lo que sea, después de que se haya mostrado el aviso, puede usar async y await.
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 🙂
Diversifiquemos nuestros enfoques y aprendamos todas las formas de desarrollar en la Web. Crea una demostración, twittea vínculos y la agregaré a la sección de remixes de la comunidad a continuación.
Remixes de la comunidad
- @_developit con HTML/CSS/JS: demostración y código
- Joost van der Schee con HTML/CSS/JS: Demostración y código