Tag: motion

Different Approaches to Responsive CSS Motion Path

As a follow-up to Jhey’s recent post on responsive motion paths, Michelle Barker notes that another approach could be to just transform: scale() the whole dang element.

The trade-off there is that you’re scaling both the path and the element on the path at the same time; Jhey’s approach only makes path flexbile and the element stays the same size.

Calculating scale is a really cool trick I think and one we’ve also covered before.

Direct Link to ArticlePermalink

The post Different Approaches to Responsive CSS Motion Path appeared first on CSS-Tricks.


, , , ,

Create a Responsive CSS Motion Path? Sure We Can!

There was a discussion recently on the Animation at Work Slack: how could you make a CSS motion path responsive? What techniques would be work? This got me thinking.

A CSS motion path allows us to animate elements along custom user-defined paths. Those paths follow the same structure as SVG paths. We define a path for an element using offset-path.

.block {   offset-path: path('M20,20 C20,100 200,0 200,100'); }

These values appear relative at first and they would be if we were using SVG. But, when used in an offset-path, they behave like px units. This is exactly the problem. Pixel units aren’t really responsive. This path won’t flex as the element it is in gets smaller or larger. Let’s figure this out.

To set the stage, the offset-distance property dictates where an element should be on that path:

Not only can we define the distance an element is along a path, but we can also define an element’s rotation with offset-rotate. The default value is auto which results in our element following the path. Check out the property’s almanac article for more values.

To animate an element along the path, we animate the offset-distance:

OK, that catches up to speed on moving elements along a path. Now we have to answer…

Can we make responsive paths?

The sticking point with CSS motion paths is the hardcoded nature. It’s not flexible. We are stuck hardcoding paths for particular dimensions and viewport sizes. A path that animates an element 600px, will animate that element 600px regardless of whether the viewport is 300px or 3440px wide.

This differs from what we are familiar with when using SVG paths. They will scale with the size of the SVG viewbox.

Try resizing this next demo below and you’ll see:

  • The SVG will scale with the viewport size as will the contained path.
  • The offset-path does not scale and the element goes off course.

This could be okay for simpler paths. But once our paths become more complicated, it will be hard to maintain. Especially if we wish to use paths we’ve created in vector drawing applications.

For example, consider the path we worked with earlier:

.element {   --path: 'M20,20 C20,100 200,0 200,100';   offset-path: path(var(--path)); }

To scale that up to a different container size, we would need to work out the path ourselves, then apply that path at different breakpoints. But even with this “simple” path, is it a case of multiplying all the path values? Will that give us the right scaling?

@media(min-width: 768px) {   .element {     --path: 'M40,40 C40,200 400,0 400,200'; // ????   } }

A more complex path such as one drawn in a vector application is going to be trickier to maintain. It will need the developer to open the application, rescale the path, export it, and integrate it with the CSS. This will need to happen for all container size variations. It’s not the worst solution, but it does require a level of maintenance that we might not want to get ourselves into.

.element {   --path: 'M40,228.75L55.729166666666664,197.29166666666666C71.45833333333333,165.83333333333334,102.91666666666667,102.91666666666667,134.375,102.91666666666667C165.83333333333334,102.91666666666667,197.29166666666666,165.83333333333334,228.75,228.75C260.2083333333333,291.6666666666667,291.6666666666667,354.5833333333333,323.125,354.5833333333333C354.5833333333333,354.5833333333333,386.0416666666667,291.6666666666667,401.7708333333333,260.2083333333333L417.5,228.75';   offset-path: path(var(--path)); } 
 @media(min-width: 768px) {   .element {     --path: 'M40,223.875L55.322916666666664,193.22916666666666C70.64583333333333,162.58333333333334,101.29166666666667,101.29166666666667,131.9375,101.29166666666667C162.58333333333334,101.29166666666667,193.22916666666666,162.58333333333334,223.875,223.875C254.52083333333334,285.1666666666667,285.1666666666667,346.4583333333333,315.8125,346.4583333333333C346.4583333333333,346.4583333333333,377.1041666666667,285.1666666666667,392.4270833333333,254.52083333333334L407.75,223.875';   } } 
 @media(min-width: 992px) {   .element {     --path: 'M40,221.625L55.135416666666664,191.35416666666666C70.27083333333333,161.08333333333334,100.54166666666667,100.54166666666667,130.8125,100.54166666666667C161.08333333333334,100.54166666666667,191.35416666666666,161.08333333333334,221.625,221.625C251.89583333333334,282.1666666666667,282.1666666666667,342.7083333333333,312.4375,342.7083333333333C342.7083333333333,342.7083333333333,372.9791666666667,282.1666666666667,388.1145833333333,251.89583333333334L403.25,221.625';   } }

It feels like a JavaScript solution makes sense here. GreenSock is my first thought because its MotionPath plugin can scale SVG paths. But what if we want to animate outside of an SVG? Could we write a function that scales the paths for us? We could but it won’t be straightforward.

Trying different approaches

What tool allows us to define a path in some way without the mental overhead? A charting library! Something like D3.js allows us to pass in a set of coordinates and receive a generated path string. We can tailor that string to our needs with different curves, sizing, etc.

With a little tinkering, we can create a function that scales a path based on a defined coordinate system:

This definitely works, but it’s also less than ideal because it’s unlikely we are going to be declaring SVG paths using sets of coordinates. What we want to do is take a path straight out of a vector drawing application, optimize it, and drop it on a page. That way, we can invoke some JavaScript function and let that do the heavy lifting.

So that’s exactly what we are going to do.

First, we need to create a path. This one was thrown together quickly in Inkscape. Other vector drawing tools are available.

A path created in Inkscape on a 300×300 canvas

Next, let’s optimize the SVG. After saving the SVG file, we’ll run it through Jake Archibald’s brilliant SVGOMG tool. That gives us something along these lines:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 79.375 79.375" height="300" width="300"><path d="M10.362 18.996s-6.046 21.453 1.47 25.329c10.158 5.238 18.033-21.308 29.039-18.23 13.125 3.672 18.325 36.55 18.325 36.55l12.031-47.544" fill="none" stroke="#000" stroke-width=".265"/></svg>

The parts we’re interested are path and viewBox.

Expanding the JavaScript solution

Now we can create a JavaScript function to handle the rest. Earlier, we created a function that takes a set of data points and converts them into a scalable SVG path. But now we want to take that a step further and take the path string and work out the data set. This way our users never have to worry about trying to convert their paths into data sets.

There is one caveat to our function: Besides the path string, we also need some bounds by which to scale the path against. These bounds are likely to be the third and fourth values of the viewBox attribute in our optimized SVG.

const path = "M10.362 18.996s-6.046 21.453 1.47 25.329c10.158 5.238 18.033-21.308 29.039-18.23 13.125 3.672 18.325 36.55 18.325 36.55l12.031-47.544"; const height = 79.375 // equivalent to viewbox y2 const width = 79.375 // equivalent to viewbox x2 
 const motionPath = new ResponsiveMotionPath({   height,   width,   path, });

We won’t go through this function line-by-line. You can check it out in the demo! But we will highlight the important steps that make this possible.

First, we’re converting a path string into a data set

The biggest part of making this possible is being able to read the path segments. This is totally possible, thanks to the SVGGeometryElement API. We start by creating an SVG element with a path and assigning the path string to its d attribute.

// To convert the path data to points, we need an SVG path element. const svgContainer = document.createElement('div'); // To create one though, a quick way is to use innerHTML svgContainer.innerHTML = `   <svg xmlns="http://www.w3.org/2000/svg">     <path d="$ {path}" stroke-width="$ {strokeWidth}"/>   </svg>`; const pathElement = svgContainer.querySelector('path');

Then we can use the SVGGeometryElement API on that path element. All we need to do is iterate over the total length of the path and return the point at each length of the path.

convertPathToData = path => {   // To convert the path data to points, we need an SVG path element.   const svgContainer = document.createElement('div');   // To create one though, a quick way is to use innerHTML   svgContainer.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg">                               <path d="$ {path}"/>                             </svg>`;   const pathElement = svgContainer.querySelector('path');   // Now to gather up the path points.   const DATA = [];   // Iterate over the total length of the path pushing the x and y into   // a data set for d3 to handle 👍   for (let p = 0; p < pathElement.getTotalLength(); p++) {     const { x, y } = pathElement.getPointAtLength(p);     DATA.push([x, y]);   }   return DATA; }

Next, we generate scaling ratios

Remember how we said we’d need some bounds likely defined by the viewBox? This is why. We need some way to calculate a ratio of the motion path against its container. This ratio will be equal to that of the path against the SVG viewBox. We will then use these with D3.js scales.

We have two functions: one to grab the largest x and y values, and another to calculate the ratios in relation to the viewBox.

getMaximums = data => {   const X_POINTS = data.map(point => point[0])   const Y_POINTS = data.map(point => point[1])   return [     Math.max(...X_POINTS), // x2     Math.max(...Y_POINTS), // y2   ] } getRatios = (maxs, width, height) => [maxs[0] / width, maxs[1] / height]

Now we need to generate the path

The last piece of the puzzle is to actually generate the path for our element. This is where D3.js actually comes into play. Don’t worry if you haven’t used it before because we’re only using a couple of functions from it. Specifically, we are going to use D3 to generate a path string with the data set we generated earlier.

To create a line with our data set, we do this:

d3.line()(data); // M10.362000465393066,18.996000289916992L10.107386589050293, etc.

The issue is that those points aren’t scaled to our container. The cool thing with D3 is that it provides the ability to create scales. These act as interpolation functions. See where this is going? We can write one set of coordinates and then have D3 recalculate the path. We can do this based on our container size using the ratios we generated.

For example, here’s the scale for our x coordinates:

const xScale = d3   .scaleLinear()   .domain([     0,     maxWidth,   ])   .range([0, width * widthRatio]);

The domain is from 0 to our highest x value. The range in most cases will go from 0 to container width multiplied by our width ratio.

There are times where our range may differ and we need to scale it. This is when the aspect ratio of our container doesn’t match that of our path. For example, consider a path in an SVG with a viewBox of 0 0 100 200. That’s an aspect ratio of 1:2. But if we then draw this in a container that has a height and width of 20vmin, the aspect ratio of the container is 1:1. We need to pad the width range to keep the path centered and maintain the aspect ratio.

What we can do in these cases is calculate an offset so that our path will still be centered in our container. 

const widthRatio = (height - width) / height const widthOffset = (ratio * containerWidth) / 2 const xScale = d3   .scaleLinear()   .domain([0, maxWidth])   .range([widthOffset, containerWidth * widthRatio - widthOffset])

Once we have two scales, we can map our data points using the scales and generate a new line.

const SCALED_POINTS = data.map(POINT => [   xScale(POINT[0]),   yScale(POINT[1]), ]); d3.line()(SCALED_POINTS); // Scaled path string that is scaled to our container

We can apply that path to our element by passing it inline via a CSS property 👍

ELEMENT.style.setProperty('--path', `"$ {newPath}"`);

Then it’s our responsibility to decide when we want to generate and apply a new scaled path. Here’s one possible solution:

const setPath = () => {   const scaledPath = responsivePath.generatePath(     CONTAINER.offsetWidth,     CONTAINER.offsetHeight   )   ELEMENT.style.setProperty('--path', `"$ {scaledPath}"`) } const SizeObserver = new ResizeObserver(setPath) SizeObserver.observe(CONTAINER)

This demo (viewed best in full screen) shows three versions of the element using a motion path. The paths are present to easier see the scaling. The first version is the unscaled SVG. The second is a scaling container illustrating how the path doesn’t scale. The third is using our JavaScript solution to scale the path.

Phew, we did it!

This was a really cool challenge and I definitely learned a bunch from it! Here’s a couple of demos using the solution.

It should work as a proof of concept and looks promising! Feel free to drop your own optimized SVG files into this demo to try them out! — it should catch most aspect ratios.

I’ve created a package named “Meanderer” on GitHub and npm. You can also pull it down with unpkg CDN to play with it in CodePen, if you want to try it out.

I look forward to seeing where this might go and hope we might see some native way of handling this in the future. 🙏

The post Create a Responsive CSS Motion Path? Sure We Can! appeared first on CSS-Tricks.


, , , ,

Get Moving (or not) with CSS Motion Path

We just linked up the idea that offset-path can be cleverly used to set type on a path. Don’t miss Michelle Barker’s experimentation either, with drawing paths or animating text along a path.

Dan Wilson has also been following this tech for quite a while and points out why the sudden surge of interest in this:

With the release of Firefox 72 on January 7, 2020, CSS Motion Path is now in Firefox, new Edge (slated for a January 15, 2020 stable release), Chrome, and Opera (and other Blink-based browsers). That means each of these browsers supports a common baseline of offset-path: path()offset-distance, and offset-rotate.

Dan’s post does a great job of covering the basics, including some things you might not think of, like the fact that the path itself can be animated.

Direct Link to ArticlePermalink

The post Get Moving (or not) with CSS Motion Path appeared first on CSS-Tricks.


, ,

Motion Paths – Past, Present and Future

Cassie Evans has a great intro to motion paths. That is, being able to animate an element along a path. Not just up/down/left/right, but whatever curvy/wiggly/weird path you want.

It’s an interesting subject because there are so many different technologies helping to do it over time. SMIL, JavaScript-powered animation libraries, native JavaScript APIs, and even CSS via offset-path and friends. I think offset-path is funny – it was changed to that name from motion-path as you don’t technically have to apply motion to an element you place on a path in this way.

There’s no clear winner. I’m (perhaps obviously) a fan of doing stuff like this in CSS whenever possible, but the browser support there is essentially Chrome-only. Plus seeing SVG path values in CSS always feels a smidge uncomfortable because of the unitless numbers. SMIL feels like essentially dead technology, but at least then you’re in SVG-land and the paths make good sense in that context. If browser support is vital, you have to use a library.

I do think there is untapped cool design possibility in motion paths. It’s not just for landing space ships, but can be for practical stuff like how a modal enters a page.

Direct Link to ArticlePermalink

The post Motion Paths – Past, Present and Future appeared first on CSS-Tricks.


, , , ,

Reduced Motion Picture Technique, Take Two

Did you see that neat technique for using the <picture> element with <source media=""> to serve an animated image (or not) based on a prefers-reduced-motion media query?

After we shared that in our newsletter, we got an interesting reply from Michael Gale:

What about folks who love their animated GIFs, but just didn’t want the UI to be zooming all over the place? Are they now forced to make a choice between content and UI?

I thought that was a pretty interesting question.

Also, whenever I see <img src="gif.gif"> these days, my brain is triggered into WELL WHAT ABOUT MP4?! territory, as I’ve been properly convinced that videos are better-in-all-ways on the web than GIFs. Turns out, some browsers support videos right within the <img> element and, believe it or not, you can write fallbacks for that, with — drumroll, please — for the <picture> element as well!

Let’s take a crack at combining all this stuff.

Adding an MP4 source

The easy one is adding an additional <source> with the video. That means we’ll need three source media files:

  1. A fallback non-animated graphic when prefers-reduced-motion is reduce.
  2. An animated GIF as the default.
  3. An MP4 video to replace the GIF, if the fallback is supported.

For example:

<picture>   <source srcset="static.png" media="(prefers-reduced-motion: reduce)"></source>   <source srcset="animated.mp4" type="video/mp4">   <img srcset="animated.gif" alt="animated image" /> </picture>

Under default conditions in Chrome, only the GIF is downloaded and shown:

Chrome DevTools showing only gif downloaded

Under default conditions in Safari, only the MP4 is downloaded and shown:

Safari DevTools showing only mp4 downloaded

If you’ve activated prefers-reduced-motion: reduce in either Chrome or Safari (on my Mac, I go to System PreferencesAccessibilityDisplayReduce Motion), both browsers only download the static PNG file.

Chrome DevTools showing png downloaded

I tested Firefox and it doesn’t seem to work, instead continuing to download the GIF version. Firefox seems to support prefers-reduced-motion, but perhaps it’s just not supported on <source> elements yet? I’m not sure what’s up there.

Wouldn’t it be kinda cool to provide a single animated source and have a tool generate the others from it? I bet you could wire that up with something like Cloudinary.

Adding a toggle to show the animated version

Like Michael Gale mentioned, it seems a pity that you’re entirely locked out from seeing the animated version just because you’ve flipped on a reduced motion toggle.

It should be easy enough to have a <button> that would use JavaScript to yank out the media query and force the browser to show the animated version.

I’m fairly sure there is no practical way to do this declaratively in HTML. We also can’t put this button within the <picture> tag. Even though <picture> isn’t a replaced element, the browser still gets confused and doesn’t like it. Instead, it doesn’t render it at all. No big deal, we can use a wrapper.

<div class="picture-wrap">      <picture>      <!-- sources  -->   </picture>    <button class="animate-button">Animate</button>  </div>

We can position the button on top of the image somewhere. This is just an arbitrary choice — you could put it wherever you want, or even have the entire image be tappable as long as you think you could explain that to users. Remember to only show the button when the same media query matches:

.picture-wrap .animate-button {   display: none; }  @media (prefers-reduced-motion: reduce) {   .picture-wrap .animate-button {      display: block;   } }

When the button is clicked (or tapped), we need to remove the media query to start the animation by downloading an animated source.

let button = document.querySelector(".animate-button");  button.addEventListener("click", () => {   const parent = button.closest(".picture-wrap");   const picture = parent.querySelector("picture");   picture.querySelector("source[media]").remove(); });

Here’s that in action:

See the Pen
Prefers Reduction Motion Technique PLUS!
by Chris Coyier (@chriscoyier)
on CodePen.

Maybe this is a good component?

We could automatically include the button, the button styling, and the button functionality with a web component. Hey, why not?

See the Pen
Prefers Reduction Motion Technique as Web Component
by Chris Coyier (@chriscoyier)
on CodePen.

The post Reduced Motion Picture Technique, Take Two appeared first on CSS-Tricks.


, , , ,

Reducing motion with the picture element

Here’s a bonafide CSS/HTML trick from Brad Frost and Dave Rupert where they use the <picture> element to switch out a GIF file with an image if the user has reduced motion enabled. This is how Brad goes about implementing that:

<picture>   <!-- This image will be loaded if the media query is true  -->   <source srcset="no-motion.jpg" media="(prefers-reduced-motion: reduce)"></source>    <!--  Otherwise, load this gif -->   <img srcset="animated.gif alt="brick wall"/> </picture>

How nifty is this? It makes me wonder if there are other ways this image-switching technique can be used besides accessibility and responsive images…

Also it’s worth noting that Eric Bailey wrote about the reduced motion media query a while back where he digs into its history and various approaches to use it.

Direct Link to ArticlePermalink

The post Reducing motion with the picture element appeared first on CSS-Tricks.


, , ,

Revisiting prefers-reduced-motion, the reduced motion media query

Two years ago, I wrote about prefers-reduced-motion, a media query introduced into Safari 10.1 to help people with vestibular and seizure disorders use the web. The article provided some background about the media query, why it was needed, and how to work with it to avoid creating disability-triggering visual effects.

The article was informed by other people’s excellent work, namely Orde Saunders’ post about user queries, and Val Head’s article on web animation motion sensitivity.

We’re now four months into 2019, and it makes me happy to report that we have support for the feature in all major desktop browsers! Safari was first, with Firefox being a close second. Chrome was a little late to the party, but introduced it as of version 74.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.


Chrome Opera Firefox IE Edge Safari
74 No 63 No No 10.1

Mobile / Tablet

iOS Safari Opera Mobile Opera Mini Android Android Chrome Android Firefox
10.3 No No No No No

While Microsoft Edge does not have support for prefers-reduced-motion, it will become Chrome under the hood soon. If there’s one good thing to come from this situation, it’s that Edge’s other excellent accessibility features will (hopefully) have a good chance of being back-ported into Chrome.


While I’m happy to see some websites and web apps using the media query, I find that it’s rare to encounter it outside of places maintained by people who are active in CSS and accessibility spaces. In a way, this makes sense. While prefers-reduced-motion is relatively new, CSS features and functionality as a whole are often overlooked and undervalued. Accessibility even more so.

It’s tough to blame someone for not using a feature they don’t know exists, especially if it’s relatively new, and especially in an industry as fast-paced as ours. The deck is also stacked in terms of what the industry prioritizes as marketable, and therefore what developers pay attention to. And yet, prefers-reduced-motion is a library-agnostic feature that ties into Operating System-level functionality. I’m pretty sure that means it’ll have some significant staying power in terms of reward for time spent for skill acquisition.

Speaking of rewards, I think it’s also worth pointing out the true value prefers-reduced-motion represents: Not attracting buzzword-hungry recruiters on LinkedIn, but improving the quality of life for the people who benefit from the effect it creates. Using this media query could spare someone from having to unnecessarily endure a tremendous amount of pain for simply having the curiosity to click on a link or scroll down a page.

The people affected

When it comes to disability, many people just assume “blind people.” The reality is that disabilities are a complicated and nuanced topic, one that is surprisingly pervasive, deeply personal, and full of unfortunate misconceptions. It’s also highly variable. Different people are affected by different disability conditions in different ways — extending to a wide gamut of permanent, temporary, environmental, and situational concerns. Multiple, compounding conditions can (and do) affect individuals, and sometimes what helps one person might hinder another. It’s a difficult, but very vital thing to keep in mind.

If you have a vestibular disorder or have certain kinds of migraine or seizure triggers, navigating the web can be a lot like walking through a minefield — you’re perpetually one click away from activating an unannounced animation. And that’s just for casual browsing.

If you use the web for work, you might have no choice but to endure a web app that contains triggering animations multiple times a week, or even per day or hour. In addition to not having the autonomy to modify your work device, you may also not have the option to quickly and easily change jobs — a privilege easily forgotten when you’re a specialized knowledge worker.

It’s a fallacy to assume that a person is aware of their vestibular disorder, or what triggers it. In fact, sometimes the initial triggering experience exacerbates your sensitivity and makes other parts of a design difficult to use. Facundo Corradini shares his experience with this phenomenon in his article, “Accessibility for Vestibular Disorders: How My Temporary Disability Changed My Perspective.”

Not all assistive technology users are power users, so it’s another fallacy to assume that a person with a vestibular disorder is aware of, or has the access rights to enable a motion-reducing Operating System setting or install a browser extension.

Think of someone working in a large corporation who has to use a provisioned computer with locked-down capabilities. Or someone who isn’t fully aware of what of their tablet is capable of doing past browsing social media, watching video, and messaging their family and friends. Or a cheap and/or unorthodox device that will never support prefers-reduced-motion feature — some people purchase discontinued devices such as the Windows Phone specifically because their deprecation makes them affordable.

Do these people deserve to be hurt because of their circumstances? Of course not.

Considering what’s harmful

You can tie harm into value, the same way you can with delight. Animation intended to nudge a person towards a signup could also drive them away. This kind of exit metric is more difficult to quantify, but it definitely happens. Sometimes the harm is even intentional, and therefore an easier datapoint to capture — what you do with that information is a whole other issue.

If enough harm happens to enough people, it affects that certain something we know as branding. This effect doesn’t even need to be tied to a disability condition. Too much animation, applied to the wrong things in the wrong way will drive people away, even if they can’t precisely articulate why.

You also don’t know who might be on the receiving end, or what circumstances they’re experiencing the moment they load your website or web app. We can’t — and shouldn’t — know this kind of information, either. It could be a prospective customer, the employee at a venture capitalist firm tasked with evaluating your startup, or maybe even your new boss.

We also don’t need to qualify their relationship to us to determine if their situation is worth considering — isn’t it enough to just be proactively kind?

Animation is progressive enhancement

We also need to acknowledge that not every device that can access the web can also render animation, or render animation smoothly. When animation is used on a low-power or low quality device that “technically” supports it, the overall user experience suffers. Some people even deliberately seek this experience out as a feature.

Devices may also be set to specialized browsing modes to allow people to access your content in alternate ways. This concept is known as being robust, and is one of the four high-level principles that govern the guidelines outlining how to craft accessible experiences.

Animation might not always look the way you intend it in these modes. One example would be when the viewport is zoomed and the animation isn’t built using relative units. There’s a non-trivial chance important parts might be pushed out of the viewport, leaving the animation appearing as a random collection of flickering bits. Another example of a specialized browsing mode might be Reader Mode, where the animation may not appear at all.

Taking it to code

Considering all this, I’m wondering if there are opportunities to help web professionals become more aware of, and therefore more considerate of the downsides of poorly conceived and implemented animation.

Maybe we proactively incorporate a media query high up in the cascade to disable all animation for those who desire it, and for those who have devices that can’t support it. This can be accomplished by targeting anything where someone has expressed a desire for a low-to-no-animation experience, or any device that has a slow screen refresh rate.

The first part of the query, targeting low-to-no-animation, is done via prefers-reduced-motion. The second, targeting a screen with a low refresh rate, uses update. update is a new media feature that allows us to “query the ability of the output device to modify the appearance of content once it has been rendered.”

@media screen and   (prefers-reduced-motion: reduce),    (update: slow) {   * {     animation-duration: 0.001ms !important;     transition-duration: 0.001ms !important;   } }

This code forces all animation that utilizes a declaration of animation-duration or transition-duration to conclude at a rate that is imperceptible to the human eye. It will work when a person has requested a reduced motion experience, or the device has a screen with a slow refresh rate, say e-ink or a cheap smartphone.

Retaining the animation and transition duration also ensures that any functionality that is tied to CSS-based animation will activate successfully (unlike using a declaration of animation: none), while still preventing a disability condition trigger or creating rendering lag.

This declaration is authored with the intent of introducing some intentional friction into our reset styles. Granted, it’s not a perfect solution, but it does drive at a few things:

  1. Increasing the chances of developers becoming aware of the two media features, by way of making them present in the cascade of every inspected element.
  2. Providing a moment to consider why and how animation will be introduced into a website or web app, and what the experience should be like for those who can’t or don’t want to experience it.
  3. Encouraging developers who are less familiar with CSS to think of the cascade in terms of components and nudge them towards making more easily maintainable stylesheets.

Animation isn’t unnecessary

In addition to vestibular disorders and photosensitive conditions, there’s another important aspect of accessibility we must consider: cognitive disabilities.

Cognitive disabilities

As a concern, the category is wide and often difficult to quantify, but no less important than any other accessibility discipline. It is also far more prevalent. To expand on this some, the World Health Organization reports an estimated 300 million people worldwide are affected by depression, a temporary or permanent, environmental and/or biological condition that can significantly impair your ability to interact with your environment. This includes interfering with your ability to understand the world around you.

Animation can be a great tool to help combat some forms of cognitive disability by using it to break down complicated concepts, or communicate the relationship between seemingly disparate objects. Val Head’s article on A List Apart highlights some other very well-researched benefits, including helping to increase problem-solving ability, recall, and skill acquisition, as well as reducing cognitive load and your susceptibility to change blindness.

Reduce isn’t necessarily remove

We may not need to throw the baby out with the bathwater when it comes to using animation. Remember, it’s prefers-reduced-motion, not prefers-no-motion.

If we embrace the cascade, we can work with the animation reset code described earlier on a per-component basis. If the meaning of a component is diminished by removing its animation altogether, we could slow down and simplify the component’s animation to the point where the concept can be communicated without potentially being an accessibility trigger.

If you’re feeling clever, you might even be able to use CSS Custom Properties to help achieve this in an efficient way. If you’re feeling extra clever, you could also use these Custom Properties for a site-wide animation preferences widget.

In the following code sample, we’re defining default properties for our animation and transition durations, then modifying them based on the context they’re declared in:

/* Set default durations */ :root {   --animation-duration: 250ms;    --transition-duration: 250ms;  }  /* Contextually shorten duration length */ @media screen and (prefers-reduced-motion: reduce), (update: slow) {   :root {     --animation-duration: 0.001ms !important;      --transition-duration: 0.001ms !important;   } }  @media screen and (prefers-reduced-motion: reduce), (update: slow) {   /* Remove duration for all unknown animation when a user requests a reduced animation experience */   * {     animation-duration: var(--animation-duration);     transition-duration: var(--animation-duration);   } }  /* Update the duration when animation is critical to understanding and the device can support it */ @media screen and (prefers-reduced-motion: reduce), (update: fast) {   .c-educational-concept {     /* Set a new animation duration scoped to this component */     --animation-duration: 6000ms !important;      ...     animation-name: educational-concept;     /* Use the scoped animation duration */     animation-duration: var(--animation-duration);    } }

However, trying to test the effectiveness of this slowed-down animation puts us in a bit of a pickle: there’s no real magic number we can write a test against.

We need to have a wide representation of people who are susceptible to animation-based disability triggers to sign off on it being safe, which unfortunately involves subjecting them to something that may potentially not be. That’s a huge ask.

A better approach is to ask about what kinds of animation have been triggers for them in the past, then see if what they describe matches what we’ve made. This approach also puts the onus on yourself, and not the person with a disability, to do the work to provide accommodation.

If you’re having trouble finding people, ask your friends, family, and coworkers — I’m sure there’s more people out there than you think. And if you need a good starting point for creating safer animation, I once again urge you to read Val’s article on A List Apart.


There’s a lot to unpack here, and I’m not the most qualified person to talk about it. Here’s what my friend Shell Little, an Accessibility Specialist at Wells Fargo DS4B, has to say about it:

Web animation as it relates to Neurodivergence (ND) can be a fantastic tool to guide users to solidify meaning and push understanding. The big issue is the same animation that can assist one group of ND users can create a barrier for another. As mentioned by Eric, Neurodivergence is a massive group of people with a vast range of abilities and covers a wide variety of cognitive disabilities including but not limited to ADHD, autism, dyslexia, epilepsy, dyscalculia, obsessive-compulsive disorder, dyspraxia, and Tourette syndrome.

When speaking about motion on the web it’s important we think specifically about attention-related disabilities, autism, and sensory processing disorders that are also closely linked to both. These groups of people, who coincidentally includes me, are especially sensitive to motion as it relates to understanding information and interacting with the web as a whole. Animations can easily overwhelm, distract, and frustrate users who are sensitive to motion and from personal experience, it can even do all three at once.

Because so many people are affected by motion and animation on the web the W3C’s WCAG have a criterion named Pause, Stop, Hide that is specifically written to guide content creators on how to best create accessible animations. My main issues with this guideline are, it only applies to animations that last longer than 5 seconds and motion that is deemed essential is exempt from the standard. That means a ton of animations that can create barriers such as distraction, dizziness, and even harm are out there in the wild.

It makes sense, as Eric mentioned, that we can’t get rid of all animation. Techniques such as spinners let users know the page is still working on the task it was given, and micro-interactions help show progression. But depending on someone’s brain, the things that are helpful at lunch can be a barrier later that night. Someone’s preferences and needs shift throughout their day, and that’s the beauty of prefers-reduced-motion. It has the potential to be what fills the gaps left by Pause, Stop, Hide and allow users to decide when they do or do not want to have motion. That right there is priceless to someone like me.

As someone with an attention-related disability, an interaction I have found to be exceedingly frustrating is autoplay. Many media sharing sites have auto-playing content such as videos, gifs, and ads but because they can be paused, they pass the WCAG standard. That doesn’t mean they aren’t a huge barrier for me as I can’t read any text around them when they are playing. This causes me to have to pause every single moving item I run into. This not only significantly slows me down, and eats away at my limited spoons, but it also derails my task flow and train of thought. Now, it is true some sites — such as Twitter and LinkedIn — have settings to turn autoplay off, but this isn’t true for all sites. This would be a perfect place for prefers-reduced-motion.

In a world where I would be able to determine when and if I want videos to start playing at me, I would be able to get more done with less cognitive strain. prefers-reduced-motion is freedom for me and the millions of people whose brains work like mine. In sum, the absolute best thing we can do for our users who are sensitive to motion is to put a system in place that empowers them to decide when and where animation should be displayed to them. Let the user decide because they will always know their access needs better than we do.

Thanks, Shell!

I don’t hate fun, I just don’t want to hurt people

On my own time, I’m fortunate enough to be able to enjoy animation. I appreciate the large amounts of time and attention involved with making something come alive on the screen, and I’ve definitely put my fair share of time ooh-ing and aah-ing over other people’s amazing work in CodePen. I’ve also watched enough DC Animated Universe to be able to instantly recognize Kevin Conroy’s voice — if you’re looking for even deeper nerd cred, Masaaki Yuasa is a seriously underrated animator.

However, I try to not overly rely on animation as a web professional. There’s a number of factors as to why:

  1. First is simply pushing on awareness of the concerns outlined earlier, as many are unaware they exist. Animation has such a crowd-pleasing gee-whiz factor to it that it’s often quickly accepted into a product without a second thought.
  2. Second is mitigating risk. Not adhering to the Web Content Accessibility Guidelines (WCAG) — including provisions for animation — means your inaccessible website or web app becomes a legal liability. There is now legal precedent for the websites and web apps of private companies being sued, so it’s a powerful metric to weigh your choices against.
  3. Third is user experience. With that gee-whiz factor, people tend to forget that being forced to repeatedly view that super-slick animation over and over again will eventually become a tedious chore. There’s a reason why we no longer make 90s-style loading screens (content warning: high-contrast strobing and flickering, Flash, mimes). If you need a more contemporary example, consider why Netflix lets us skip TV show intros.
  4. Fourth is understanding the lay of the land. While prefers-reduced-motion is getting more support, the majority of it is on desktop browsers, and not mobile. We’re not exactly a desktop-first world anymore, especially if you’re in an underserved community or emerging market. A mobile form factor also may exacerbate vestibular issues. Moving around while using your device means you may lose a fixed reference point, unlike sitting at a desk and staring at a monitor — this kind of trigger is similar to why some of us can get seasick.
  5. The fifth factor is a bit of a subset of the fourth. Animation eats device data and battery, and it’s important to remember that it’s the world wide web, not the wealthy Western web. The person using your service may not have consistent and reliable access to income or power, so you want to get to know your audience before spending their money for them.

The ask

Not everyone who could benefit from prefers-reduced-motion cares about accessibility-related content, so I’d love to see the media query start showing up in the code of more popular sites. The only real way to do this is to spread awareness. Not only of the media query, but more importantly, understanding the nuance involved with using animation responsibly.

CSS-Tricks is a popular website for the frontend industry, and I’m going to take advantage of that. If you feel comfortable sharing, what I would love is to describe what kinds of animation have been problematic for you, in either the comments or on Twitter.

The idea here is we can help build a reference of what kinds of things to be on the lookout for animation-wise. Hopefully, with time and a little luck, we can all help make the web better for everyone.

Thanks to Scott O’Hara, Zach Leatherman, Shell Little, and Geoff Graham for reviewing this article.

The post Revisiting prefers-reduced-motion, the reduced motion media query appeared first on CSS-Tricks.


, , , , ,