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

Два мода на одной странице Тильды: сниппеты не мешают друг другу

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

Два самодостаточных сниппета на одной странице не конфликтуют, если второй экземпляр ядра не создаётся, а находит первый. Тогда два мода делят один наблюдатель, один таймер и один объект регистраций, а на странице остаётся один стиль на мод и одна метка на секцию. Цена за самодостаточность платится не поведением, а байтами: ядро лежит в каждом сниппете, и на этот счёт ниже цифры, а не слово «легковесно».

✓ ПРОВЕРЕНО 29 июля 2026 две записи T123 с разными модами на одной странице — одно ядро, один стиль на мод, один MutationObserver; тот же сниппет, вставленный дважды; вес сниппетов и прирост от второго при gzip; для случая с модами двух продавцов — что масштаб Zero Block Тильда берёт через глобальное имя window.t396__getCurrentScaleFactor за проверкой typeof, и что 33 из 44 файлов tilda-*.js не обёрнуты и держат свои функции глобальными — стенд с рантаймом Тильды, Chromium через Playwright; заголовки ответа живой Тильды через curl; чтение сохранённого рантайма — tilda-zero-scale-1.0.min.js и tilda-zero-1.1.min.js

Совет «не вставляйте на страницу два сторонних скрипта» встречается часто, и за ним стоит настоящий опыт: два независимых куска кода правда умеют мешать друг другу. Но мешают они не потому, что их два, а из-за конкретных вещей в коде. Здесь разобрано, какие это вещи и как выглядит код, у которого этой проблемы нет.

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

Сниппет ModKod самодостаточен: внутри и ядро, и код мода. Второй такой блок второе ядро не создаёт — он смотрит window.MK.version и, если ядро на странице уже есть и не старше, работает с найденным. Дальше два мода делят один объект регистраций, одну очередь, один таймер и один MutationObserver. Платить за это приходится весом: ядро едет в каждом сниппете.

Что происходит, когда два сниппета объявляют одну глобальную переменную

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

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

<script>
(function () {
  'use strict';

  var MK = (function () { /* ядро */ })();

  /* код мода: MK.define('MOD-041', { … }) */

  MK.run("MOD-041", { duration: 600, stagger: 70 });
})();
</script>

var MK объявлена внутри функции, а значит глобальной переменной не является: снаружи её не видно, и второй сниппет со своим var MK про неё ничего не знает. Единственное общее имя на всю страницу — window.MK, и его ставит только ядро, последней строкой. Всё остальное — имена функций, счётчики, кеши — заперто в своём вызове. Тильда, кстати, советует то же самое про стили: не брать в селекторы теги и *, а использовать свои классы, чтобы не задеть чужое.

Защита версией: второй экземпляр берёт первый, а не перетирает

Начало ядра:

var MK = (function () {
  var VERSION = 2;

  // Ядро на странице уже есть и оно не старше нашего — берём его.
  if (window.MK && window.MK.version >= VERSION) return window.MK;

Сравнение именно >=, а не ===, и это важно. Если сниппеты собраны в разное время и во втором ядро постарше, оно увидит на странице более новое и уступит ему: новое ядро умеет всё, что умело старое. Обратный порядок — сначала старое, потом новое — единственный случай, когда на странице заводятся два ядра: новое не может доверить работу старому, строит своё и занимает window.MK.

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

Один стиль на страницу, сколько бы копий ни вставили

/** Стиль мода — один раз на страницу, сколько бы копий сниппета ни вставили. */
function css(id, text) {
  var key = String(id).toLowerCase();
  if (document.querySelector('style[data-mk="' + key + '"]')) return;
  var style = document.createElement('style');
  style.setAttribute('data-mk', key);
  style.appendChild(document.createTextNode(text));
  (document.head || document.documentElement).appendChild(style);
}

Проверка идёт по странице, а не по переменной вроде injected[id] = true. Разница видна ровно в том пограничном случае из предыдущего раздела: переменная живёт внутри одного ядра, а document.querySelector видит и то, что добавило чужое. Второе ядро находит уже вставленный стиль и уходит.

Один наблюдатель на всех, а не по одному на мод

Секции в Тильде появляются и пересобираются после загрузки: артборд Zero Block собирается позже DOMContentLoaded, ленивые блоки — ещё позже. Значит, страницу нужно перепроверять. Наблюдатель при этом один на все моды:

function watch() {
  if (watching) return;
  watching = true;

  // Zero Block пересобирает артборд на ресайзе — секцию нужно проверить снова.
  window.addEventListener('resize', schedule);

  var root = document.getElementById('allrecords') || document.body;
  if (root && window.MutationObserver) {
    new MutationObserver(schedule).observe(root, { childList: true, subtree: true });
  }
}

Корень — #allrecords, а не document: это наименьший узел, внутри которого Тильда создаёт и перерисовывает записи. Из типов изменений — только childList: свои же атрибуты и классы наблюдателя не будят, иначе метка на секции вызывала бы новую проверку, которая ставит метку.

Второй предохранитель — общий таймер:

function schedule() {
  if (timer) return;
  timer = setTimeout(function () {
    timer = null;
    rescan();
  }, 80);
}

rescan() проходит по общей очереди и запускает все зарегистрированные моды. Поэтому проверок ровно столько, сколько было изменений на странице, а не столько же, умноженное на число модов. Считать тут есть что: каждый MK.run ставит серию повторов на 150, 400, 1000 и 2500 мс — два мода дают восемь вызовов schedule, к ним добавляется ресайз и каждое изменение разметки. Все они сходятся в один проход раз в 80 мс.

Метка ставится до запуска: упавший мод не зовут второй раз

for (var i = 0; i < found.length; i++) {
  var el = found[i];
  if (el.hasAttribute(attr)) continue;
  // Метку ставим до запуска: если мод упадёт, второй раз его не зовём.
  el.setAttribute(attr, '1');
  try {
    mod.setup(el, params);
  } catch (e) {
    warn(id, e);
  }
}

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

Метка перед запуском превращает это в одну строку в консоли с идентификатором мода: [ModKod] MOD-041: …. Остальная страница при этом работает — падение мода поймано, соседний мод и рантайм Тильды его не заметят.

Сколько это весит на самом деле: цифры вместо «легковесно»

Ядро в собранном сниппете — 13 312 байт. Вот из чего складывается каждый:

МодСниппетКод модаДоля ядра
MOD-012 · кольцо-курсор22 231 Б8 919 Б60 %
MOD-041 · проявление текста25 410 Б12 098 Б52 %
MOD-077 · анимация чисел27 961 Б14 649 Б48 %
MOD-146 · зависимая анимация32 040 Б18 728 Б42 %
MOD-072 · проверка телефона36 361 Б23 049 Б37 %

Два мода из первых строк — это 47 641 байт исходника в разметке страницы. Но посетитель качает сжатое, и живая Тильда отдаёт страницы с content-encoding: gzip — это проверено запросом, а не предположение. У gzip окно поиска повторов 32 КБ, и от расстояния между блоками зависит, найдёт ли он второе ядро:

Где стоит второй блокВторой сниппет добавил
сразу за первым3 288 Б
через 20 КБ разметки7 632 Б
через 60 КБ разметки8 091 Б
он один на странице8 151 Б

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

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

Если моды куплены у двух разных продавцов

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

Можно, и место столкновения у такой пары одно. Не «два скрипта на странице», а конкретное глобальное имя, которое оба захотят себе. На масштабировании это видно буквально. Код Тильды, который считает масштаб, обёртки не имеет: tilda-zero-scale-1.0.min.js начинается со строки function t396_scaleBlock(e){, то есть все его функции — глобальные. Так устроены 33 из 44 файлов tilda-*.js в рантайме. А сам масштаб этот файл спрашивает так:

"function" == typeof window.t396__getCurrentScaleFactor
  ? a = t396__getCurrentScaleFactor(e)
  : (a = window.tn && window.tn["ab" + e] && window.tn["ab" + e].scaleFactor)
    || (a = window.tn_scale_factor);

Функция с этим именем объявлена в другом файле Тильды, tilda-zero-1.1.min.js, и файл масштабирования берёт её через window по имени. Для мода это удобная точка входа: нужно поменять масштаб — подмени window.t396__getCurrentScaleFactor. Поэтому такие моды и работают.

Поэтому же два таких мода вместе не работают. Проверка typeof … == "function" истинна для любой функции и не умеет заметить, что имя уже занято чужим модом: масштаб посчитает та версия, которая лежит в window в момент вызова, то есть определённая последней. Порядок задают блоки на странице, а не авторы, и ошибки в консоли не будет — будет «второй мод не действует» или «масштаб не тот».

Что с этим делать покупателю:

  • Узнать имя. У каждого мода спросить продавца, какую глобальную функцию он переопределяет, — или посмотреть самому: в консоли опубликованной страницы набрать t396__getCurrentScaleFactor.toString(). Родная версия Тильды короткая, в одну строку с window.tn[e].scaleFactor. Если там чужой код — имя занято, и видно, кем.
  • Имена разные — ставьте спокойно. Два мода, которые правят разные функции, не пересекаются вообще; общее у них только то, что оба лежат на странице.
  • Имя одно — порядком блоков не лечится. Переставить блоки местами значит выбрать, какой из двух модов работает, а не заставить работать оба. Починка здесь одна: один из модов должен сохранить прежнюю функцию и вызвать её, а не выбросить. Это разница между модом, который умеет соседей, и модом, который просто занял место, — и спрашивать об этом стоит до покупки.

Чем от этого отличается общее ядро: у модов одной библиотеки на всю страницу одно глобальное имя, window.MK, и второй сниппет его не занимает, а находит — как разобрано выше. Столкнуться там нечем по устройству, а не по договорённости авторов. Обратная сторона у этого честная: чем больше эффектов собрано в одном ядре, тем больше завязано на одну библиотеку. Страховка от привязки — не в обещании, а в том же правиле >=: новое ядро подхватывает страницу со старыми сниппетами, поэтому обновление одного мода не требует переставлять остальные, а сниппет остаётся простым текстом в блоке, который вы можете удалить в любой день.

Что проверить на своей странице за две минуты

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

  • MK.version — число. Ядро на странице есть.
  • Object.keys(MK.mods) — список идентификаторов. Здесь должны быть все моды, которые вы вставили. Нет одного — ищите его блок, а не конфликт.
  • document.querySelectorAll('style[data-mk]').length — по одному на мод со своим стилем, не больше.
  • document.querySelectorAll('[data-mk-mod-041]').length — сколько секций взял мод. Ноль означает, что класс не на той секции или не сохранён.
  • строки [ModKod] в консоли — упавший мод или плохой селектор, с идентификатором.

Если на странице есть чужой мод, к этому добавляется одна проверка — не занято ли глобальное имя. Для масштабирования в Zero Block: t396__getCurrentScaleFactor.toString(). Короткая функция в одну строку — родная Тильды, никто её не подменял. Длинная или незнакомая — имя занято модом, и если таких модов два, работает только один.

И один взгляд на вес: вкладка Network, строка самого документа, колонки Size и Content — сколько приехало и сколько распаковалось. Это и есть настоящая цена всех ваших блоков с кодом, включая чужие.

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

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

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