Animation can be a great tool to make apps seem more performant, be more engaging or even just more interesting. There are, however, quite a few groups of people who can not deal with things animating, as it distracts them or can even cause nausea. This is why operating systems allow you to enable "reduced motion" which turns of all unnecessary animation in the OS. Our own apps should also abide by this setting in order to be accessible, as it is even a WCAG Success Criterion.
Detecting prefers-reduced-motion
For browser based apps, we have a media feature that gets triggered when a user has reduced motion turned on called prefers-reduced-motion. This one can be used in CSS, but also as a media attribute on link elements.
In JavaScript, we can use matchmedia(), which means that checking if animations are wanted or not is a one liner (here on 3 to be more readable):
let prefersReducedMotion = window.matchMedia(
'(prefers-reduced-motion: reduce)'
).matches;
On devices with reduced motion turned on, prefersReducedMotion
is true
, otherwise it is false.
We can use this to simply not show animations when it is false. Take for example a button that picks one item from an array of categories. When reduced motion is not turned requested, we show a random amount of options before we get to the last one. When it is requested, we just show one category every time the user activates the button. You can try this in the reduced no checkbox codepen:
The code is pretty straight forward:
<button>Pick Category</button>
<output></output>
// test for reduced motion
let prefersReducedMotion = window.matchMedia(
'(prefers-reduced-motion: reduce)'
).matches;
// define categories
let categories = [
'Accessibility','AI/ML','Command Line tools','Code'
// … more …
];
let counter = 0; // repeat counter
// dom elements
const button = document.querySelector('button');
const out = document.querySelector('output');
// recursive function to call counter times
const shuffle = counter => {
// show a random category
out.innerText = categories[
Math.floor(Math.random() * categories.length)
];
// if animation is wanted
if (!prefersReducedMotion) {
// turn off button
button.disabled = true;
// reduce counter and call shuffle again after 100ms
counter = counter - 1;
if (counter > 0){
setTimeout(() => { shuffle(counter); }, 100);
// if counter is zero, enable button
} else {
button.disabled = false;
}
}
};
// When the button is clicked, define counter as
// a random number between 0 and 30 and call shuffle
document.querySelector('button').addEventListener('click', e => {
counter = Math.random() * 30 | 0;
shuffle(counter);
});
Allowing for manual override
This is a simple example, but whilst people have requested reduced motion, they may still want to have animations from time to time. To enable that, we can offer a checkbox so they can turn it on and off. You can see this in action in the Animation Test codepen:
The main difference is that we add a checkbox to the HTML:
<input id="animated" type="checkbox">
<label for="animated">Animated</label>
<p>
<button>Pick Category</button>
<output></output>
</p>
And in the JavaScript, we set this to the state of media feature:
let prefersReducedMotion = window.matchMedia(
'(prefers-reduced-motion: reduce)'
).matches
document.querySelector('input').checked = !prefersReducedMotion
We then need to override the prefersReducedMotion
value when the user chooses to have animations. This is a simple event handler on the checkbox:
document.querySelector('input').addEventListener(
'change', e => {
prefersReducedMotion = !prefersReducedMotion;
}
);
That way, we offer the best of both states. We turn off animations by default when the user has reduced motion enabled, and we allow them to override this.
If you use a Chromium browser, you can even simulate reduced motion without having to change it in the operating system.