Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

What's best for prefers-reduced-motion? #11

Open
jensimmons opened this issue Feb 4, 2019 · 22 comments · Fixed by #51
Open

What's best for prefers-reduced-motion? #11

jensimmons opened this issue Feb 4, 2019 · 22 comments · Fixed by #51

Comments

@jensimmons
Copy link
Owner

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 jensimmons added philosophical Further information is requested to do labels Feb 4, 2019
@olach
Copy link

olach commented Feb 4, 2019

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

@jensimmons
Copy link
Owner Author

@jensimmons
Copy link
Owner Author

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:
w3c/csswg-drafts#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.)

@hiikezoe
Copy link

hiikezoe commented Feb 9, 2019

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

@fantasai
Copy link
Contributor

fantasai commented Feb 9, 2019

No, CSS does not have control over autoplay.

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

@tigt
Copy link

tigt commented Feb 11, 2019

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.

@rjgotten
Copy link

rjgotten commented Feb 11, 2019

@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...

@valhead
Copy link

valhead commented Feb 11, 2019

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.

@jensimmons
Copy link
Owner Author

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 added a commit that referenced this issue Feb 11, 2019
@tigt
Copy link

tigt commented Feb 12, 2019

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.)

@jensimmons jensimmons added accessibility and removed philosophical Further information is requested to do labels Feb 12, 2019
@jensimmons
Copy link
Owner Author

@valhead What do you think about pseudo-elements?

@valhead
Copy link

valhead commented Feb 12, 2019

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;

@scottkellum
Copy link

scottkellum commented Feb 12, 2019

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
Copy link

scottkellum commented Jul 11, 2019

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

@Malvoz
Copy link
Contributor

Malvoz commented Jul 11, 2019

In addition to animations and transitions, I too, think the impact of scroll-behavior: smooth as suggested by #11 (comment) 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
Copy link
Contributor

Malvoz commented Sep 10, 2019

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.

@mirisuzanne
Copy link
Collaborator

@Malvoz good catch, see PR #55

@Malvoz
Copy link
Contributor

Malvoz commented Nov 23, 2019

*, ::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 #11 (comment)).

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

@rjgotten
Copy link

@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.

tomayac added a commit to GoogleChrome/web.dev that referenced this issue Nov 25, 2019
Following the best practices for `prefers-reduced-motion` that were the outcome of a discussion in jensimmons/cssremedy#11, via #1849 (comment). Thanks, @Malvoz.
@Malvoz
Copy link
Contributor

Malvoz commented Dec 3, 2019

@rjgotten I agree.

@mirisuzanne do you mind checking out #11 (comment), and #11 (comment)? Thanks!

@mirisuzanne
Copy link
Collaborator

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

@ZachSaucier
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.