Chapitre 19 : Réduction des mouvements avec prefers-reduced-motion
Désactivation ou atténuation des animations CSS pour les utilisateurs sensibles
L'animation est l'un des outils les plus puissants du CSS moderne pour améliorer l'expérience utilisateur (UX). Elle permet de guider le regard, de confirmer une action ou de rendre une interface plus "vivante". Cependant, pour une partie non négligeable de la population, les mouvements brusques, les parallaxes ou les animations de zoom peuvent provoquer des vertiges, des nausées, voire des crises d'épilepsie.
C'est ici qu'intervient la media query prefers-reduced-motion.
Le concept de prefers-reduced-motion
1. Quoi
La media query prefers-reduced-motion est une fonctionnalité CSS qui permet de détecter si l'utilisateur a activé une option de réduction des mouvements dans les paramètres d'accessibilité de son système d'exploitation (Windows, macOS, iOS, Android).
Elle possède deux valeurs principales :
no-preference: L'utilisateur n'a exprimé aucune préférence particulière. On peut utiliser toutes les animations prévues.reduce: L'utilisateur a explicitement demandé que les mouvements soient réduits ou supprimés.
Techniquement, c'est une requête média utilisateur, au même titre que prefers-color-scheme pour le mode sombre.
2. Pourquoi
L'utilité de cette directive repose sur l'accessibilité numérique (a11y). Plusieurs profils d'utilisateurs sont concernés :
- Troubles vestibulaires : Certaines personnes souffrent de troubles de l'oreille interne. Un mouvement de défilement parallaxe ou un zoom rapide peut provoquer une sensation de désorientation spatiale et des nausées physiques.
- Épilepsie photosensible : Bien que
prefers-reduced-motionne gère pas directement le clignotement (qui est un autre problème), la réduction des mouvements brusques aide à limiter les déclencheurs potentiels. - TDAH et troubles de l'attention : Pour certains utilisateurs, des animations constantes et distractives en arrière-plan rendent la lecture du contenu principal difficile.
- Préférence personnelle : Certains utilisateurs trouvent simplement les animations lentes et préfèrent une interface instantanée pour gagner en productivité.
En ignorant ce paramètre, un développeur risque de rendre son site physiquement insupportable pour une partie de son audience.
3. Comment
A. Syntaxe de base
La méthode la plus simple consiste à entourer vos animations de cette media query pour les désactiver totalement.
/* Animation standard pour tout le monde */
.box {
transition: transform 0.3s ease-in-out;
}
.box:hover {
transform: scale(1.1);
}
/* Désactivation si l'utilisateur préfère moins de mouvement */
@media (prefers-reduced-motion: reduce) {
.box {
transition: none;
transform: none !important;
}
}
B. Cas concret : Stratégies d'atténuation
Désactiver brutalement toutes les animations peut parfois rendre l'interface "sèche" ou masquer des informations importantes (comme l'ouverture d'un menu). La meilleure pratique consiste à remplacer le mouvement par un fondu (fade), car les changements d'opacité sont généralement mieux tolérés que les déplacements spatiaux.
Voici un exemple robuste pour un bouton avec un effet de survol et un menu déroulant :
/* 1. Styles de base */
.card {
padding: 20px;
border: 1px solid #ccc;
transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275),
background-color 0.3s ease;
}
.card:hover {
transform: translateY(-10px);
background-color: #f0f8ff;
}
.dropdown-menu {
opacity: 0;
transform: translateY(-20px);
transition: opacity 0.3s ease, transform 0.3s ease;
pointer-events: none;
}
.dropdown-menu.is-open {
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
/* 2. Adaptation pour l'accessibilité */
@media (prefers-reduced-motion: reduce) {
.card {
/* On supprime le mouvement vertical (translateY)
mais on garde le changement de couleur pour le feedback visuel */
transition: background-color 0.3s ease;
}
.card:hover {
transform: none;
}
.dropdown-menu {
/* On remplace le glissement par un simple fondu */
transition: opacity 0.3s ease;
transform: none;
}
.dropdown-menu.is-open {
opacity: 1;
transform: none;
}
}
C. Limitations
- Support Navigateur : La quasi-totalité des navigateurs modernes supportent
prefers-reduced-motion. Cependant, sur de très vieux navigateurs, la media query sera simplement ignorée, et l'utilisateur verra les animations par défaut. - Animations JS : Cette media query ne s'applique qu'au CSS. Si vous utilisez des bibliothèques comme GSAP, Framer Motion ou des animations via
requestAnimationFrameen JavaScript, vous devez vérifier la préférence utilisateur via JavaScript :
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (prefersReducedMotion) {
// Désactiver les animations JS ou utiliser des alternatives simplifiées
}
4. Zone de Danger
❌ L'erreur commune : Tout supprimer sans réfléchir Certains développeurs suppriment toutes les transitions. Problème : si une transition servait à indiquer qu'un élément est apparu (comme un message d'erreur), sa disparition instantanée peut être perturbante.
✅ La bonne pratique : Substituer plutôt que supprimer L'objectif n'est pas de supprimer le feedback visuel, mais de supprimer le déplacement spatial.
- Remplacer
transform: translateX()paropacity: 1. - Remplacer un
slide-inpar unfade-in. - Remplacer une animation de rebond par un changement de couleur discret.
Flux de détection de la réduction des mouvements
Questions clés
1. Quelle est la différence entre no-preference et reduce ?
Découvrir la réponse
no-preference signifie que l'utilisateur n'a pas modifié les paramètres de son système pour limiter les mouvements. reduce signifie que l'utilisateur a explicitement demandé au système (et donc aux applications et navigateurs) de limiter les animations et les mouvements brusques.
2. Est-ce que prefers-reduced-motion désactive automatiquement les animations de mon site ?
Découvrir la réponse
Non. C'est une "sonnette d'alarme" que le navigateur vous envoie. C'est au développeur d'écrire le code CSS à l'intérieur de la media query @media (prefers-reduced-motion: reduce) pour définir comment le site doit réagir.
3. Pourquoi privilégier l'opacité (opacity) plutôt que le déplacement (transform) pour les utilisateurs sensibles ?
Découvrir la réponse
Le déplacement d'un objet dans l'espace écran crée une illusion de mouvement qui peut stimuler le système vestibulaire et provoquer des nausées. Le changement d'opacité est perçu comme un changement d'état et non comme un mouvement physique, ce qui est beaucoup mieux toléré.
4. Comment tester cette fonctionnalité sur mon propre ordinateur ?
Découvrir la réponse
- macOS : Réglages Système → Accessibilité → Affichage → Réduire les animations.
- Windows : Paramètres → Accessibilité → Effets visuels → Effets d'animation (Désactiver).
- Chrome DevTools : Inspecteur → Onglet "Rendering" (en bas) → "Emulate CSS media feature prefers-reduced-motion".
Mise en pratique
Exercice 1 : Reproduction guidée
HTML de départ :
<button class="btn">Valider</button>
Stylez ce bouton pour qu'il s'agrandisse légèrement au survol (scale(1.1)). Ajoutez ensuite la media query nécessaire pour que cet effet de zoom disparaisse complètement si l'utilisateur a activé la réduction des mouvements.
Découvrir la solution commentée
.btn {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
cursor: pointer;
/* Transition fluide pour le mode standard */
transition: transform 0.2s ease;
}
.btn:hover {
transform: scale(1.1);
}
/* Application de la règle d'accessibilité */
@media (prefers-reduced-motion: reduce) {
.btn {
/* On supprime la transition pour éviter tout mouvement */
transition: none;
}
.btn:hover {
/* On annule le zoom */
transform: none;
}
}
Exercice 2 : Adaptation (Le fondu intelligent)
HTML de départ :
<div class="notification show">Nouvelle mise à jour disponible !</div>
CSS de départ (à adapter) :
.notification {
position: fixed;
top: 0;
width: 100%;
background: #333;
color: white;
transform: translateY(-100%);
opacity: 0;
transition: transform 0.5s ease, opacity 0.5s ease;
}
.notification.show {
transform: translateY(0);
opacity: 1;
}
Ajoutez un bloc @media (prefers-reduced-motion: reduce) pour que, pour les utilisateurs sensibles, la barre n'apparaisse plus en glissant depuis le haut, mais via un fondu progressif (opacity) uniquement.
Découvrir la solution commentée
.notification {
position: fixed;
top: 0;
width: 100%;
background: #333;
color: white;
/* État initial : hors écran et invisible */
transform: translateY(-100%);
opacity: 0;
transition: transform 0.5s ease, opacity 0.5s ease;
}
.notification.show {
transform: translateY(0);
opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
.notification {
/* On fige la position en haut, on ne gère plus que l'opacité */
transform: translateY(0);
transition: opacity 0.5s ease;
}
.notification.show {
/* Seul l'opacité change, aucun mouvement spatial */
opacity: 1;
transform: translateY(0);
}
}
Exercice 3 : Conception (Le Dashboard Animé)
HTML de départ :
<div class="dashboard">
<div class="stat-card card-1">
<h3>Revenus</h3>
<p>12 450 €</p>
</div>
<div class="stat-card card-2">
<h3>Commandes</h3>
<p>348</p>
</div>
<div class="stat-card card-3">
<h3>Utilisateurs</h3>
<p>1 204</p>
</div>
</div>
Consignes :
- Définissez une
@keyframes fadeInUpqui fait passer chaque carte deopacity: 0; transform: translateY(20px)àopacity: 1; transform: translateY(0). - Appliquez cette animation à
.stat-card(durée :0.6s,forwardspour conserver l'état final). - Créez un effet cascade avec
animation-delay:0.1spour.card-1,0.2spour.card-2,0.3spour.card-3. - Dans
@media (prefers-reduced-motion: reduce): supprimez l'animation et les délais, rendez les cartes directement visibles (opacity: 1; transform: none).
Découvrir la solution commentée
.stat-card {
opacity: 0;
transform: translateY(20px);
animation: fadeInUp 0.6s forwards;
}
/* Création de délais pour l'effet cascade */
.card-1 { animation-delay: 0.1s; }
.card-2 { animation-delay: 0.2s; }
.card-3 { animation-delay: 0.3s; }
@keyframes fadeInUp {
to {
opacity: 1;
transform: translateY(0);
}
}
@media (prefers-reduced-motion: reduce) {
.stat-card {
/* On remplace l'animation complexe par une transition simple */
animation: none;
opacity: 1; /* Apparition immédiate ou via transition simple */
transform: none;
}
/* On supprime les délais qui pourraient créer une attente inutile
pour un utilisateur cherchant l'efficacité */
.card-1, .card-2, .card-3 {
animation-delay: 0;
}
}