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

Зависимая анимация по наведению: Тильда умеет, но по одной картинке

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

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

✓ ПРОВЕРЕНО 28 июля 2026 событие hover и список элементов-триггеров в пошаговой анимации, поведение группы картинок на списке из четырёх пунктов и первый кадр до запуска скрипта — стенд с Zero Block и рантаймом Тильды, Chromium, покадрово

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

Короткий ответ: Тильда это умеет

Пошаговая анимация Zero Block принимает событие hover и, главное, список других элементов, наведение на которые её запускает. То есть «наводишь на пункт — анимируется картинка рядом» — родная возможность, а не задача для скрипта: справка Тильды. Предел один: у картинок нет общего состояния, каждая включается сама по себе. На двух-трёх это незаметно, на шести — уже нет.

Как это устроено внутри

Элемент, который анимируется пошагово, помечен в разметке атрибутами:

<div class="tn-elem"
     data-animate-sbs-event="hover"
     data-animate-sbs-opts="…"
     data-animate-sbs-trgels="…">

Три вещи, которые из этого следует знать:

  • data-animate-sbs-event принимает и scroll, и hover. Именно здесь выбирается, чем анимация запускается.
  • data-animate-sbs-trgels — тот самый список элементов-триггеров. Рантайм читает его при разборе анимации и собирает из него набор узлов, на которые вешает слушатели.
  • Содержимое элемента заворачивается в .tn-atom__sbs-anim-wrapper. Это важнее, чем кажется: если вы соберётесь трогать такой элемент своим кодом, вы найдёте внутри лишний узел, которого не было в макете.

Любой атрибут с префиксом data-animate- означает, что элементом владеет анимация Тильды. Ваш код на такой элемент лезть не должен вовсе — двух эффектов на одном элементе посетитель не переживёт.

Почему на телефоне тоже срабатывает

Слушателей на элементы-триггеры вешается три: mouseenter, mouseleave и click. Последний и есть ответ на вопрос «а как это работает на телефоне, там же нет наведения»: тап приходит как клик, и анимация запускается.

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

Где встроенный инструмент упирается

Теперь по существу. У дизайнера список из шести пунктов и шесть картинок. Во встроенном инструменте это шесть отдельных анимаций: шесть раз выбрать элемент, шесть раз поставить событие hover, шесть раз указать список триггеров.

Дальше начинаются последствия:

Что нужноВо встроенном инструменте
Поменять длительностьшесть правок, по одной на анимацию
Добавить седьмой пунктещё одна анимация целиком
«Видна ровно одна картинка»не выражается: состояния нет
«На старте видна первая»не выражается: начального кадра нет
Переставить пункты местамитриггеры сбиваются, править руками

Первые две строки — про труд, и с ними можно жить. Третья и четвёртая — про то, что средствами настройки не выражается вообще.

Что значит «нет начальной картинки»

Пошаговая анимация описывает переход, а не состояние. Она отвечает на вопрос «что сделать, когда навели», и не отвечает на вопрос «а что видно до этого».

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

И то же самое между наведениями: увели курсор быстро, тапнули по второму пункту, не убрав палец с первого, вернулись на страницу из другой вкладки — состояний, которых никто не описывал, оказывается больше, чем описанных.

Переключатель против набора анимаций

Разница в одном предложении: набор анимаций отвечает на события, переключатель держит состояние.

Из «держит состояние» следует всё остальное. Видна ровно одна картинка. На старте — та, что назначена. Переход между любыми двумя — один и тот же. Добавить пункт — значит добавить пару, а не анимацию. Порядок элементов в артборде не имеет значения, потому что связывает пару номер, а не соседство.

В моде это выглядит как разметка классами: пункт получает mk-item-1, его картинка — mk-pic-1, следующая пара — mk-item-2 и mk-pic-2. Всё остальное делает код: считает пары, показывает одну, прячет остальные и пересчитывает это на каждом событии заново — между наведениями Тильда могла переставить узлы.

Прозрачность при этом пишется инлайном, а не правилом:

pr.atom.style.opacity = on ? String(pr.base) : '0';

Причина — не вкус: Zero Block держит прозрачность в правиле с id в селекторе, и правило по классу секции его не перебивает. Это отдельный разбор — почему ваш CSS проигрывает Zero Block.

Обратите внимание на pr.base: показываем до прозрачности, которую задал дизайнер, а не до единицы. Если картинка в макете стоит на 80 %, она такой и остаётся.

И одна подробность разметки, на которой спотыкаются все, кто связывает элементы классами. Класс, вписанный в поле «CSS класс» элемента Zero Block, приезжает в разметку с хвостом _elem: вы написали mk-pic-1, а в сохранённой странице стоит mk-pic-1_elem. Поэтому селектор надо готовить к обеим формам сразу:

// Обе формы годятся, и дизайнеру не нужно знать, какую выберет Тильда.
return new RegExp('^' + safe + '-(\\d+)(?:_elem)?$');

Ищете вы своё же имя класса и не находите — проверьте это первым делом.

Спрятанная картинка обязана пропускать клики

Одно правило, без которого эффект выглядит работающим и при этом сломан:

/* Спрятанная картинка остаётся в раскладке на своём месте, поэтому она обязана
   пропускать клики: иначе посетитель попадает по невидимой картинке вместо
   видимой, и если это ссылка — уходит не туда. */
.mk-hover [data-mk-pic='out'] {
  pointer-events: none;
}

Прозрачность не убирает элемент из раскладки. Шесть картинок лежат друг на друге, пять из них невидимы — и клик достаётся верхней, то есть случайной. В Zero Block картинка часто и есть ссылка (<a class="tn-atom">), так что цена ошибки — посетитель уходит не на ту страницу.

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

С клавиатуры и без анимации

Два требования, которые к «наведению» не относятся, но без них эффект работает только для части посетителей.

Клавиатура. Текстовый элемент Zero Block сам по себе фокус не принимает — значит, до пункта нельзя добраться табом, и переключатель для клавиатуры не существует. Ссылка внутри пункта фокус принимает; если её нет, пункту нужен tabindex="0". Слушателей поэтому три:

/* Слушатели — на секции, а не на пунктах: mouseover, focusin и touchstart
   всплывают, поэтому делегирование работает, и замена внутренних узлов
   обработчики не уносит. mouseenter не всплывает — он здесь не годится. */
section.addEventListener('mouseover', onPoint);
section.addEventListener('focusin', onPoint);
section.addEventListener('touchstart', onPoint, { passive: true });

focusin в этом списке — весь клавиатурный доступ. И заметьте, почему не mouseenter, который для наведения кажется точнее: он не всплывает, до секции не доходит, а вешать слушатели на сами пункты нельзя — Zero Block переставляет узлы.

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

Чего не должен делать любой такой эффект

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

Из него следуют три отказа, каждый из которых лучше молчаливой поломки:

  • Картинка без пункта-триггера не прячется. Показать её потом нечем — значит, спрятать её один раз это убрать её со страницы навсегда.
  • На одной паре эффект не включается. Переключать нечего, а прятать нельзя.
  • Картинка, которой уже владеет анимация Тильды, не трогается. Два эффекта потянут её каждый в свою сторону, и это увидит посетитель.

И последнее, про направление поломки. Начальное состояние ставит код, а не правило CSS, поэтому до первого прохода видны все картинки. Это выбрано осознанно: если скрипт не отработает, посетитель увидит лишнее, а не пустое место. Правило CSS с opacity: 0 при той же поломке оставило бы дырку в макете.

Что проверить у себя

  1. Сначала настройки. Пошаговая анимация, событие hover, список элементов-триггеров. На двух-трёх картинках дальше идти незачем.
  2. Посмотрите на страницу до наведения. Видно всё сразу или ничего — значит, начального состояния нет.
  3. Проверьте с телефона. mouseleave там не наступит; тап включает и не выключает.
  4. Тапните по двум пунктам подряд. Остались обе картинки — общего состояния нет.
  5. Кликните по месту, где лежит невидимая картинка. Ушли не туда — не хватает pointer-events: none.
  6. Посчитайте пункты. Больше четырёх — настройка руками начнёт стоить дороже готового переключателя.
Вопросы по этой статье 4

На двух-трёх картинках — можно, и это правильный первый шаг. Пошаговая анимация умеет и hover, и список элементов, на которые надо наводить. Настройка руками растёт линейно: на шести пунктах это шесть анимаций, у каждой свой триггер, и любую правку придётся повторить шесть раз.