cssremedy icon indicating copy to clipboard operation
cssremedy copied to clipboard

What's best for `prefers-reduced-motion`?

Open jensimmons opened this issue 5 years ago • 22 comments

I wrote

/* Stop any animation if the user has set their device to "prefers reduced motion". */
@​media (prefers-reduced-motion: reduce) {
  * {
    animation: none !important;
    transition: none !important;
    animation-duration: 0.1s !important;
    transition-duration: 0.1s !important;
  }
}

Or I should say, I snagged that code from discussions on Twitter. Which I'd linked to, but then erased the link. Hm, I should find it again.

@meyerweb raised a good point — why 0.1s and not 0?

Also, do browsers not do this already? Is there no mandate in the CSS spec for user agents to enforce this? Why not? Or if there is (and browsers just haven't implemented it yet), then what is it? What was the discussion about this?

Likely there's some back story with wisdom we can draw from.

jensimmons avatar Feb 04 '19 19:02 jensimmons

Here is another variant I have encountered (not tested):

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0s !important;
    transition: none !important;
    scroll-behavior: auto !important;
  }
}

From: https://github.com/jonikorpi/base-files/blob/e584daf4772d29ff63ddf6d60c49f7652729c422/reset.css#L133-L139

olach avatar Feb 04 '19 23:02 olach

I found the tweets I snagged this code from... by @scottjehl

https://twitter.com/scottjehl/status/1086287082031583232

https://twitter.com/scottjehl/status/1085600505299111936

jensimmons avatar Feb 05 '19 00:02 jensimmons

The Media Queries 5 spec defines prefers-reduced-motion here: https://drafts.csswg.org/mediaqueries-5/#descdef-media-prefers-reduced-motion

The issue where this CSS feature was created is here: https://github.com/w3c/csswg-drafts/issues/442

There's a bit of discussion about enforcing this with the browser itself, but that idea was set aside:

The core reason this needs to expose a user preference rather than change something about the view is because UI varies greatly. There's no way a UA can reliably reduce or stop motion on behalf of the user and still guarantee an understandable interface. Exposing the pref gives authors a way to do this appropriately within the context of their own interfaces.

Reading the debate in the CSSWG issue is a good way to think about the use cases and options.

I'm left thinking we should include this in Remedy, not because the CSSWG "got it wrong", but because this is a good default for all of the web developers (Authors) who don't bother thinking about prefers-reduced-motion at all. It gives people who need animations eliminated or reduced what they need. And while this might potentially 'break' some UX, developers can easily fix those less-frequent cases by overriding Remedy's code.

Now if we could only make this apply to all the auto-play video/gif/animated advertisements. (UA's should enforce this by turning all gifs and videos into NOT autoplaying. Can we do that in the CSSWG @frivoal @fantasai ?)

Oh, which still leaves the question of how.... (I just also want to consider if, for a bit, too / think about why the CSSWG didn't put the burden of this on the browser to enforce.)

jensimmons avatar Feb 05 '19 01:02 jensimmons

How about this? transition-timing-function: steps(1, start) !important; animation-timing-function: steps(1, start) !important;

hiikezoe avatar Feb 09 '19 01:02 hiikezoe

No, CSS does not have control over autoplay.

Setting both transition/animation to none and transition/animation-duration to 0 seems redundant?

fantasai avatar Feb 09 '19 02:02 fantasai

Setting transition/animation: none might break animations using the FLIP model, so *-duration: 0s would be more robust in light of that.

That said, an at-the-time WebKit engineer cautioned me against this sort of CSS when I mentioned it on Twitter 2 years ago:

Both of those are likely overkill, and depending on the context, could cause usability problems.

So this might be a thornier issue than it first seems.

tigt avatar Feb 11 '19 00:02 tigt

@tigt It won't just break animations using the FLIP model. It would break anything using a transitionEnd or animationEnd event.

For instance: to detect from the JavaScript side when an element has changed state of one of its properties; or to detect that it has just switched from invisible to visible/rendered state.

The second in particular is a necessity for a few complicated solutions. Including some strategies used to polyfill or provide fallback solutions for the ResizeObserver API. You don't want to mess with this by specifying !important styles and start off a specificity war...

rjgotten avatar Feb 11 '19 11:02 rjgotten

This is definitely a more complex problem than it initially seems as has been stated above. In my research so far I haven't found a blanket solution that isn't without issues. But trying for a "least risky" option to include in this reset could provide a decent starting point. (And help spread knowledge that such a media query exists.)

Here’s a CodePen example where I applied some of the common suggestions to see the results they have on some simple CSS animations and transitions.

Most have been mentioned in this thread already, but here's a summary based on my research so far that might be helpful for this decision:

The almost 0s duration:

 animation-duration: 0.001s !important;
 transition-duration: 0.001s !important;
  • It seems that some 0s duration animations and transitions don't play at all in Safari based on my tests so far, but 0.001s is very close to 0s and still too fast to be perceivable in most cases. (It does result in "blips" of motion in other browsers in some cases though.)
  • Animations are (almost) instantly advanced to their end state/keyframe.
  • Some keyframe animations without an animation-fill-mode of forwards or both may never be visible. (An animation-fill-mode:forwards !important could be added, but would also have its own potential issues.)
  • Any animations or transitions that end with an opacity of 0, or positioned out of view, would never be visible .
  • It assumes that the end of the animation contains the most significant meaning of the animated content and that it doesn't interfere with other content.

Based on this test, animation events still fire, but the timing of when they fire may still be an issue for some uses.

This nearly 0s duration approach might be an acceptable way to go for this project. It’s not without issues or risks, but if it can be assume that this base stylesheet is being implemented at the beginning of a project, the author could be encouraged to compose all CSS-based animations with this reduced rule in mind from the start. (Admittedly, that is still a big assumption.)

none:

animation: none !important;
transition: none !important;
  • In my opinion, the option most likely to cause usability problems when applied to other people’s code
  • Removing all animations and transitions potentially results in some content never becoming visible, and/or some content never disappearing and obscuring other content (plus the issues already mentioned above)

Very short duration:

   animation-duration: 0.1s !important;
   transition-duration: 0.1s !important;
  • Animations play at an accelerated speed which might be more harmful/distracting and less desirable overall.

Steps:

transition-timing-function: steps(1, start) !important;
animation-timing-function: steps(1, start) !important;
  • Similar results as 0.001s duration on simple animations
  • Has less desirable effects on more complex uses of keyframes ( See this example )

Two other points to consider:

Whatever this rule ends up being, it will be favoring stopping all animation by default which can be beyond what is needed in many situations. Both the WebKit blog post on prefers-reduced-motion and the WCAG point out that not all kinds of animation are potentially triggering, and define criteria for animations that should be reduced when requested.

prefers-reduced-motion can be respected for JS animation as well, but that would be separate effort. The combination of reduced CSS-based motion and non-reduced JavaScript motion could result in usability problems as well.

valhead avatar Feb 11 '19 22:02 valhead

Thank you Val.

So, to be clear,

 animation-duration: 0s !important;
 transition-duration: 0s !important;

is likely a bad solution, because in certain browsers, the state change will never happen. So, for example, if someone has applied a fade to a hover color, instead of immediately getting the hover color (rather than a fade), with a 0 duration, the user might get no hover color — things just staying without a color change at all.

So that's off the table. It's good to know why.

Seems to me like we should do this:

@​media (prefers-reduced-motion: reduce) {
  * {
      animation-duration: 0.001s !important;
      transition-duration: 0.001s !important;
  }
}

and nothing else is required. (Unlike the recent code in the project that was more complex.

There are many, many situation where that won't work, but it's also likely that 90% of websites only use animations at this point for things like hover color transitions, and other slight details. Hopefully anyone building more complex animations will know to think through what happens when prefers-reduced-motion: reduce is applied.

In fact, the alarm that people might have from us doing this might generate enough buzz and conversation that the message will get around. Pay attention to prefers-reduced-motion: reduce. Don't just allow the default from Remedy or other similar projects.

The other choice is to not do this at all. To leave it all up to the developers. I just don't like that. I'd rather risk breaking fancy animations than risk no websites bothering to write good code for people who can't take motion very well.

(And I say this as one of those people — motion on websites makes me nauseous. Really any looping motion, like repeating animated gifs in conference presentations. Ugh. Sadly, this setting will not help with the worst problems — ads with motion. Reader Mode is the only solution to that, atm.)

Sadly using !important will make it hard to override this default. If anyone has ideas about that, let's discuss. If I were a developer planning to do a lot of animation, it's likely I'd fork Remedy, and remove this code in order to not be stuck trying to override an !important statement. Not everyone will have the ability to do that.

I'm going to change the code. Let's keep talking about it.

jensimmons avatar Feb 11 '19 23:02 jensimmons

Should the code also apply to ::before, ::after, and other pseudo-elements? (Though, I think ::first-line and friends only accepting certain typography-based styles means they don’t allow transition and animation set directly on them.)

tigt avatar Feb 12 '19 01:02 tigt

@valhead What do you think about pseudo-elements?

jensimmons avatar Feb 12 '19 17:02 jensimmons

Two additional note on the 0.001s duration idea:

  • I didn't take infinitely repeating keyframe animations into account on this. A duration this short would have them in a sort of freak-out mode that would likely be pretty terrible. Since 0s causes trouble for keyframe animations in Safari, adding animation-iteration-count: 1 !important would prevent that repeating freak-out state from happening.
  • If decimal places are an issue 0.01s would serve the same purpose and is shorter.

Applying the rule to ::before and ::after seems smart. Those are often used in animations.

Related to your button hover example, @jensimmons : It looks like a 0s duration on transitions still functions in Safari (test here), so the 0.01s would only be needed for CSS keyframe animations written with implied styles for the 0% (and possibly other) keyframes.

Adjusting for not allowing infinitely repeating animations and the fact that only keyframe animations appear to fail with the 0s duration in Safari:

 animation-duration: 0.01s !important;
 animation-iteration-count: 1 !important;
 transition-duration: 0s !important;

valhead avatar Feb 12 '19 18:02 valhead

For animation, what if the duration was set to a really large value instead of a really small value to effectively pause it? On that note, animation-play-state: paused will do this.

@​media (prefers-reduced-motion: reduce) {
  * {
      animation-play-state: paused !important;
      transition: none !important;
  }
}

animation: none will break is related to a project I’m working on a project now. It leverages CSS animations for spacial, not time, based events. A paused state would be ideal to not break animations used to display things that aren’t time based. A similar technique could be used to blend two CSS variables like tint a primary color (pause an animation between var(--primary) and white).

This still may run into the case of animations that start by hiding content through opacity: 0 or off screen. Therefore pausing the content in an unavailable state.

scottkellum avatar Feb 12 '19 19:02 scottkellum

Thoughts on this? I’m liking @valhead’s direction but modified with a delay to fast forward the animation.

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
      animation-delay: -1s !important;
      animation-duration: 1s !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.01s !important;
  }
}

example here: https://codepen.io/scottkellum/pen/YogBVM?editors=1100

scottkellum avatar Jul 11 '19 21:07 scottkellum

In addition to animations and transitions, I too, think the impact of scroll-behavior: smooth as suggested by https://github.com/mozdevs/cssremedy/issues/11#issuecomment-460455224 should be strongly considered.

Also, here's an opinion on parallax motion effect due to background-attachment: fixed from https://alistapart.com/article/accessibility-for-vestibular/:

there are no words to describe just how bad a simple parallax effect, scrolljacking, or even background-attachment: fixed would make me feel. I would rather jump on one of those 20-G centrifuges astronauts use than look at a website with parallax scrolling.

So perhaps:

*,
::before,
::after {
  background-attachment: initial !important;
}

Malvoz avatar Jul 11 '19 22:07 Malvoz

Was there an explicit decision made not to include scroll-behavior: auto !important;? Disabling potential scroll-behavior: smooth occurrences could be just as important (if not more so, particularly on long pages) than perhaps other types of motion through animation/transition/background-attachment.

Malvoz avatar Sep 10 '19 19:09 Malvoz

@Malvoz good catch, see PR #55

mirisuzanne avatar Sep 10 '19 22:09 mirisuzanne

*, ::before, ::after {
  animation-delay: -1s !important;
  animation-duration: 1s !important;
  ...
}

As I understand it, the negative animation-delay is used to break even with the animation-duration of 1s, however there's a bug in Safari where negative animation-delay is treated as 0s, resulting in (I assume) an effective animation-duration of 1s and not 0s - just an FYI. If we have to choose which bug to avoid, I guess it's still better to have a potential 1s animation-delay than setting animation-delay (and animation-duration) to 0s which would break stuff using transitionEnd or animationEnd events in Safari (as suggested in https://github.com/mozdevs/cssremedy/issues/11#issuecomment-462527813).

Also I noticed there's no transition-delay: 0s !important, I don't think that's intentional... is it?

Malvoz avatar Nov 23 '19 15:11 Malvoz

@Malvoz

If you have to choose the lesser of two evils; you can make a significantly less evil, lesser evil:

*, ::before, ::after {
  animation-delay: -1ms !important;
  animation-duration: 1ms !important;
  ...
}

That should still produce a net 0s duration except for bugged Safari, where a 1ms duration is still far more acceptable than a 1s duration.

rjgotten avatar Nov 25 '19 00:11 rjgotten

@rjgotten I agree.

@mirisuzanne do you mind checking out https://github.com/mozdevs/cssremedy/issues/11#issuecomment-557808192, and https://github.com/mozdevs/cssremedy/issues/11#issuecomment-557944270? Thanks!

Malvoz avatar Dec 03 '19 21:12 Malvoz

@Malvoz That all makes sense to me. Do you want to open a PR?

mirisuzanne avatar Dec 03 '19 21:12 mirisuzanne

This has already been included in the PR above, but for those looking at this thread, make sure to also zero out the transition delay.

ZachSaucier avatar Aug 10 '21 16:08 ZachSaucier