Créer un Spinner accessible

Publié par Fabrice le 11 mars 2019, temps de lecture estimé : 8 minutes Webmastering

Créer un Spinner accessible

Le spinner, animation quasi indispensable de l'expérience utilisateur, n'échappe pas à une conformité à l'accessibilité. Le mouvement créé doit respecter le Critère 13.17 du Référentiel Général d'Accessibilité pour les Administrations.

Selon les WCAG et le RGAA : les contenus en mouvement doivent être contrôlables ou ne pas excéder 5 secondes :

Critère 13.17 [A] Dans chaque page web, chaque contenu en mouvement ou clignotant est-il contrôlable par l'utilisateur ?

Test 13.17.1 : Dans chaque page web, chaque contenu en mouvement, déclenché automatiquement, vérifie-t-il une de ces conditions ?

  • La durée du mouvement est inférieure ou égale à 5 secondes.
  • L'utilisateur peut arrêter et relancer le mouvement.
  • L'utilisateur peut afficher et masquer le contenu en mouvement.
  • L'utilisateur peut afficher la totalité de l'information sans le mouvement.

Critère 13.17 du RGAA

Dans le cas d'un spinner, en matière d'expérience utilisateur, arrêter le mouvement reviendrait à indiquer un crash du script. Il est donc préférable d'en donner le contrôle à l'internaute via un bouton et un peu de javascript. Il pourra ainsi, à sa guise, suspendre le mouvement.
Note : On est peut-être ici dans un cas un peu extrême du simple fait qu'un spinner ne s'affichera que quelques secondes dans la plupart des cas.

Il n'est évidemment plus question d'utiliser des GIF ou PNG animés, mais de passer au couple SVG / CSS (la base du spinner a été créée par Fabio Ottaviani). Le bouton, est lui, composé de deux éléments de contrôle en SVG et d'un texte masqué, sauf pour les lecteurs d'écran.

Évidemment, il est à supposer qu'il n'y ait pas d'autres informations en dur permettant d'indiquer à l'utilisateur qu'il doit attendre le chargement du contenu. Dans le cas inverse le spinner peut-être masqué aux lecteurs d'écran avec la balise aria-hidden="true" (le bouton restera accessible via la navigation au clavier).

Le code HTML de base

<div role="alert" aria-live="assertive">
	<!-- Spinner -->
	<p id="loading-spinner">
		<svg class="spinner" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="50" height="50" viewBox="0 0 50 50" role="img">
			<title>Loading, please wait...</title>
			<circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
		</svg>
	</p>
	<!-- Play/Pause Button -->
	<p>
		<button class="spinner-button" aria-controls="loading-spinner">
			<svg class="playpause" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14" height="14" viewBox="0 0 14 14" role="img">
				<g class="pause">
					<path d="M3,3h4v10h-4M9,3h4v10h-4z"></path>
				</g>
				<g class="play">
					<path d="M3,3l10,5l-10,5z"></path>
				</g>
			</svg>		
			<span class="visually-hidden">Pause spinner</span>
		</button>
	</p>
</div>

Le code CSS

div {
	margin: 5em auto;
	padding: 1em;
	width: 30%;
	height: 100px;
	text-align: center;
	border: 1px dashed #ccc;
	position:relative;	
}
.visually-hidden {
	position: absolute !important;
	border: 0 !important;
	height: 1px !important;
	width: 1px !important;
	padding: 0 !important;
	overflow: hidden !important;
	clip: rect(0, 0, 0, 0) !important;
}
/* Spinner : https://codepen.io/supah/pen/BjYLdW */
.spinner {
	animation: rotate 2s linear infinite;
	animation-play-state: running;
	width: 50px;
	height: 50px;
}  
.spinner .path {
	stroke: #494949;
	stroke-linecap: round;
	animation: dash 1.5s ease-in-out infinite;
	animation-play-state: running;
}
@keyframes rotate {
	100% {
		transform: rotate(360deg);
	}
}
@keyframes dash {
	0% {
		stroke-dasharray: 1, 150;
		stroke-dashoffset: 0;
	}
	50% {
		stroke-dasharray: 90, 150;
		stroke-dashoffset: -35;
	}
	100% {
		stroke-dasharray: 90, 150;
		stroke-dashoffset: -124;
	}
}
/* Play/Pause Button */
.spinner, 
.spinner-button {
    top: calc(50% - 25px);
    left: calc(50% - 25px);
    position: absolute;
}
.spinner-button {
	width: 50px;
	height: 50px;
	border:none;
	background: none;
}
/* Play/Pause Action - IE10+ */
.spinner.paused,
.spinner .path.paused {
    animation-play-state: paused;
    opacity: 0.9;
}
.spinner .path.paused {
    animation: none;
}
.playpause {
	stroke: #494949;
	fill: #494949;
    opacity: 0.9;	
}
.play,
.playpause.paused .pause {
	display: none;	
}
.playpause.paused .play {
	display: block;	
}

Le JavaScript (en Vanilla) pour contrôler le tout

var spinner = document.querySelector('.spinner');
var spinnerpath = document.querySelector('.path');
var button  = document.querySelector('.spinner-button');
var buttonspan  = document.querySelector('.visually-hidden');
var buttonsvg  = document.querySelector('.playpause');

button.addEventListener('click', function () {

    if(spinner.classList.contains('paused')) {
        spinner.classList.remove('paused'); 
        spinnerpath.classList.remove('paused');
        buttonsvg.classList.remove('paused');
        buttonspan.innerText = 'Pause spinner';
    } else {
        spinner.classList.add('paused');
        spinnerpath.classList.add('paused'); 
        buttonsvg.classList.add('paused');    
        buttonspan.innerText = 'Play spinner';
    }
	
},false);

Voir la démo sur CodePen