Creazione di un componente toast

Una panoramica di base su come creare un componente di Toast adattivo e accessibile.

In questo post voglio condividere i miei pensieri su come creare un componente toast. Prova la demo.

Demo

Se preferisci i video, ecco una versione di questo post su YouTube:

Panoramica

I toast sono messaggi brevi non interattivi, passivi e asincroni per gli utenti. Generalmente vengono utilizzati come pattern di feedback sull'interfaccia per informare l'utente sui risultati di un'azione.

Interazioni

I toast sono diversi dalle notifiche, avvisi e messaggi perché non sono interattivi, non devono essere ignorate o persistenti. Le notifiche servono per informazioni più importanti, per la messaggistica sincrona che richiede l'interazione o messaggi a livello di sistema (anziché a livello di pagina). I toast sono più passivi delle altre strategie di notifica.

Aumento

La <output> è una buona scelta per il toast, perché viene annunciato lettori. Il codice HTML corretto fornisce una base sicura per migliorare con JavaScript e e c'è molto codice JavaScript.

Un toast

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

Può essere di più inclusivo aggiungendo role="status". Ciò fornisce un di riserva se il browser non fornisce agli elementi <output> la ruolo in base alle specifiche.

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

Un contenitore dei toast

È possibile visualizzare più toast alla volta. Per orchestrare più elementi viene usato un container. Questo contenitore gestisce anche la posizione 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 inset-block-end dall'area visibile e, se vengono aggiunti altri toast, si impilano dal bordo dello schermo.

Container GUI

Il contenitore dei toast si occupa di tutto il lavoro di layout per la presentazione dei toast. È fixed all'area visibile e utilizza la proprietà logica inset per specificare quale bordi su cui 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;
}

Screenshot con dimensioni della casella DevTools e spaziatura interna sovrapposti a un elemento contenitore .gui-toast-container.

Oltre a posizionarsi all'interno dell'area visibile, il contenitore toast è una contenitore in grado di allineare e distribuire i toast. Gli elementi sono centrati come gruppo con justify-content e centrato 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;
}

Screenshot con l&#39;overlay della griglia CSS sul gruppo popup, questa volta
mettendo in evidenza lo spazio e gli intervalli tra gli elementi secondari della funzionalità &quot;Toast&quot;.

Toast con GUI

Un singolo toast ha alcuni padding, alcuni angoli più morbidi con border-radius, e una funzione min() per sono utili per il dimensionamento di dispositivi mobili e computer. Le dimensioni adattabili nel seguente CSS impedisce che i toast crescano più del 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;
}

Screenshot di un singolo elemento .gui-toast, con la spaziatura interna e il bordo
viene mostrato.

Stili

Dopo aver impostato il layout e il posizionamento, aggiungi CSS che aiutano ad adattarti all'utente impostazioni e interazioni.

Contenitore toast

I toast non sono interattivi, toccare o far scorrere i toast non serve a nulla, attualmente consumano eventi puntatore. Evita che i toast rubino di clic con il seguente CSS.

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

Toast con GUI

Assegna ai toast un tema adattivo chiaro o scuro con proprietà personalizzate, HSL e un query supporti 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

Dovrebbe apparire un nuovo avviso popup con un'animazione quando entra nella schermata. La gestione del movimento ridotto avviene impostando i valori di translate su 0 entro il il valore predefinito, ma aggiorna il valore di movimento a una lunghezza in un contenuto multimediale con preferenze di movimento query . Tutti ricevono un'animazione, ma solo alcuni utenti hanno l'occasione di toast una distanza.

Di seguito sono riportati i fotogrammi chiave utilizzati per l'animazione toast. Il CSS controllerà i l'ingresso, l'attesa e l'uscita del 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 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

Con gli stili e l'HTML accessibile allo screen reader pronti, JavaScript è necessario per orchestrare la creazione, l'aggiunta e la distruzione dei toast in base alle eventi. L'esperienza degli sviluppatori con il componente toast deve essere minima e iniziare con facilità, come in questo esempio:

import Toast from './toast.js'

Toast('My first toast')

Creazione del gruppo toast e dei toast

Quando il modulo toast viene caricato da JavaScript, deve creare un contenitore toast. e aggiungerla alla pagina. Ho scelto di aggiungere l'elemento prima di body, in questo modo È improbabile che si verifichino problemi di sovrapposizione di z-index perché il container si trova al di sopra del container per a 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
}

Screenshot del gruppo toast tra i tag head e body.

La funzione init() viene chiamata internamente al modulo, archiviando l'elemento come Toaster:

const Toaster = init()

La creazione di elementi HTML toast viene completata con la funzione createToast(). La richiede del testo per il toast, crea un elemento <output>, adorna 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 al documento un contenitore per i toast e viene per aggiungere i toast creati. La funzione addToast() orchestra la gestione di uno o molti toast. Innanzitutto, controlla il numero di toast e se il movimento è corretto, poi useremo queste informazioni per aggiungere il toast o per fare qualche idea in modo che gli altri toast appaiano "fare spazio" per il nuovo brindisi.

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 toast, Toaster.appendChild(toast) aggiunge un toast al pagina che attiva le animazioni CSS: anima in entrata, attendi 3s, anima in uscita. flipToast() viene chiamato quando sono presenti toast esistenti, utilizzando una tecnica chiamata FLIP da Paul Lewis. L'idea è calcolare la differenza nelle posizioni del contenitore, prima e dopo l'aggiunta del nuovo avviso popup. È come contrassegnare il luogo in cui si trova ora il tostapane, quindi e si anima dalla posizione in cui 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',
  })
}

La griglia CSS esegue il sollevamento del layout. Quando viene aggiunto un nuovo toast, viene inserito nella griglia all'inizio e lo spazia tra le altre. Nel frattempo, un animazione è utilizzata 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 e aggiunto alla pagina (forse anche il contenitore è animato per accogliere il nuovo toast), un promettere e il toast creato viene restituito guardato per Completamento dell'animazione CSS (le tre animazioni di fotogrammi chiave) per una risoluzione 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 pensato che la parte confusa di questo codice fosse nella funzione Promise.allSettled() e toast.getAnimations(). Dato che ho usato diverse animazioni di fotogrammi chiave per il toast, per sapere con sicurezza che tutti hanno finito, ognuna deve da JavaScript e ognuno dei suoi finished promesse osservate per il completamento. allSettled se lo fa per noi, risolvendosi come completa una volta tutte le sue promesse sono state soddisfatte. Se utilizzi await Promise.allSettled(), la riga successiva può rimuovere con sicurezza l'elemento e presumere che l'avviso popup abbia durante il ciclo di vita di attività. Infine, chiamare il numero resolve() soddisfa la promessa di Toast di alto livello, gli sviluppatori possono eseguire la pulizia o svolgere altre operazioni una volta visualizzato il messaggio popup.

export default Toast

Infine, la funzione Toast viene esportata dal modulo, per gli altri script in importare e usare.

Utilizzo del componente Toast

L'utilizzo del toast, o dell'esperienza da sviluppatore del toast, avviene importando Toast e chiamandola 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 il toast possono usare le parole asincrone attendi.

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 ho fatto, come faresti‽ 🙂

Diversificaamo i nostri approcci e impariamo tutti i modi per creare sul web. Crea una demo, twittami con i link e la aggiungerò alla sezione dei remix della community qui sotto.

Remix della community