const QUERY = '(prefers-reduced-motion: no-preference)';
const isRenderingOnServer = typeof window === 'undefined';
const getInitialState = () => {
// For our initial server render, we won't know if the user
// prefers reduced motion, but it doesn't matter. This value
// will be overwritten on the client, before any animations
// occur.
return isRenderingOnServer ? true : !window.matchMedia(QUERY).matches;
};
function usePrefersReducedMotion() {
const [prefersReducedMotion, setPrefersReducedMotion] = React.useState(
getInitialState
);
React.useEffect(() => {
const mediaQueryList = window.matchMedia(QUERY);
const listener = (event) => {
setPrefersReducedMotion(!event.matches);
};
if (mediaQueryList.addEventListener) {
mediaQueryList.addEventListener('change', listener);
} else {
mediaQueryList.addListener(listener);
}
return () => {
if (mediaQueryList.removeEventListener) {
mediaQueryList.removeEventListener('change', listener);
} else {
mediaQueryList.removeListener(listener);
} };
}, []);
return prefersReducedMotion;
}Link to this headingContext
For some people, motion can be harmful.
The prefers-reduced-motion CSS media query allows us to disable animations for these folks. For our animations that are entirely in CSS (eg. CSS transitions, CSS keyframe animations), this works great 💯
What about for our animations in JavaScript, though? For example, when we use a library like React Spring or Framer Motion? We need to manage it ourselves, and it becomes a pretty tricky endeavour.
For these cases, I created a use-prefers-reduced-motion hook.
I wrote a blog post all about this:
Link to this headingListening for changes
For the best possible user experience, we want to re-render components that rely on this hook when the user toggles prefers-reduced-motion on or off.
In older browsers, this is done with mediaQueryList.addListener(...). This syntax has been updated in newer browsers to be a bit more conventional: mediaQueryList.addEventListener('change', ...).
To make sure we support as many browsers as possible, we'll use both.
Link to this headingUsage
Link to this headingWith React Spring
import { useSpring, animated } from 'react-spring';
const Box = ({ isBig }) => {
const prefersReducedMotion = usePrefersReducedMotion();
const styles = useSpring({
width: 100,
height: 100,
background: 'rebeccapurple',
transform: isBig ? 'scale(2)' : 'scale(1)',
immediate: prefersReducedMotion,
});
return <animated.div style={styles}>Box!</animated.div>;
};This snippet, along with any other code on this page, is released to the public domain under the Creative Commons Zero (CC0) license(opens in new tab).
Last updated on
October 6th, 2021
