Una panoramica fondamentale su come creare un componente toast adattivo e accessibile.
In questo post vorrei condividere riflessione su come creare un componente di un annuncio toast. Prova la demo.
Se preferisci i video, ecco una versione di YouTube di questo post:
Panoramica
I toast sono messaggi brevi non interattivi, passivi e asincroni per gli utenti. Generalmente vengono utilizzati come pattern di feedback dell'interfaccia per informare l'utente dei risultati di un'azione.
Interazioni
Gli avvisi toast sono diversi dalle notifiche, dagli avvisi e dai messaggi perché non sono interattivi e non sono pensati per essere ignorati o mantenuti. Le notifiche riguardano informazioni più importanti, messaggi sincroni che richiedono interazione o messaggi a livello di sistema (anziché a livello di pagina). I toast sono più passivi di altre strategie di notifica.
Markup
L'elemento <output>
è una buona scelta per il toast perché viene annunciato agli screen reader. Il codice HTML corretto ci offre una base sicura per il miglioramento con JavaScript e CSS e ci sarà molto codice JavaScript.
Un toast
<output class="gui-toast">Item added to cart</output>
Può essere più inclusivo aggiungendo role="status"
. Viene fornito un riserva se il browser non assegna agli elementi <output>
il ruolo implicito in base alla specifica.
<output role="status" class="gui-toast">Item added to cart</output>
Un contenitore di toast
È possibile mostrare più di un toast alla volta. Per orchestrare più toast, viene usato 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 bloccare i toast in
inset-block-end
dell'area visibile e, se vengono aggiunti altri toast, vengono impilati dal bordo dello schermo.
Contenitore GUI
Il contenitore dei toast fa tutto il lavoro di layout per presentare i toast. È
fixed
per l'area visibile e utilizza la proprietà logica
inset
per specificare a quali
bordi fissare, più 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 dei toast è un
contenitore a griglia che può allineare e distribuire i toast. Gli elementi vengono centrati come un
gruppo con justify-content
e centrati individualmente con justify-items
.
Metti un po' di gap
in modo che i toast non vengano toccati.
.gui-toast-group {
display: grid;
justify-items: center;
justify-content: center;
gap: 1vh;
}
Notifica toast con GUI
Un singolo toast ha un po' di padding
, alcuni angoli più morbidi con
border-radius
e una funzione min()
che
aiuta le dimensioni di dispositivi mobili e desktop. Le dimensioni adattabili nel seguente CSS impediscono ai toast di crescere più ampie del 90% dell'area visibile o di 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 layout e posizionamento, aggiungi CSS che facilita l'adattamento alle impostazioni e alle interazioni dell'utente.
Contenitore toast
I toast non sono interattivi. Toccarli o scorrerli non funziona, ma al momento consumano eventi di puntatore. Impedisci ai toast di sottrarre clic con il seguente CSS.
.gui-toast-group {
pointer-events: none;
}
Notifica toast con GUI
Assegna ai toast un tema adattivo chiaro o scuro con proprietà personalizzate, HSL e una query multimediale preferita.
.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
Quando entra sullo schermo, dovrebbe apparire un nuovo avviso popup con un'animazione.
Per adeguare il movimento ridotto si esegue l'impostazione dei valori translate
su 0
per
impostazione predefinita, ma l'aggiornamento del valore di movimento a una lunghezza in una query multimediale
per le preferenze di movimento . Tutti hanno un po' di animazione, ma solo alcuni utenti hanno il toast
percorrere una certa distanza.
Questi sono i fotogrammi chiave utilizzati per l'animazione del toast. CSS controllerà l'ingresso, l'attesa e l'uscita della notifica toast, 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 toast configura 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
Con gli stili e l'HTML accessibile tramite screen reader pronti, è necessario JavaScript per orchestrare la creazione, l'aggiunta e l'eliminazione dei toast in base agli eventi dell'utente. L'esperienza degli sviluppatori con il componente toast dovrebbe essere minima e facile da iniziare, come questa:
import Toast from './toast.js'
Toast('My first toast')
Creazione del gruppo di toast e dei toast in corso...
Quando il modulo toast viene caricato da JavaScript, deve creare un contenitore toast e aggiungerlo alla pagina. Ho scelto di aggiungere l'elemento prima del giorno body
, in modo da rendere improbabili i problemi di sovrapposizione di z-index
, in quanto il container si trova sopra il container 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, nascondendo l'elemento come Toaster
:
const Toaster = init()
La creazione di elementi HTML toast viene eseguita con la funzione createToast()
. La
funzione richiede del testo per il toast, 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 container al documento per contenere i toast ed è pronto per aggiungere gli toast creati. La funzione addToast()
orchestra la gestione di uno
o più toast. Prima controlla il numero dei toast e controlla se il movimento è buono, poi usa queste informazioni per aggiungere il toast o creare un'animazione
fantastica in modo che gli altri toast facciano 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 aggiunge il primo avviso popup, Toaster.appendChild(toast)
aggiunge un avviso popup alla pagina che attiva le animazioni CSS: anima in entrata, attendi 3s
, animazione fuori.
flipToast()
viene chiamato quando sono presenti dei toast esistenti che utilizza una tecnica chiamata FLIP di Paul Lewis. L'idea è calcolare la differenza nelle posizioni
del container prima e dopo l'aggiunta del nuovo toast.
È come se segnare dove si trova il tostapane, dove andrà
e poi creare un'animazione dal punto in cui si trovava a dove si trova.
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',
})
}
Il sollevamento del layout viene eseguito tramite la griglia CSS. Quando viene aggiunto un nuovo toast, la griglia lo inserisce all'inizio e lo spazia con gli altri. Nel frattempo, viene utilizzata un'animazione web per animare il contenitore dalla posizione precedente.
Inserimento di tutto il codice JavaScript
Quando viene chiamato Toast('my first toast')
, viene creato un toast, aggiunto alla pagina
(magari anche il contenitore è animato per inserire il nuovo toast), viene restituita una
promessa
e viene guardato il toast creato per
completare l'animazione CSS (le tre animazioni di 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()
})
}
Ho sentito che la parte confusa di questo codice è nella funzione Promise.allSettled()
e nella mappatura toast.getAnimations()
. Poiché ho utilizzato più animazioni con fotogrammi chiave per il toast, per avere la certezza che siano stati tutti completati è necessario richiedere a JavaScript e ciascuna promessa finished
per il completamento.
allSettled
funziona per noi, risolvendosi come completo una volta che tutte le promesse
sono state soddisfatte. L'utilizzo di await Promise.allSettled()
significa che la riga di codice successiva può rimuovere con sicurezza l'elemento e presumere che il toast 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 eseguire altre operazioni dopo la visualizzazione del messaggio toast.
export default Toast
Infine, la funzione Toast
viene esportata dal modulo, per l'importazione e l'utilizzo di altri script.
Utilizzo del componente Toast
Per utilizzare il toast, o l'esperienza di sviluppo del toast, puoi importare la funzione Toast
e chiamarla con una stringa di messaggio.
import Toast from './toast.js'
Toast('Wizard Rose added to cart')
Se lo sviluppatore vuole eseguire un lavoro di pulizia o altro, dopo la visualizzazione del toast, può utilizzare il comando 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 ci sono riuscito, come faresti? 🙂
Diversifica i nostri approcci e scopriamo tutti i modi per creare sul web. Crea una demo, inviami un tweet con i link e lo aggiungerò alla sezione 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