Disable animations when user prefers reduced motion

When browser preference for reduced motion is enabled:
* Disables bolding of table of contents
* Disables sticky header transition

Bug: T254399
Change-Id: I8ef9e59b258fed977ce370da352b1924832d842b
This commit is contained in:
Jon Robson 2022-04-19 14:56:29 -07:00 committed by Jdlrobson
parent 7d75bb37ad
commit c269419af3
5 changed files with 42 additions and 9 deletions

View file

@ -9,7 +9,7 @@
},
{
"resourceModule": "skins.vector.legacy.js",
"maxSize": "2 kB"
"maxSize": "2.2 kB"
},
{
"resourceModule": "skins.vector.search",

View file

@ -53,6 +53,15 @@ module.exports = function tableOfContents( props ) {
};
}
/**
* Does the user prefer reduced motion?
*
* @return {boolean}
*/
const prefersReducedMotion = () => {
return window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches;
};
/**
* Sets an `ACTIVE_SECTION_CLASS` on the element with an id that matches `id`.
* If the element is not a top level heading (e.g. element with the
@ -78,7 +87,12 @@ module.exports = function tableOfContents( props ) {
const topSection = /** @type {HTMLElement} */ ( selectedTocSection.closest( `.${PARENT_SECTION_CLASS}` ) );
if ( selectedTocSection === topSection ) {
// The bolding of sections is arguably not "motion", however does provide a distraction to readers
// who are scrolling by visibly changing the table of contents. This can be removed if someone has
// a strong argument for why this should not be considered motion.
if ( prefersReducedMotion() ) {
return;
} else if ( selectedTocSection === topSection ) {
activeTopSection = topSection;
activeTopSection.classList.add( ACTIVE_SECTION_CLASS );
} else {
@ -144,8 +158,7 @@ module.exports = function tableOfContents( props ) {
Math.min( containerRect.bottom, window.innerHeight );
// Respect 'prefers-reduced-motion' user preference
const mediaQuery = window.matchMedia( '(prefers-reduced-motion: reduce)' );
const scrollBehavior = ( !mediaQuery || !mediaQuery.matches ) ? 'smooth' : undefined;
const scrollBehavior = prefersReducedMotion() ? 'smooth' : undefined;
// Manually increment and decrement TOC scroll rather than using scrollToView
// in order to account for threshold

View file

@ -23,11 +23,6 @@
justify-content: space-between;
box-sizing: border-box;
// If the user has expressed their preference for reduced motion, then disable animation for the sticky header.
@media ( prefers-reduced-motion: reduce ) {
transition: none;
}
@media ( min-width: @width-breakpoint-desktop ) {
padding: 6px 25px;
}

View file

@ -29,3 +29,23 @@
@media print {
@import './layouts/print.less';
}
/**
* Respect users who prefer reduced motion.
* This code can be removed if and when it is upstreamed to ResourceLoaderSkinModule
* (see T254399).
*/
/* stylelint-disable declaration-no-important, time-min-milliseconds */
@media ( prefers-reduced-motion: reduce ) {
*,
:before,
:after {
animation-delay: -1ms !important; /* 1 */
animation-duration: 1ms !important; /* 1 */
animation-iteration-count: 1 !important; /* 1 */
background-attachment: initial !important; /* 2 */
scroll-behavior: auto !important; /* 3 */
transition-delay: 0s !important; /* 4 */
transition-duration: 0s !important; /* 4 */
}
}

View file

@ -92,6 +92,11 @@ function mount( templateProps = {} ) {
}
describe( 'Table of contents', () => {
beforeEach( () => {
// @ts-ignore
global.window.matchMedia = jest.fn( () => ( {} ) );
} );
describe( 'renders', () => {
test( 'when `vector-is-collapse-sections-enabled` is false', () => {
const toc = mount();