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

Почему анимация в Zero Block не работает, хотя CSS написан верно

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

Zero Block выписывает для каждого элемента артборда собственное правило, в селекторе которого стоит id секции. Его вес — (1,3,0), и ваше правило по классу с весом (0,2,0) проигрывает ему всегда. Проигрывает молча: скрипт отработал, атрибуты на месте, в консоли чисто, анимации нет. Разбираем, что именно пишет Zero Block, почему !important закрывает симптом, а не задачу, и почему прозрачность в Тильде ставят инлайном, а сдвиг — только правилом.

✓ ПРОВЕРЕНО 24 июля 2026 правило Zero Block с id в селекторе, отбор transform у элементов с backdrop-filter и снятие инлайнов после анимации — живой артборд T396 с рантаймом Тильды, Chromium

Симптом всегда один и тот же. Вы пишете в блок T123 несколько строк CSS — спрятать текст и показать его по скроллу, — публикуете страницу, и текст просто стоит на месте. В консоли пусто. Селектор в инспекторе находится. Правило написано верно. Ничего не происходит.

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

Ваше правило есть, но браузер его отбросил. Zero Block выписывает для каждого элемента артборда своё правило, и в селекторе стоит id секции — вес (1,3,0). Правило по классу секции весит (0,2,0) и проигрывает по первому же разряду. Отброшены и opacity, и transition, которым собирались показать текст. Решение зависит от свойства: прозрачность ставят только инлайном, сдвиг — только правилом.

Сначала: не занята ли анимация самой Тильдой

Проверка на тридцать секунд, которая иногда закрывает вопрос целиком. У элемента Zero Block есть своя панель «Анимация», и если дизайнер её включил, то движением элемента уже владеет Тильда. Ваш эффект в этом случае не «не работает» — он воюет с её эффектом за один и тот же элемент, и результат зависит от того, кто успел последним.

Систем анимации у Тильды не одна, и по одному признаку их не видно:

  • data-animate-style — обычное появление;
  • data-animate-sbs-event, -sbs-opts, -sbs-trgels — пошаговая анимация; она ещё и заворачивает содержимое элемента в свою обёртку .tn-atom__sbs-anim-wrapper;
  • data-animate-prx, -prx-dx — параллакс, он тоже правит transform на скролле;
  • data-animate-fix, data-animate-chain — фиксация и связки.

Поэтому смотреть надо не на конкретный атрибут, а на приставку:

function animatedByTilda(el) {
  for (var node = el; node && node.nodeType === 1; node = node.parentElement) {
    for (var i = 0; i < node.attributes.length; i++) {
      if (node.attributes[i].name.indexOf('data-animate-') === 0) return true;
    }
  }
  return false;
}

Проверять надо и родителей: анимацию дизайнер включает и группе. Если функция вернула true — правильный ответ лежит не в CSS, а в той же панели «Анимация». Она умеет больше, чем принято думать: у пошаговой анимации есть и события hover/click, и список элементов-триггеров, так что «наводишь на пункт списка — двигается картинка рядом» делается настройками. Начинать стоит со справки Тильды про пошаговую анимацию, а не со скрипта.

Что Zero Block пишет для каждого элемента

Откройте инспектор на опубликованной странице с артбордом и посмотрите на любой текстовый элемент. Рядом с вашими правилами найдётся такое:

#rec1024570506 .tn-elem[data-elem-id="1738663428334"] .tn-atom {
  opacity: 0.8;
  transition:
    background-color 0.3s ease-in-out,
    color 0.3s ease-in-out,
    border-color 0.3s ease-in-out,
    box-shadow 0.3s ease-in-out;
}

Здесь важно каждое слово селектора:

  • #rec1024570506 — id секции. Один идентификатор в селекторе.
  • .tn-elem[data-elem-id="…"] — класс и атрибут. Ещё два разряда среднего веса.
  • .tn-atom — третий.

Итого (1,3,0): один идентификатор, три класса-атрибута, ноль элементов.

opacity в этом правиле появляется не всегда — только если дизайнер выставил элементу прозрачность в панели. transition для ховера — почти всегда.

Почему .my-class .tn-atom проигрывает — и почему этого не видно

Правило, которое обычно пишут в T123, выглядит так:

.my-reveal .tn-atom {
  opacity: 0;
  transition: opacity 0.6s ease;
}

Вес — (0,2,0): два класса. Сравнение специфичности идёт по разрядам слева направо, и первый же разряд решает всё: 1 против 0. Ни второй разряд, ни третий, ни порядок правил в документе уже не рассматриваются. Ваше правило проиграет, даже если оно написано ниже, даже если оно подключено позже.

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

Это и есть характер всей работы с Тильдой: почти все поломки здесь молчаливые. Ничего не падает — просто ничего не происходит.

Что пробуют дальше и почему это не помогает

Дописать ещё один класс. .t396 .my-reveal .tn-atom — это (0,3,0). Первый разряд остался нулём, сравнение снова заканчивается на нём. Классов можно приписать пять, результат не изменится.

Поставить в селектор id записи. #rec1024570506 .my-reveal .tn-atom даёт (1,3,0) — ровно столько же, сколько у правила Тильды, и дальше исход решает порядок в документе. Правило начинает применяться, и это худший из возможных исходов: id записи принадлежит не вам. Он меняется, когда страницу дублируют, когда блок копируют в другую страницу, когда секцию пересоздают. Тот же код в общем для сайта блоке T123 сработает на одной странице и промолчит на остальных. Через полгода никто не свяжет «анимация пропала» с тем, что страницу копировали.

Подключить свой CSS ниже. Порядок в каскаде рассматривается только при равном весе. При разном — не рассматривается вообще, где бы ваше правило ни стояло.

Почему !important «работает» и всё равно неверный ответ

!important поднимает объявление выше любой специфичности, и правило начинает применяться. Симптом уходит. Проблема — в том, что вы перебиваете не только opacity.

Посмотрите на правило Zero Block ещё раз: в нём вместе с прозрачностью лежит transition для ховера — тот, который дизайнер настроил в панели «При наведении». Ваше transition: opacity 0.6s ease !important его затрёт, и ховер элемента перестанет работать. Не сломается заметно — просто станет мгновенным. Никто не свяжет это с анимацией появления, добавленной три месяца назад.

Вторая причина серьёзнее. !important в таблице стилей остаётся там навсегда. Анимация появления нужна на шестьсот миллисекунд, а перебитая специфичность живёт всё время, пока страница открыта. Инлайновый стиль даёт ровно тот же приоритет и при этом снимается одной строкой:

el.style.removeProperty('opacity');
el.style.removeProperty('transition');

После этого элемент снова живёт по правилам дизайнера — со своей прозрачностью и своим ховером.

opacity — только инлайном, transform — только правилом

Дальше начинается неочевидное. Правило «пишите инлайном» верно не для всех свойств: у прозрачности и у сдвига опасности зеркальные.

СвойствоГде его пишет ТильдаГде можно писать вам
opacityВ правиле элемента, вес (1,3,0). Инлайном — никогда.Только инлайном. Инлайн перебивает правило, и Тильда его не затирает.
transitionВ правиле элемента (ховер). Инлайном — никогда.Только инлайном, и снимать, когда анимация кончилась.
transformИнлайном, при каждой перерисовке артборда. В правиле — нет.Только правилом. Инлайн затрёт следующая перерисовка.
height, width, left, topИнлайном, при каждой перерисовке.Нигде. Раскладка артборда принадлежит Zero Block.

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

Поэтому сдвиг остаётся правилом. Правило Тильда не затирает, потому что своего transform в правилах она не держит:

.mk-reveal [data-mk-reveal]:not([data-mk-fade]) {
  transform: translate3d(var(--mk-x, 0px), var(--mk-y, 0px), 0);
}

.mk-reveal [data-mk-reveal='in']:not([data-mk-fade]) {
  transform: translate3d(0, 0, 0);
}

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

Где transform вообще нельзя брать

Есть класс элементов, у которых сдвиг не сработает никаким способом. Если у элемента (или у его обёртки в двух шагах вверх) задан backdrop-filter, Zero Block переносит transform с .tn-atom на обёртку, а самому атому ставит transform: none — инлайном. Инлайн перебьёт ваше правило, и элемент застынет посреди сдвига: он выехал на 24 пикселя и остался там.

Проверить это до того, как что-то ставить, можно прямо из скрипта:

function canTransform(el) {
  var node = el;
  for (var depth = 0; node && depth < 3; depth++) {
    var s = getComputedStyle(node);
    var bf = s.backdropFilter || s.webkitBackdropFilter;
    if (bf && bf !== 'none') return false;
    node = node.parentElement;
  }
  return true;
}

Три узла — не магическое число, а ровно та вложенность, которая есть. Проверять надо .t396__elem: именно на нём Zero Block смотрит backdrop-filter (t396__processElementsTransform в tilda-zero-1.1.min.js). А в функцию вы передаёте .tn-atom, потому что двигать надо его. Атом лежит либо прямым ребёнком элемента, либо под одной обёрткой — .tn-atom__sbs-anim-wrapper, -prx-wrapper или -sticky-wrapper. Атом → обёртка → элемент: три узла, ни одного лишнего.

И ещё одно, из-за чего эту проверку нельзя пропустить «потому что у меня всё работает». t396__processElementsTransform вызывается не один раз: она же отрабатывает при смене ширины окна и при повороте экрана. К этому моменту вычисленный трансформ атома — это уже ваш трансформ. Тильда прочитает его как дизайнерский, выпишет атому transform: none и приколотит ваше текущее значение к обёртке. Элемент замрёт в той позе, в которой его застал поворот телефона.

Для таких элементов остаётся проявление по прозрачности. Оно работает всегда: инлайновой opacity Тильда не пишет нигде.

Не считайте opacity: 1 целью

Самая тихая ошибка из всех. Стандартная анимация появления выглядит как «от 0 до 1», и почти во всех примерах в интернете написано именно так.

Но opacity: 0.8 в правиле Zero Block означает, что дизайнер задал этому элементу восемьдесят процентов прозрачности осознанно. Анимация «до единицы» сделает текст ярче, чем его задумали, — и оставит его таким. Никто не напишет вам об этом в поддержку: страница выглядит нормально, просто не так, как в макете.

Цель нужно прочитать у браузера до того, как прятать элемент:

function ownOpacity(el) {
  var v = parseFloat(getComputedStyle(el).opacity);
  return isNaN(v) ? 1 : v;
}

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

В обычных блоках этой проблемы нет

Всё описанное — про Zero Block, и только про него. Обычные блоки Тильды (T030, T015, T060 и остальные) правил с id записи для отдельных элементов не пишут: внутри них заголовок и текст размечены классами .t-title и .t-text, и вес таких правил — те же (0,2,0), что у вашего. При равном весе решает порядок в документе, а <style> из блока T123 стоит в документе позже подключённых стилей Тильды. Ваше правило выигрывает.

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

Как проверить, что анимация есть, а не «скрипт сработал»

Проверка вида «класс на месте, атрибут поставился, ошибок нет» этот класс поломок не видит вообще. Она проверяет ваш код, а сломалось не в нём, а в каскаде.

Спрашивать надо у браузера — есть ли на элементе настоящая анимация:

document.getAnimations().filter((a) => a.effect.target.matches('[data-mk-reveal]'));

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

Второй способ, без консоли: откройте вкладку «Animations» в инструментах разработчика и прокрутите до секции. Если полоса не появилась — анимации нет.

Чек-лист диагностики

Пройдите сверху вниз — почти всегда останавливается на первых трёх пунктах.

  1. Страница опубликована, а не открыта в редакторе. В редакторе Тильда не выполняет пользовательский JS.
  2. Кеш сброшен — Ctrl+Shift+R. Тильда отдаёт закешированный HTML до пяти минут.
  3. В инспекторе ваше правило не перечёркнуто. Перечёркнуто — вы попали именно в эту статью: перепишите прозрачность инлайном.
  4. Класс стоит на секции, а не на элементе Zero Block. Настройки блока → CSS класс. Не у элемента внутри артборда.
  5. opacity вы ставите инлайном, transform — правилом. Наоборот — не работает ни то, ни другое, но по разным причинам.
  6. Целевая прозрачность прочитана у элемента, а не взята за единицу.
  7. У элемента нет backdrop-filter — иначе сдвига не будет, только проявление.
  8. Инлайновые стили снимаются после анимации. Иначе ховер дизайнера мёртв.
  9. document.getAnimations() непуст в момент входа секции в экран.
  10. На секции нет второй анимации — ни вашей, ни встроенной в Тильду. Если у элемента есть любой атрибут, начинающийся с data-animate-, его анимацией уже владеет Тильда, и в него лучше не лезть.

Если же document.getAnimations() пуст всегда и ни у одного элемента анимации нет вовсе, дело может быть не в каскаде: секция могла просто ни разу не быть признана вошедшей в экран. Это отдельная поломка со своей арифметикой — порог наблюдателя и высота секции.

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

В редакторе Тильда не выполняет пользовательский JS из блока T123 — вы видите либо её собственную анимацию, либо ничего. Проверять нужно только на опубликованной странице и с очищенным кешем, потому что Тильда отдаёт закешированный HTML до пяти минут.