Перейти к содержимому
Забрать бесплатно
Модификации выключены. Так эта страница выглядит на обычной Тильде.

Анимация на Тильде и prefers-reduced-motion: что делает скрипт

28 июля 2026 8 МИН КОМАНДА MODKOD

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

✓ ПРОВЕРЕНО 28 июля 2026 отсутствие строки prefers-reduced-motion во всех ресурсах Тильды со снятой страницы (27 файлов CSS, 53 файла JS, включая все четыре системы анимации), поведение модов при включённой настройке и без мыши, девять контейнеров .t-input-error с aria-live в сохранённой форме — снимок страницы Тильды в modkit/stand, Chromium; настройка включалась на уровне системы

Доступность на Тильде обычно обсуждают как альт-тексты и контрастность. Это верно, но к сторонним скриптам относится мало: скрипт добавляет на страницу движение, невидимые элементы и свои сообщения — и ломается доступность именно там. Ниже четыре места, где это происходит, с кодом, который делает правильно.

Короткий ответ

Скрипт, добавляющий движение, обязан проверить настройку «меньше движения» и при ней не запускаться — до того, как он что-то спрятал или создал. Эффект, которому нужна мышь, не включается там, где мыши нет. Что стало невидимым, обязано пропускать клики и уходить из чтения скринридером. Сама Тильда настройку не проверяет: во всех её ресурсах со снятой страницы строки prefers-reduced-motion нет ни одной.

Кто такой посетитель с «меньше движения» и чего он просил

Это не настройка «не люблю анимацию». У части людей движение на экране вызывает вестибулярную реакцию — головокружение, тошноту, дезориентацию. Настройка делается один раз в системе, а не на каждом сайте, и браузер сообщает её странице медиавыражением prefers-reduced-motion. Пути в системе, если нужно проверить у себя, перечислены в справочнике MDN — оттуда же эта таблица:

СистемаГде включается
Windows 11Параметры → Специальные возможности → Визуальные эффекты → Эффекты анимации
macOS до 15Системные настройки → Универсальный доступ → Дисплей → Уменьшение движения
macOS 25 и новееСистемные настройки → Универсальный доступ → Движение → Уменьшение движения
iOSНастройки → Универсальный доступ → Движение
Android 9+Настройки → Специальные возможности → Удалить анимацию

Просьба конкретная: убрать несущественное движение. Плавное появление текста, бегущие числа, параллакс, кольцо, которое ездит за курсором, — всё это несущественное движение по определению: страница без него работает полностью.

Скрипт должен не запускаться, а не «анимировать быстрее»

В ядре библиотеки проверка занимает одну функцию:

function reduced() {
  return !!(
    window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches
  );
}

Важнее, где стоит её вызов. В моде проявления текста — сразу после того, как элементы найдены, и до того, как хоть один из них спрятан:

if (!items.length) return;

// Уважение к системной настройке: движение отключено — ничего не прячем.
if (MK.reduced()) return;

В моде бегущих чисел то же место и та же причина, только формулировка своя — «числа как есть»: посетитель видит итоговое значение, а не ноль, который никогда не начнёт расти.

Из этого следует правило, которое важнее самой проверки: начальное состояние эффекта обязан ставить скрипт, а не CSS. Соблазн написать в стиле .mk-reveal p { opacity: 0 } велик — так текст не мигнёт до запуска. Но правило из <style> применится всегда, а скрипт может не дойти до дела: настройка включена, ошибка выше в коде, блок T123 не сохранился. И тогда на странице остаётся невидимый текст, которого нет способа показать. При правильном порядке худший исход — текст просто виден без анимации.

Медиазапрос в CSS при этом лишним не будет, но как вторая страховка, а не как единственная защита. Скрипт делает не только анимацию: он создаёт элементы, подписывается на события, считает координаты каждый кадр. Спрятать результат через @media — не то же самое, что не начинать.

Курсор-кольцо на тач-устройстве — мусор на экране

Мод с кольцом-курсором проверяет два условия, и оба до создания элемента:

var hoverable = !window.matchMedia || window.matchMedia('(hover: hover)').matches;
if (!hoverable || MK.reduced()) return;

Первое — про мышь. Кольцо, которое догоняет курсор, на телефоне не догоняет ничего: курсора там нет, элемент повиснет в углу или посреди экрана и останется там. Проверка hover: hover отвечает не на вопрос «телефон ли это», а на нужный — «есть ли устройство, которое умеет наводить».

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

В стилях те же два условия повторены медиазапросами:

@media (prefers-reduced-motion: reduce) {
  #mk-cursor { display: none; }
}

/* Без мыши кольцо не нужно. Скрипт и так не запускается, это вторая страховка. */
@media (hover: none) {
  #mk-cursor { display: none; }
}

Ещё две вещи в этом же моде стоит назвать. Системный курсор по умолчанию остаётся на месте: спрятать его можно параметром, но это ухудшает доступность, поэтому не по умолчанию. И кольцо получает aria-hidden="true" в момент создания — оно декоративное, и в чтении страницы ему делать нечего.

Невидимое не должно перехватывать клики

Это самая тихая из ошибок. Элемент с opacity: 0 невидим, но остаётся в раскладке и продолжает принимать клики. Посетитель нажимает на то, что видит, а попадает в то, чего не видит. Если невидимое — ссылка, он уходит не туда, и пожаловаться на это невозможно внятно: «кнопка не работает» звучит как каприз.

Мышь закрывается одной строкой — pointer-events: none на спрятанном элементе; в моде зависимой анимации она стоит с объяснением прямо в CSS, вместе с разбором самого эффекта — зависимая анимация по наведению.

Дальше начинается то, чего этой строкой не закрыть. pointer-events — про указатель, и на дерево доступности он не влияет вообще: прозрачная картинка остаётся в нём наравне с видимой. Значит, программа чтения экрана пройдёт по всем шести подписям подряд, из которых человек видит одну, и структура страницы для незрячего разойдётся с тем, что на экране, — причём в ту же сторону, что и клик по невидимому: содержание есть, а причины для него нет. Поэтому у спрятанного элемента должен сниматься и ставиться aria-hidden — вторым действием там же, где меняется прозрачность, а не отдельным проходом.

У кольца из предыдущего раздела pointer-events: none стоит всегда: оно висит поверх всей страницы, и без этого правила по странице стало бы нельзя щёлкнуть вообще.

Отдельная проверка — клавиатура. Если эффект висит только на mouseover, для человека без мыши он не существует. В моде зависимой анимации к наведению добавлен focusin: пункт списка, до которого дошли табуляцией, включает картинку так же, как наведённый курсором.

Ошибка поля: почему своя плашка хуже родного контейнера

В сохранённой форме Тильды под каждым полем уже лежит контейнер .t-input-error внутри .t-input-group. Он не пустая заготовка: у него есть aria-live="polite", и он связан с полем через aria-describedby="error_<id>" — в проверенной форме таких контейнеров девять.

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

Отсюда правило, применимое к любому моду, который сообщает посетителю об ошибке: писать текст в найденный .t-input-error внутри .t-input-group того же поля и ставить группе класс js-error-control-box — тот же, что ставит сама Тильда. Готовый код этого приёма с разбором каждой строки — в статье про проверку длины телефона, где он и работает.

Побочная выгода тут не в доступности, а в виде: контейнер уже оформлен дизайнером, и своё сообщение выглядит как родное, без единой строки CSS.

Что уносит aria-describedby и как это заметить

У этой связки есть враг, и это не сторонний скрипт. Если в редакторе включена автоматическая маска телефона, tilda-phone-mask-1.1.min.js при запуске пересобирает поле: вместо одного <input> появляется обёртка с выбором страны и новое поле, а номер с кодом уходит в скрытое поле результата. Вместе со старым узлом пропадает и aria-describedby — связь поля с контейнером ошибки, которую Тильда написала в разметке сама.

Заметить это можно за десять секунд на опубликованной странице: выбрать поле телефона в инспекторе и посмотреть, есть ли на нём aria-describedby. Если поле лежит внутри .t-input-phonemask__wrap, атрибута не будет. Починить это, не отбирая маску у Тильды, можно только одним способом — вернуть атрибут после того, как маска отработала, и следить, чтобы он не потерялся снова.

Чек-лист: семь проверок к любой модификации

  1. Системная настройка «меньше движения» проверяется в самом скрипте, и при ней он не запускается, а не работает быстрее.
  2. Если анимации нет, контент виден. Начальное состояние ставит скрипт, а не правило в <style>.
  3. Эффекту, которому нужна мышь, проверяется hover: hover — до создания элементов.
  4. Всё, что стало невидимым, получает pointer-events: none.
  5. Всё декоративное и всё скрытое получает aria-hidden, и атрибут снимается при показе.
  6. Эффект по наведению доступен с клавиатуры — focusin рядом с mouseover.
  7. Сообщение об ошибке идёт в родной .t-input-error, а не в свою плашку.

Все моды библиотеки собраны по этим семи пунктам, и это записано в шапке каждого до кода — каталог. Если нужен эффект под свою задачу и с теми же правилами, это отдельная работа — эффект на заказ.

Вопросы по этой статье 4

Нет. В снятых со страницы ресурсах Тильды — 27 файлов стилей и 53 файла скриптов, включая все четыре её системы анимации — строки prefers-reduced-motion нет ни одной. Значит встроенные появления, пошаговые анимации и параллакс работают одинаково у всех, а учитывать настройку может только тот код, который вы добавили сами.