Модернизация с Web-платформой: Уменьшение движения
Это вторая часть серии статей о силе современной Web-платформы. Каждая статья написана на основе опыта проекта, построенного в 2018 году, а затем обновлённого в 2024 году. В этой статье речь пойдёт об уменьшении движения для обеспечения доступности.
Навигация как в приложении
Изначально на сайте использовался smoothState, ныне не существующий плагин jQuery, позволяющий сделать любой многостраничный сайт похожим на одностраничное приложение (SPA). Благодаря предварительной загрузке контента с помощью jQuery и замене только части DOM, smoothState позволяет посетителю перейти по любому URL-адресу и получить полный HTML-ответ, а затем перейти на другие страницы без полной перезагрузки.
В одной из следующих статей этой серии мы рассмотрим все подробности, но необходимость в предварительной загрузке была в устранена благодаря Service Worker и хранилищу кэша, устанавливаемому при первом посещении. Поэтому smoothState был заменён на View Transitions:
@view-transition {
navigation: auto;
}
Эта комбинация простых View Transitions, кэша PWA и существующих визуальных переходов, происходящих при каждой загрузке страницы, обеспечивает мгновенную навигацию, ощущаемую как SPA. И это при том, что сократили целую JS-библиотеку!
Уменьшение движения через медиа-запрос
Движение обеспечивает плавность навигации, но оно также может быть неприятным для людей с нарушениями вестибулярного аппарата или просто нежелательным, если люди предпочитают не видеть его.
Добавьте CSS медиа-запрос prefers-reduced-motion
.
Эта настройка применяется на уровне операционной системы как средство обеспечения доступности и передаётся веб-браузеру в режиме реального времени. Этот медиа-запрос можно использовать для изменения каждого определения CSS переходов и анимации, но гораздо проще написать несколько действительно сильных переопределений, если медиа-запрос активен:
/**
* Уменьшение движения через медиа-запрос
*/
@media (prefers-reduced-motion: reduce) {
/* Отключение плавной прокрутки */
html:focus-within {
scroll-behavior: auto !important;
}
/* Отключение всех CSS переходов и CSS анимаций */
*,
*::before,
*::after {
animation-delay: -1ms !important;
animation-duration: 0.1ms !important;
animation-iteration-count: 1 !important;
background-attachment: initial !important;
scroll-behavior: auto !important;
transition-duration: 0.1ms !important;
transition-delay: 0.1ms !important;
}
/* Отключение всех View Transitions */
@view-transition {
navigation: none;
}
}
На момент написания статьи не удалось найти точный источник этого кода, но уверен, что это обновлённая версия современного CSS reset Энди Белла, который является предшественником более подробной версии в предыдущем блоке кода.
- Первое правило, относящееся к
scroll-behavior
, отменяет функцию плавной прокрутки, предоставляемую браузерами, в том числе когда используется механизм поиска браузера для перемещения по странице. - Второе правило, нацеленное на
*
и псевдоэлементы, по сути, упрощает все переходы и анимации, устраняя их одним махом. Это круто и означает, что не нужно каждый раз думать о реализации этого медиа-запроса; вместо этого можно написать свои правила, ориентированные на движение, и они будут отменены, если это необходимо. Дополнительная настройка возможна через свойствоanimation-fill-mode
, имеющее по умолчанию значениеnone
. Но если стили «ожидают», что анимация запустится и будут иметь смысл только после этого, возможно, потребуется выбрать другое значение, напримерforward
илиboth
. - Третье правило отменяет View Transitions и гарантирует, что они больше не применяются к странице. Мне нужно было отменить только один переход, но каждый из определённых переходов необходимо отменять отдельно.
Не уверен насчёт всех операционных систем, но на Mac эта настройка соблюдается в реальном времени во всех основных браузерах. Откройте System Settings > Accessibility > Reduce motion и включите эту опцию, и вы сразу же увидите, что многие эффекты исчезли: у фона пропал эффект медленного пульсирования, меню больше не скользит, и перемещение между страницами также не сопровождается эффектом движения.
В этом видео используется эмуляцию медиа-запроса в Chrome для переключения режима уменьшенного движения. Однако эффект точно такой же, и можно увидеть, как он меняется в реальном времени:

Скринкаст, демонстрирующий, как включение CSS медиа-запроса prefers-reduced-motion
влияет на веб-страницу в реальном времени, отключая все движения, включая CSS переходы, CSS анимацию и анимированные GIF-файлы.
Уменьшение движения через настройки JavaScript
Есть и второй способ уменьшить движение на DevilsGame — через страницу настроек. Возможно, вы просто находите движение на этом конкретном сайте отвлекающим или ещё не знаете о настройках на уровне ОС. Самое замечательное, что, написав правила для медиа-запроса CSS, можно повторить их для настроек на JS.
Страница настроек представляет собой HTML-форму, к которой прикреплены обработчики событий JavaScript. Вкратце, если медиа-запрос prefers-reduced-motion
уже включён, то JavaScript автоматически отключит checkbox
и покажет сообщение о соблюдении существующего предпочтения. Если медиа-запрос отсутствует, тогда переключим data-атрибут в корне документа следующим образом:
animationCheckbox.addEventListener('change', (ev) => {
// Добавление или удаление атрибута data в элементе <html>.
if (ev.target.checked) {
root.removeAttribute('data-reduce-motion');
} else {
root.setAttribute('data-reduce-motion', '');
}
// Сохранение нового значения после настройки.
window.localStorage.setItem('show_animations', ev.target.checked);
});
При каждой последующей загрузке страницы JS будет искать настройки в window.localStorage
и при необходимости повторно применять data-атрибут. Благодаря кэшированию Service Worker этот JS может выполняться более или менее мгновенно при каждой загрузке страницы, и страница никогда не покажет анимацию, если посетитель отключит её с помощью JS.
CSS для управления data-атрибутом, управляемым JS, должен быть немного изменён; одна из главных причин заключается в том, что я использовал оба псевдоэлемента в самом <html>
, чтобы обеспечить фоновую анимацию, сохраняющуюся, когда smoothState заменяет весь тег body
. Если вы не используете корневой тег для всего, что связано с движением, то можете продублировать CSS с уменьшенным движением, не внося в него никаких изменений:
/**
* Уменьшение движения с помощью настроек JS.
*
* То же самое поведение, что и в MQ, но с настройками JS,
* которые ищут атрибут data-reduce-motion на корневом элементе <html>.
*/
html[data-reduce-motion] {
/* повторите содержимое блока медиа-запроса из предыдущего раздела */
}
Даже если код приходится повторять, он всё равно выполняет ту же задачу по устранению движения без необходимости написания дополнительного кода для отдельных анимаций.
Уменьшение движения в отзывчивых изображениях
В одной из глав истории отображается анимированная графика, а во время обновления 2024 года другое агентство предоставило новый логотип сайта, также имеющий анимированный вариант. Все эти графические элементы изначально отображались с помощью тегов <img>
. Однако они также должны учитывать предпочтения посетителей в отношении движения.
Для начала я обернул все экземпляры <img>
тегом <picture>
, позволяющим использовать различные источники. Тег <source>
позволяет указать медиа-запрос, часто называемый художественное направление
, когда читаешь вводные статьи об элементе picture
. Вариант использования художественного направления
сфокусирован на контекстах отзывчивого веб-дизайна, где размер области просмотра является важным фактором для выбора правильного изображения.
Однако здесь подойдёт любой CSS медиа-запрос. В том числе медиа-запросы prefers-reduced-motion
! Рассмотрим следующее:
<picture>
<source media="(prefers-reduced-motion: reduce)" srcset="/img/looping-ring-of-fire-static.png">
<img src="/img/looping-ring-of-fire-optimized.gif" alt="Animated ring of fire" loading="lazy">
</picture>
При использовании тега <picture>
браузер выберет первый <source>
, условия которого выполняются. Это означает, что можно показать статичное изображение посетителю, предпочитающему уменьшенное движение.
Обработка настроек с помощью JS менее проста, но всё же возможна. Я написал JavaScript, обнаруживающий каждый <source>
, использующий медиа-запрос prefers-reduced-motion
, и изменяющий его так, чтобы вместо него был (min-width: 0px)
. Поскольку область просмотра всех визуальных пользовательских агентов больше 0
, он всегда будет выбираться вместо анимированного src
по умолчанию:
// Если найден data-атрибут с поддержкой JS, обрабатываем все теги <source> так,
// чтобы их условие гарантированно было истинным.
if (window.localStorage.getItem('show_animations') !== 'false') {
const sources = document.querySelectorAll('source[media*=prefers-reduced-motion]');
sources.forEach(el => {
el.setAttribute('media','(min-width: 0px)');
});
}
Движение остановлено
С помощью трёх описанных выше техник удалось создать полностью неподвижный интерфейс, не прибегая к серьёзной переработке существующей разметки или авторских стилей. Надеюсь, что, продемонстрировав, как это было сделано в реальном проекте, вы сможете адаптировать данные приёмы для использования в своей работе.
Работа над доступностью жизненно важна, и она не всегда может дожидаться следующего проекта, создаваемого с чистого листа. Улучшения доступности можно интегрировать в существующие сайты, а в некоторых случаях для этого даже не нужно менять существующий код.