Loader


Vue d'ensemble

Dans presque tous les sites web aujourd'hui, il y a des interactions avec l'utilisateur. Que ce soit une soumission de formulaires, le chargement de contenu ou données à partir d'une API, le téléchargement de fichiers, pour n'en nommer que quelques-uns.

Il nous faut fournir des indices visuels ou sémantiques indiquant que l'application est occupée ! Vos utilisateurs, ne resteront pas immobiles pendant le traitement de ces fonctions asynchrone.

Le loader, nous permet d'indiquer aux utilisateurs ainsi qu'aux utilisateurs de lecteurs d'écran que l'application est occupée et qu'ils doivent attendre.

L'attribut aria-busy doit-être lié à l'attribut wai-aria aria-live="polite". Comme ceci, l'utilisateur utilisant une technologie d'assistance, sera notifié dès que le contenu sera chargé.

Petit exemple, simulant le chargement d'une citation tiré de la série Kaamelott, via l'api publique API citations Kaamelott

Saison :  - Episode : 

Acteur :  - Personnage : 
<button id="loadCitation" class="btn--block">Nouvelle citation</button>
<div id="citationCard" class="card" aria-live="polite" aria-busy="false";>
	<div class="card__header flex--row flex--v-center" style="--p:1em;--bdrb:1px solid var(--neutral-color-variation-3);">
		<p><b>Saison :</b>&nbsp;<span id="season"></span>&nbsp;-&nbsp;<b>Episode :</b>&nbsp;<span id="episode"></span></p>
	</div>
	<div class="panel">
		<blockquote id="citation" class="blockquote--hide-glyph">
			<p></p>
			<footer style="--bdrt:1px solid var(--neutral-color-variation-3);">
				<cite><b>Acteur :</b>&nbsp;<em><span id="actor"></span></em>&nbsp;-&nbsp;<b>Personnage :</b>&nbsp;<span id="perso"></span></cite>
			</footer>
		</blockquote>
	</div>
</div>
const citationCard = document.getElementById("citationCard");
const seasonEl =  document.getElementById("season");
const episodeEl =  document.getElementById("episode");
const citationEl =  document.querySelector("#citation > p");
const actorEl =  document.getElementById("actor");
const persoEl =  document.getElementById("perso");

const btnLoadCitation =  document.getElementById("loadCitation");

const loadAndDisplayCitation = async () => {
	citationCard.setAttribute('aria-busy', "true" );
	await window['sass-swing'].wait(1000);
	const response = await fetch('https://kaamelott.chaudie.re/api/random');
	if (!response.ok) {
		const message = `Il y a eu une erreur dans la reponse : ${response.status}`;
		citationEl.innerText = message;
		citationCard.setAttribute('aria-busy', "false" );
		throw new Error(message);
	}
	const data = await response.json();
  console.log(data);

	if (data.status !== 1) {
		const message = `Il y a eu une erreur avec l'API : (status = ${data.status} - code = ${data.code}) : ${data.error}`;
		citationEl.innerText = message;
		citationCard.setAttribute('aria-busy', "false" );
		throw new Error(message);
	}

	const { acteur, auteur, episode, personnage, saison } = data.citation.infos;
	const citation = data.citation.citation;
	actorEl.innerText = acteur;
	persoEl.innerText = personnage;
	seasonEl.innerText = saison;
	episodeEl.innerText = episode;
	citationEl.innerText = window['sass-swing'].escapeHTML(citation);

	citationCard.setAttribute('aria-busy', "false" );

}

window.addEventListener("DOMContentLoaded", () => {
	const debounceLoadCitation = window['sass-swing'].debounceAsync(loadAndDisplayCitation, 500);
	btnLoadCitation.addEventListener("click", debounceLoadCitation);
})

Personnalisation

Pour personnaliser le loader, vous pouvez surcharger le pseudo élément ::after de l'attribut [aria-busy="true"]. Ou bien écrire votre propre classe.

Il vous faudra également impérativement utiliser la propriété css allunset; afin de réinitialiser toutes les règles.

[aria-busy="true"].custom--loader::after {
  all:unset;
  content: "";
  position: absolute;
  width: .7em;
  top: 50%;
  left: 50%;
  aspect-ratio: 1;
  border-radius: 50%;
  opacity:1;
  transform: translate(-50%, -50%);
  transition: opacity .35s ease;
  animation: anim-custom-loader 1s infinite linear alternate;
}
@keyframes anim-custom-loader {
  0%  {box-shadow: 20px 0 var(--primary-color), -20px 0 var(--primary-color-variation-1) ;background: var(--primary-color) }
  33% {box-shadow: 20px 0 var(--primary-color), -20px 0 var(--primary-color-variation-1);background: var(--primary-color-variation-1)}
  66% {box-shadow: 20px 0 var(--primary-color-variation-1),-20px 0 var(--primary-color); background: var(--primary-color-variation-1)}
  100%{box-shadow: 20px 0 var(--primary-color-variation-1),-20px 0 var(--primary-color); background: var(--primary-color) }
}

Besoin d'inspiration pour vos loaders, vous pouvez consulter la série d'articles de Temani Afif sur Dev.to : I made 100 CSS loaders for your next project. Il vous en propose 1000 !