Présentation de base sur la création d'un composant toast adaptatif et accessible.
Dans cet article, je vais vous expliquer comment créer un composant toast. Essayez la démonstration.
Si vous préférez les vidéos, voici une version YouTube de cet article :
Présentation
Les toasts sont des messages courts, passifs, asynchrones et non interactifs destinés aux utilisateurs. Ils sont généralement utilisés comme modèle de commentaires d'interface pour informer l'utilisateur des résultats d'une action.
Interactions
Les toasts sont différents des notifications, des alertes et des invites, car ils ne sont pas interactifs. Ils ne sont pas censés être ignorés ni persister. Les notifications sont réservées aux informations importantes, aux messages synchrones nécessitant une interaction ou aux messages au niveau du système (par opposition à ceux au niveau de la page). Les toasts sont plus passifs que les autres stratégies de notification.
Annoter
L'élément <output>
est un bon choix pour le toast, car il est annoncé aux lecteurs d'écran. Un code HTML correct constitue une base sûre que nous pouvons améliorer avec JavaScript et CSS. Et il y aura beaucoup de JavaScript.
Un toast
<output class="gui-toast">Item added to cart</output>
Il peut être plus inclusif en ajoutant role="status"
. Cela fournit une solution de secours si le navigateur n'attribue pas aux éléments <output>
le rôle implicite conformément à la spécification.
<output role="status" class="gui-toast">Item added to cart</output>
Un conteneur de toast
Plusieurs toasts peuvent être affichés en même temps. Un conteneur est utilisé pour orchestrer plusieurs toasts. 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 au inset-block-end
de la fenêtre d'affichage. Si d'autres toasts sont ajoutés, ils s'empilent à partir de ce bord de l'écran.
Conteneur d'UI
Le conteneur de toasts se charge de la 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 les bords à épingler, ainsi qu'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;
}
En plus de se positionner dans la fenêtre d'affichage, le conteneur de toast est un conteneur de grille qui peut aligner et distribuer les toasts. Les éléments sont centrés en tant que groupe avec justify-content
et 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;
}
Toast de l'IUG
Un toast individuel comporte des coins padding
et des coins plus doux avec border-radius
, ainsi qu'une fonction min()
pour faciliter le dimensionnement sur mobile et ordinateur. La taille responsive dans le CSS suivant empêche les toasts de s'étendre au-delà de 90 % de la fenêtre d'affichage ou de 25ch
.
.gui-toast {
max-inline-size: min(25ch, 90vw);
padding-block: .5ch;
padding-inline: 1ch;
border-radius: 3px;
font-size: 1rem;
}
Styles
Une fois la mise en page et le positionnement définis, ajoutez le code CSS qui permet de s'adapter aux paramètres et aux interactions de l'utilisateur.
Conteneur de toast
Les toasts ne sont pas interactifs. Appuyer dessus ou balayer l'écran ne fait rien, mais ils consomment actuellement des événements de pointeur. Empêchez les toasts de voler des clics avec le CSS suivant.
.gui-toast-group {
pointer-events: none;
}
Toast de l'IUG
Donnez aux toasts un thème adaptatif clair ou sombre avec des propriétés personnalisées, HSL et une 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 entre dans l'écran.
Pour tenir compte de la réduction du mouvement, les valeurs translate
sont définies sur 0
par défaut, mais la valeur de mouvement est mise à jour sur une longueur dans une requête média de préférence de mouvement . Tout le monde bénéficie d'une animation, mais seuls certains utilisateurs voient le toast se déplacer.
Voici les images clés utilisées pour l'animation du toast. Le CSS contrôlera l'entrée, l'attente et la sortie du toast, le tout dans 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 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
Une fois les styles et le code HTML accessible au lecteur d'écran prêts, JavaScript est nécessaire pour orchestrer la création, l'ajout et la suppression des toasts en fonction des événements utilisateur. L'expérience du développeur avec le composant toast doit être minimale et facile à 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 à partir de JavaScript, il doit créer un conteneur de toast et l'ajouter à la page. J'ai choisi d'ajouter l'élément avant body
. Les problèmes d'empilement de z-index
sont ainsi peu probables, car le conteneur se trouve au-dessus du conteneur de 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
}
La fonction init()
est appelée en interne au module, en stockant l'élément sous la forme Toaster
:
const Toaster = init()
La création d'éléments HTML de toast est effectuée avec la fonction createToast()
. La fonction nécessite du texte pour le toast, crée un élément <output>
, l'agrémente de classes et d'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 désormais un conteneur au document pour contenir les toasts et est prêt à ajouter les toasts créés. La fonction addToast()
orchestre la gestion d'un ou de plusieurs toasts. Vérifiez d'abord le nombre de toasts et si le mouvement est correct, puis utilisez ces informations pour ajouter le toast ou effectuer une animation sophistiquée afin 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)
}
Lorsque vous ajoutez le premier toast, Toaster.appendChild(toast)
ajoute un toast à la page, ce qui déclenche les animations CSS : animation d'entrée, attente 3s
, animation de sortie.
flipToast()
est appelé lorsqu'il existe des toasts, en utilisant une technique appelée FLIP par Paul Lewis. L'idée est de calculer la différence de position du conteneur avant et après l'ajout du nouveau toast.
Imaginez que vous marquez l'emplacement actuel du grille-pain, celui où il va se trouver, puis que vous l'animez de son emplacement initial à son emplacement final.
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 la mise en page. Lorsqu'un nouveau toast est ajouté, la grille le place au début et l'espace avec les autres. Pendant ce temps, une animation Web est utilisée pour animer le conteneur à partir de l'ancienne position.
Regrouper tout le code JavaScript
Lorsque Toast('my first toast')
est appelé, un toast est créé, ajouté à la page (le conteneur peut même être animé pour s'adapter au nouveau toast), une promesse est renvoyée et le toast créé est observé pour la fin de l'animation CSS (les trois animations de keyframe) 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()
})
}
La partie déroutante de ce code se trouve dans la fonction Promise.allSettled()
et le mappage toast.getAnimations()
. Comme j'ai utilisé plusieurs animations de keyframes pour le toast, pour être sûr qu'elles sont toutes terminées, chacune doit être demandée à partir de JavaScript et chacune de leurs promesses finished
doit être observée pour la fin.
allSettled
fonctionne pour nous, en se résolvant comme terminé une fois que toutes ses promesses ont été tenues. L'utilisation de await Promise.allSettled()
signifie que la ligne de code suivante peut supprimer l'élément en toute confiance et supposer que le toast a terminé son cycle de vie. Enfin, l'appel de resolve()
remplit la promesse de Toast de haut niveau afin que les développeurs puissent nettoyer ou effectuer d'autres tâches une fois le toast affiché.
export default Toast
Enfin, la fonction Toast
est exportée du module pour que d'autres scripts puissent l'importer et l'utiliser.
Utiliser le composant Toast
Pour utiliser le toast ou l'expérience de développement du toast, importez la fonction Toast
et appelez-la avec une chaîne de message.
import Toast from './toast.js'
Toast('Wizard Rose added to cart')
Si le développeur souhaite effectuer un nettoyage ou autre chose après l'affichage du toast, il peut utiliser async et await.
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 ? 🙂
Diversifions nos approches et découvrons toutes les façons de créer sur le Web. Créez une démo, tweetez-moi les liens et je l'ajouterai à la section des remix de la communauté ci-dessous !
Remix de la communauté
- @_developit avec HTML/CSS/JS : démo et code
- Joost van der Schee avec HTML/CSS/JS : démonstration et code