Créer un composant de toast

Présentation générale de la création d'un composant de toast adaptatif et accessible.

Dans ce post, j'aimerais vous expliquer comment créer un composant toast. Essayez le demo.

Démonstration

Si vous préférez la vidéo, voici une version YouTube de cet article:

Présentation

Les toasts sont des messages courts non interactifs, passifs et asynchrones pour les utilisateurs. Elles servent généralement de modèle de rétroaction pour informer l'utilisateur sur les résultats d'une action.

Interactions

Les toasts sont différents des notifications, alertes et invites, car elles ne sont pas interactives ; ils ne sont pas destinés à être ignorés ni à persister. Les notifications sont destinées à des informations plus importantes, à la messagerie synchrone nécessite une interaction ou des messages au niveau du système (par opposition au niveau de la page). Les toasts sont plus passifs que les autres stratégies de notification.

Majoration

La <output> est un bon choix pour le toast, car il est annoncé à l'écran. lecteurs. Le code HTML correct constitue une base sûre pour l'amélioration que nous pouvons apporter à l'aide de JavaScript et et il y aura beaucoup de code JavaScript.

Un toast

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

Il peut s'agir de plus inclusifs en ajoutant role="status". Vous bénéficiez ainsi d'une création de remplacement si le navigateur ne donne pas aux éléments <output> les caractères implicites rôle conformément aux spécifications.

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

Conteneur de toast

Vous pouvez afficher plusieurs toasts à la fois. Pour orchestrer plusieurs des toasts, un conteneur est utilisé. Ce conteneur gère également la position des toasts à l'écran.

<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>

Mises en page

J'ai choisi d'épingler les toasts sur la inset-block-end de la fenêtre d'affichage. Si d'autres toasts sont ajoutés, ils s'empilent à partir du bord de l'écran.

Conteneur d'IUG

Le conteneur de toasts effectue toutes les opérations de mise en page pour présenter les toasts. Il est fixed à la fenêtre d'affichage et utilise la propriété logique ; inset pour spécifier bords à épingler, plus un peu de padding à partir du même bord block-end.

.gui-toast-group {
  position: fixed;
  z-index: 1;
  inset-block-end: 0;
  inset-inline: 0;
  padding-block-end: 5vh;
}

Capture d&#39;écran avec la taille de la zone d&#39;outils de développement et une marge intérieure en superposition sur un élément .gui-toast-container.

En plus de se positionner dans la fenêtre d'affichage, le conteneur de toast est qui peut aligner et distribuer des toasts. Les éléments sont centrés groupe avec justify-content et centré individuellement avec justify-items. Ajoutez un peu de gap pour que les toasts ne se touchent pas.

.gui-toast-group {
  display: grid;
  justify-items: center;
  justify-content: center;
  gap: 1vh;
}

Capture d&#39;écran avec la superposition de la grille CSS sur le groupe de toast, cette fois
en mettant en évidence l&#39;espace et les espaces entre les éléments enfants de toast.

Toast GUI

Un toast individuel comporte des padding, des coins plus doux avec border-radius, et une fonction min() pour qui permettent de dimensionner les mobiles et les ordinateurs de bureau. La taille responsive dans le CSS suivant empêche les toasts de passer sur plus de 90% de la fenêtre d'affichage 25ch

.gui-toast {
  max-inline-size: min(25ch, 90vw);
  padding-block: .5ch;
  padding-inline: 1ch;
  border-radius: 3px;
  font-size: 1rem;
}

Capture d&#39;écran d&#39;un seul élément .gui-toast, avec une marge intérieure et une bordure
du rayon indiqué.

Styles

Une fois la mise en page et le positionnement définis, ajoutez du code CSS qui facilite l'adaptation à l'utilisateur paramètres et interactions.

Conteneur de toast

Les toasts ne sont pas interactifs. Appuyer dessus ou les balayer n'a aucun effet, ils consomment actuellement des événements de pointeur. Empêcher le vol des toasts avec le CSS suivant.

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

Toast GUI

Donnez aux toasts un thème adaptatif clair ou sombre avec des propriétés personnalisées, un flux HSL requête média de préférence.

.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%;
  }
}

Animation

Un nouveau toast doit s'afficher avec une animation lorsqu'il s'affiche à l'écran. Pour prendre en compte les mouvements réduits, définissez les valeurs translate sur 0 en Valeur par défaut, mais mise à jour de la valeur du mouvement sur une durée dans un média de préférence de mouvement requête . Tout le monde voit de l'animation, mais seuls certains utilisateurs se déplacent avec un toast une distance.

Voici les images clés utilisées pour l'animation de toast. Le CSS contrôlera le l'entrée, l'attente et la sortie du toast, le tout en une seule animation.

@keyframes fade-in {
  from { opacity: 0 }
}

@keyframes fade-out {
  to { opacity: 0 }
}

@keyframes slide-in {
  from { transform: translateY(var(--_travel-distance, 10px)) }
}

L'élément de toast configure ensuite les variables et orchestre les images clés.

.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

Les styles et le lecteur d'écran étant compatibles avec le code HTML, JavaScript est nécessaire pour d'orchestrer la création, l'ajout et la destruction de toasts en fonction de l'utilisateur événements. L'expérience du développeur avec le composant toast doit être minimale et sont faciles à prendre en main, comme ceci:

import Toast from './toast.js'

Toast('My first toast')

Créer le groupe de toasts et les toasts

Lorsque le module de toast se charge depuis JavaScript, il doit créer un conteneur de toast et l'ajouter à la page. J'ai choisi d'ajouter l'élément avant body. z-index problèmes d'empilement peu probables, car le conteneur se trouve au-dessus de celui-ci pendant tous les éléments du corps.

const init = () => {
  const node = document.createElement('section')
  node.classList.add('gui-toast-group')

  document.firstElementChild.insertBefore(node, document.body)
  return node
}

Capture d&#39;écran du groupe de toast entre les balises &quot;head&quot; et &quot;body&quot;.

La fonction init() est appelée en interne dans le module, ce qui permet de cacher l'élément. en tant que Toaster:

const Toaster = init()

La création d'un élément HTML de toast s'effectue à l'aide de la fonction createToast(). La nécessite du texte pour le toast, crée un élément <output> et décore avec quelques classes et attributs, définit le texte et renvoie le nœud.

const createToast = text => {
  const node = document.createElement('output')
  
  node.innerText = text
  node.classList.add('gui-toast')
  node.setAttribute('role', 'status')

  return node
}

Gérer un ou plusieurs toasts

JavaScript ajoute maintenant au document un conteneur pour les toasts, prêt à ajouter les toasts créés. La fonction addToast() orchestre le traitement ou plusieurs toasts. Tout d'abord, vérifiez le nombre de toasts et s'il est normal de bouger. puis d'utiliser ces informations pour ajouter le toast ou pour faire un peu de fantaisie pour que les autres toasts semblent "faire de la place" pour le nouveau toast.

const addToast = toast => {
  const { matches:motionOK } = window.matchMedia(
    '(prefers-reduced-motion: no-preference)'
  )

  Toaster.children.length && motionOK
    ? flipToast(toast)
    : Toaster.appendChild(toast)
}

Lors de l'ajout du premier toast, Toaster.appendChild(toast) ajoute un toast au page déclenchant les animations CSS: animer à l'ouverture, attendre 3s, animer. flipToast() est appelé lorsqu'il existe des toasts, à l'aide d'une technique intitulée FLIP de Paul Lewis L'idée est de calculer la différence aux positions du conteneur, avant et après l'ajout du nouveau toast. C'est comme si vous aviez indiqué l'emplacement actuel du grille-pain, s'animer de l'endroit où elle se trouvait jusqu'à l'endroit où elle se trouve.

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 grille CSS se charge de soulever la mise en page. Lorsqu'un toast est ajouté, la grille le place au début et l’espace entre les autres. Parallèlement, un site Web Animation est utilisée pour animer le conteneur depuis l'ancienne position.

Assembler tout le code JavaScript

Lorsque Toast('my first toast') est appelé, un toast est créé et ajouté à la page. (peut-être même que le conteneur est animé pour accueillir le nouveau toast), une promesse est renvoyé et le toast créé est a regardé pendant Finalisation de l'animation CSS (les trois animations d'images clés) pour la résolution de la promesse.

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() 
  })
}

J'ai senti que la partie de ce code déroutante se trouvait dans la fonction Promise.allSettled() et toast.getAnimations(). Comme j'ai utilisé plusieurs animations d'images clés, d'un toast, de savoir avec certitude qu'elles sont toutes terminées, chacune doit être demandée à partir de JavaScript, finished promesses observées pour être menées à bien. allSettled est-ce que cela fonctionne pour nous, et se considère comme complet une fois toutes ses promesses ont été satisfaites. Si vous utilisez await Promise.allSettled(), la ligne suivante de code peut supprimer l'élément en toute confiance et supposer que le toast a terminé tout au long du cycle de vie. Enfin, l'appel de resolve() répond à la promesse de toast de haut niveau. Ainsi, les développeurs peuvent nettoyer ou effectuer d'autres tâches une fois le toast affiché.

export default Toast

Enfin, la fonction Toast est exportée à partir du module, afin que les autres scripts soient à importer et à utiliser.

Utiliser le composant Toast

Pour utiliser le toast, ou l'expérience de développement du toast, vous devez importer Toast et en l'appelant avec une chaîne de message.

import Toast from './toast.js'

Toast('Wizard Rose added to cart')

Si le développeur veut effectuer un nettoyage ou autre, une fois le toast terminé s'affichent, ils peuvent utiliser des métriques en attente.

import Toast from './toast.js'

async function example() {
  await Toast('Wizard Rose added to cart')
  console.log('toast finished')
}

Conclusion

Maintenant que vous savez comment j'ai fait, comment feriez-vous ? 😃

Diversifiez nos approches et découvrons toutes les manières de créer des applications sur le Web. Créer une démonstration, me envoyer des tweets et je l'ajouterai à la section des remix de la communauté ci-dessous.

Remix de la communauté