Una panoramica di base su come creare un componente Toast adattivo e accessibile.
In questo post voglio condividere alcune idee su come creare un componente di tipo toast. Prova la demo.
Se preferisci i video, ecco una versione di questo post su YouTube:
Panoramica
I popup sono messaggi brevi non interattivi, passivi e asincroni per gli utenti. In genere vengono utilizzati come pattern di feedback dell'interfaccia per informare l'utente sui risultati di un'azione.
Interazioni
A differenza di notifiche, avvisi e prompt, i popup non sono interattivi e non sono destinati a essere ignorati o a rimanere attivi. Le notifiche sono destinate a informazioni più importanti, messaggi sincroni che richiedono interazione o messaggi a livello di sistema (anziché a livello di pagina). I popup sono più passivi rispetto ad altre strategie di avviso.
Segni e linee
L'elemento
<output>
è una buona scelta per il messaggio popup perché viene annunciato agli screen reader. L'HTML corretto fornisce una base sicura da migliorare con JavaScript e CSS, e ci sarà molto JavaScript.
Un brindisi
<output class="gui-toast">Item added to cart</output>
Può essere più
inclusivo
aggiungendo role="status"
. Questo fornisce un valore alternativo se il browser non assegna agli elementi <output>
il ruolo implicito come da specifiche.
<output role="status" class="gui-toast">Item added to cart</output>
Un contenitore dei toast
È possibile visualizzare più di un messaggio popup alla volta. Per orchestrare più toast, si usa un container. Questo contenitore gestisce anche la posizione dei toast sullo schermo.
<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>
Layout
Ho scelto di fissare i toast su
inset-block-end
dell'area visibile e, se ne vengono aggiunti altri, si impilano dal bordo dello schermo.
Contenitore GUI
Il contenitore dei toast gestisce tutto il layout per la presentazione dei toast. È fixed
al viewport e utilizza la proprietà logica inset
per specificare i bordi a cui bloccarsi, oltre a un po' di padding
dallo stesso bordo block-end
.
.gui-toast-group {
position: fixed;
z-index: 1;
inset-block-end: 0;
inset-inline: 0;
padding-block-end: 5vh;
}
Oltre a posizionarsi all'interno dell'area visibile, il contenitore di messaggi popup è un contenitore della griglia che può allineare e distribuire i messaggi popup. Gli elementi vengono centrati come gruppo con justify-content
e singolarmente con justify-items
.
Metti un po' di gap
in modo che i toast non si tocchino.
.gui-toast-group {
display: grid;
justify-items: center;
justify-content: center;
gap: 1vh;
}
Toast della GUI
Un singolo messaggio popup ha alcuni padding
, alcuni angoli più morbidi con
border-radius
,
e una funzione min()
per
aiutare a impostare le dimensioni per dispositivi mobili e computer. Le dimensioni adattabili nel seguente CSS
impediscono che i toast aumentino di oltre il 90% dell'area visibile o
25ch
.
.gui-toast {
max-inline-size: min(25ch, 90vw);
padding-block: .5ch;
padding-inline: 1ch;
border-radius: 3px;
font-size: 1rem;
}
Stili
Dopo aver impostato il layout e il posizionamento, aggiungi CSS che aiuta ad adattarti alle impostazioni e alle interazioni degli utenti.
Contenitore di toast
I toast non sono interattivi, il tocco o lo scorrimento non ha alcun effetto, ma al momento utilizzano eventi puntatore. Impedisci ai popup di rubare i clic con il seguente CSS.
.gui-toast-group {
pointer-events: none;
}
Toast con GUI
Assegna ai popup un tema adattabile chiaro o scuro con proprietà personalizzate, HSL e una query sui media delle preferenze.
.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%;
}
}
Animazione
Un nuovo messaggio popup dovrebbe apparire con un'animazione quando entra nella schermata.
Per adattarsi a un movimento ridotto, imposta i valori translate
su 0
per default, ma aggiorna il valore di movimento in base alla durata in una query media per le preferenze di movimento. Tutti gli utenti visualizzano un'animazione, ma solo alcuni vedono il popup che si sposta.
Di seguito sono riportati i fotogrammi chiave utilizzati per l'animazione del popup. Il CSS controllerà l'ingresso, l'attesa e l'uscita del messaggio popup, il tutto in un'unica animazione.
@keyframes fade-in {
from { opacity: 0 }
}
@keyframes fade-out {
to { opacity: 0 }
}
@keyframes slide-in {
from { transform: translateY(var(--_travel-distance, 10px)) }
}
L'elemento popup imposta quindi le variabili e orchestra i fotogrammi chiave.
.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
Una volta pronti gli stili e l'HTML accessibile agli screen reader, è necessario JavaScript per orchestrare la creazione, l'aggiunta e l'eliminazione di toast in base agli eventi dell'utente. L'esperienza dello sviluppatore del componente popup deve essere minima e facile da iniziare a utilizzare, ad esempio:
import Toast from './toast.js'
Toast('My first toast')
Creazione del gruppo di toast e dei toast
Quando il modulo di notifica viene caricato da JavaScript, deve creare un contenitore di notifica
e aggiungerlo alla pagina. Ho scelto di aggiungere l'elemento prima di body
, in modo da ridurre al minimo i problemi di impilamento di z-index
, poiché il contenitore è sopra il contenitore per tutti gli elementi del corpo.
const init = () => {
const node = document.createElement('section')
node.classList.add('gui-toast-group')
document.firstElementChild.insertBefore(node, document.body)
return node
}
La funzione init()
viene chiamata internamente al modulo e memorizza l'elemento come Toaster
:
const Toaster = init()
La creazione dell'elemento HTML di una notifica viene eseguita con la funzione createToast()
. La funzione richiede del testo per il messaggio popup, crea un elemento <output>
, lo decora con alcune classi e attributi, imposta il testo e restituisce il nodo.
const createToast = text => {
const node = document.createElement('output')
node.innerText = text
node.classList.add('gui-toast')
node.setAttribute('role', 'status')
return node
}
Gestione di uno o più toast
Ora JavaScript aggiunge un contenitore al documento per contenere i popup e può aggiungere i popup creati. La funzione addToast()
orchestra la gestione di uno o più popup. Innanzitutto, controlla il numero di toast e se il movimento è corretto, poi utilizza queste informazioni per aggiungere il toast o creare un'animazione elaborata in modo che gli altri toast sembrino "fare spazio" al nuovo toast.
const addToast = toast => {
const { matches:motionOK } = window.matchMedia(
'(prefers-reduced-motion: no-preference)'
)
Toaster.children.length && motionOK
? flipToast(toast)
: Toaster.appendChild(toast)
}
Quando aggiungi il primo messaggio, Toaster.appendChild(toast)
aggiunge un messaggio alla pagina attivando le animazioni CSS: animazione in, attesa 3s
, animazione fuori.
flipToast()
viene chiamato quando sono presenti toast esistenti, utilizzando una tecnica chiamata FLIP di Paul
Lewis. L'idea è calcolare la differenza tra le posizioni del contenitore prima e dopo l'aggiunta del nuovo messaggio popup.
Pensa a come se dovessi contrassegnare la posizione attuale della tostiera e dove si troverà, quindi animare il movimento dalla posizione iniziale a quella finale.
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 griglia CSS gestisce il layout. Quando viene aggiunto un nuovo messaggio popup, la griglia lo inserisce all'inizio e lo inserisce tra gli altri. Nel frattempo, viene utilizzata un'animazione web per animare il contenitore dalla vecchia posizione.
Mettere insieme tutto il codice JavaScript
Quando viene chiamato Toast('my first toast')
, viene creato un avviso popup, aggiunto alla pagina
(forse anche il contenitore è animato per contenere il nuovo avviso popup), una
promessa
viene restituita e viene
guardato l'avviso popup creato per
il completamento dell'animazione CSS (le tre animazioni per i fotogrammi chiave) per la risoluzione della promessa.
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()
})
}
La parte più confusa di questo codice è la funzione Promise.allSettled()
e la mappatura toast.getAnimations()
. Dato che per il toast ho usato diverse animazioni di fotogrammi chiave
per sapere con sicurezza che tutte sono state terminate, ognuna deve essere
richiesta da JavaScript e ognuna delle sue
promesse di finished
è stata osservata per il completamento.
allSettled
funziona in questo modo, risolvendosi come completa una volta che tutte le sue promesse
sono state rispettate. L'utilizzo di await Promise.allSettled()
significa che la riga di codice successiva può rimuovere l'elemento in tutta sicurezza e presuppone che il messaggio popup abbia completato il suo ciclo di vita. Infine, la chiamata a resolve()
soddisfa la promessa di Toast di alto livello, in modo che gli sviluppatori possano eseguire la pulizia o altri lavori una volta visualizzato il messaggio.
export default Toast
Infine, la funzione Toast
viene esportata dal modulo, per l'importazione e l'utilizzo da parte di altri script.
Utilizzo del componente Toast
Per utilizzare il messaggio popup o l'esperienza dello sviluppatore del messaggio popup, importa la funzione Toast
e chiamala con una stringa di messaggio.
import Toast from './toast.js'
Toast('Wizard Rose added to cart')
Se lo sviluppatore vuole eseguire la pulizia o altro, dopo la visualizzazione del messaggio popup, può utilizzare asincrono e await.
import Toast from './toast.js'
async function example() {
await Toast('Wizard Rose added to cart')
console.log('toast finished')
}
Conclusione
Ora che sai come ho fatto, come faresti? 🙂
Diversifichiamo i nostri approcci e impariamo tutti i modi per creare sul web. Crea una demo, twittami i link e io la aggiungerò alla sezione dei remix della community di seguito.
Remix della community
- @_developit con HTML/CSS/JS: demo e codice
- Joost van der Schee con HTML/CSS/JS: demo e codice