Модернизация с 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 Энди Белла, который является предшественником более подробной версии в предыдущем блоке кода.

  1. Первое правило, относящееся к scroll-behavior, отменяет функцию плавной прокрутки, предоставляемую браузерами, в том числе когда используется механизм поиска браузера для перемещения по странице.
  2. Второе правило, нацеленное на * и псевдоэлементы, по сути, упрощает все переходы и анимации, устраняя их одним махом. Это круто и означает, что не нужно каждый раз думать о реализации этого медиа-запроса; вместо этого можно написать свои правила, ориентированные на движение, и они будут отменены, если это необходимо. Дополнительная настройка возможна через свойство animation-fill-mode, имеющее по умолчанию значение none. Но если стили «ожидают», что анимация запустится и будут иметь смысл только после этого, возможно, потребуется выбрать другое значение, например forward или both.
  3. Третье правило отменяет View Transitions и гарантирует, что они больше не применяются к странице. Мне нужно было отменить только один переход, но каждый из определённых переходов необходимо отменять отдельно.

Не уверен насчёт всех операционных систем, но на Mac эта настройка соблюдается в реальном времени во всех основных браузерах. Откройте System Settings > Accessibility > Reduce motion и включите эту опцию, и вы сразу же увидите, что многие эффекты исчезли: у фона пропал эффект медленного пульсирования, меню больше не скользит, и перемещение между страницами также не сопровождается эффектом движения.

В этом видео используется эмуляцию медиа-запроса в Chrome для переключения режима уменьшенного движения. Однако эффект точно такой же, и можно увидеть, как он меняется в реальном времени:

Скринкаст, демонстрирующий, как включение CSS медиа-запроса `prefers-reduced-motion` влияет на веб-страницу в реальном времени, отключая все движения, включая CSS переходы, CSS анимацию и анимированные GIF-файлы.

Скринкаст, демонстрирующий, как включение 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)');
});
}

Движение остановлено

С помощью трёх описанных выше техник удалось создать полностью неподвижный интерфейс, не прибегая к серьёзной переработке существующей разметки или авторских стилей. Надеюсь, что, продемонстрировав, как это было сделано в реальном проекте, вы сможете адаптировать данные приёмы для использования в своей работе.

Работа над доступностью жизненно важна, и она не всегда может дожидаться следующего проекта, создаваемого с чистого листа. Улучшения доступности можно интегрировать в существующие сайты, а в некоторых случаях для этого даже не нужно менять существующий код.

Комментарии


Дополнительные материалы

Предыдущая Статья

Резервные значения пользовательских свойств CSS

Следующая Статья

Разноцветное выделение в CSS