Una panoramica di base su come creare un componente di notifica adattabile 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 modello di feedback dell'interfaccia per informare l'utente circa i 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ù inclusiva
aggiungendo role="status"
. Questo fornisce un valore alternativo se il browser non assegna agli elementi <output>
il ruolo implicito in base alle specifiche.
<output role="status" class="gui-toast">Item added to cart</output>
Un contenitore di toast
È possibile visualizzare più di un messaggio popup alla volta. Per orchestrare più popup, viene utilizzato un contenitore. Questo contenitore gestisce anche la posizione delle notifiche 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 le notifiche popup sul canto
inset-block-end
del viewport e, se vengono aggiunte altre notifiche popup, queste si impileranno da quel 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 a quali bordi 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
.
Aggiungi 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 responsive nel seguente CSS impediscono ai popup di diventare più larghi del 90% dell'area visibile o di superare 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 il CSS che ti aiuta ad adattarti alle impostazioni e alle interazioni dell'utente.
Contenitore di toast
Le notifiche popup non sono interattive, quindi non succede nulla se le tocchi o scorri, ma al momento utilizzano gli eventi del cursore. Impedisci ai popup di rubare i clic con il seguente CSS.
.gui-toast-group {
pointer-events: none;
}
Toast della 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 visualizzano un'animazione, ma solo alcuni utenti vedono il popup percorrere una distanza.
Di seguito sono riportati i fotogrammi chiave utilizzati per l'animazione del popup. Il CSS controllerà l'entrata, l'attesa e l'uscita del messaggio 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
}
Gestire uno o più popup
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à in seguito, 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 messaggio popup, aggiunto alla pagina
(forse anche il contenitore è animato per adattarsi al nuovo messaggio popup), viene restituita una
promessa
e il messaggio popup creato viene
monitorato per verificare il completamento dell'animazione CSS (le tre animazioni delle keyframe) 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 è nella funzione Promise.allSettled()
e nella mappatura toast.getAnimations()
. Poiché ho utilizzato più animazioni di keyframe per il messaggio popup, per sapere con certezza che tutte sono terminate, ciascuna deve essere richiesta da JavaScript e ciascuna delle sue promesse finished
deve essere osservata per il completamento.
allSettled
lo fa per noi, risolvendosi come completata una volta soddisfatte tutte le sue promesse. 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 sia terminato 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 essere importata e utilizzata da altri script.
Utilizzo del componente Toast
Per utilizzare il messaggio popup o l'esperienza dello sviluppatore del messaggio popup, importa la funzioneToast
e chiamala con una stringa di messaggio.
import Toast from './toast.js'
Toast('Wizard Rose added to cart')
Se lo sviluppatore vuole eseguire operazioni di pulizia o altro, dopo che il messaggio popup è stato visualizzato, può utilizzare async 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