Фильтр на Тильде без новых страниц собираю обычно за один вечер: размечаю карточки атрибутами, вешаю JS-обработчик на клики по кнопкам фильтра и скрываю ненужные блоки через CSS-класс. Клиенты почти всегда сначала просят «сделать страницу под каждую категорию», и я объясняю, почему такой подход портит SEO и создаёт лишнюю работу на поддержку, а потом показываю рабочий пример на 30-40 карточек, который переключается без перезагрузки. Дальше разберу всю механику: от разметки в редакторе Tilda до готового скрипта с фильтрацией по нескольким параметрам и синхронизацией с адресной строкой.
Почему страницы под каждую категорию - плохая идея
Первое, что предлагает большинство гайдов по Tilda: сделать отдельный лендинг под каждый фильтр - «/catalog-divany», «/catalog-kresla», «/catalog-stoly». На вид просто, но на практике это превращается в боль уже на второй-третьей странице. Приходится вручную дублировать шапку, футер, блоки с контактами и синхронизировать изменения ассортимента сразу в десяти местах. Я делал такой сайт для магазина мебели на 42 страницы категорий, и клиент раз в две недели присылал правки, которые нужно было вносить в каждую копию отдельно - это часы рутины, которую легко исключить.
С точки зрения SEO дублирующиеся страницы с почти одинаковым контентом размывают вес между собой и путают поисковик: он не понимает, какую из десяти «диванных» страниц показывать в выдаче по запросу «купить диван». Плюс к этому у каждой лишней страницы своя скорость загрузки и свои метатеги, которые тоже нужно поддерживать руками. Фильтр без новых страниц решает это одним скриптом на одной странице: контент физически один, меняется только то, что видно пользователю.
Что умеет фильтровать Tilda «из коробки»
Встроенный фильтр в Tilda есть только в блоках интернет-магазина (T402, T403, T‑Store) и работает исключительно с товарами из каталога Tilda: можно фильтровать по цене, категории и текстовым меткам, заданным в карточке товара. Для обычного контента - портфолио, услуг, статей, объектов недвижимости - штатного фильтра нет вообще, такие блоки собираются вручную из Zero Block или готовых T‑блоков без фильтрации.
Второе ограничение: даже в блоках магазина фильтр не умеет комбинировать критерии, которых нет в стандартной схеме товара. Если нужно фильтровать по параметру «материал» и «цвет» одновременно, а не только «цена + категория», штатных настроек уже не хватает - приходится либо городить костыли через теги, либо писать свой скрипт поверх верстки. На практике я почти всегда ухожу во второй вариант, потому что он не привязан к типу блока и работает одинаково что на карточках товаров, что на портфолио, что на списке услуг.
Разметка карточек под кастомный фильтр
Первый шаг - разметить карточки данными, по которым скрипт будет их прятать и показывать. В Zero Block у каждого элемента есть поле дополнительных настроек, куда добавляется произвольный HTML-атрибут через раздел «Ещё» -> «Атрибуты» в панели редактирования блока. Для карточки товара или услуги добавляю что-то вроде:
<div class="filter-card" data-category="divany" data-price="25000">
...содержимое карточки...
</div>
Если верстаю блок через встроенный редактор Tilda, а не через кастомный HTML, тот же результат даёт CSS-класс с закодированным значением: например, класс filter-divany вешается на обёртку карточки через поле «доп. класс» в настройках элемента, а скрипт потом ищет по этому классу. Способ с data-атрибутами удобнее, когда параметров фильтрации больше двух-трёх - не приходится городить десятки классов на одну карточку.
Кнопки самого фильтра размечаю похожим образом - каждая кнопка получает data-filter со значением категории, а кнопка «Все» - пустое значение или data-filter=“all”.
Бесплатный материал
🎁 Полезный скрипт в подарок
Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.
Без спама. Отписка в 1 клик.
JS-скрипт: фильтрация без перезагрузки страницы
Логика простая: по клику на кнопку фильтра скрипт проходит по всем карточкам, сравнивает их data-атрибут с выбранным значением и переключает видимость через CSS-класс, а не через display в inline-стиле - так проще управлять анимацией показа-скрытия. Вставляю скрипт через блок T123 (HTML-код) в конец страницы или в настройках страницы, в поле «Перед закрывающим тегом body».
document.addEventListener('DOMContentLoaded', function () {
var buttons = document.querySelectorAll('[data-filter]');
var cards = document.querySelectorAll('.filter-card');
buttons.forEach(function (btn) {
btn.addEventListener('click', function () {
var value = btn.getAttribute('data-filter');
buttons.forEach(function (b) { b.classList.remove('is-active'); });
btn.classList.add('is-active');
cards.forEach(function (card) {
var match = value === 'all' || card.getAttribute('data-category') === value;
card.classList.toggle('is-hidden', !match);
});
});
});
});
Класс is-hidden добавляю через тот же блок T123, но в CSS-часть: display: none в паре с transition для плавности, если нужна анимация. Проверяю сразу на мобильной версии - Tilda для мобильного макета часто дублирует блок с другой версткой, и если фильтровать нужно и там, класс .filter-card должен присутствовать в обеих версиях разметки, иначе скрипт отработает только на десктопе.
Фильтр сразу по нескольким параметрам
Когда нужно фильтровать не по одной категории, а сразу по нескольким признакам - например, «диваны» и «цена до 30 000» одновременно, - логику меняю на накопление активных фильтров в объекте, а не на одно значение:
var activeFilters = { category: 'all', price: 'all' };
function applyFilters() {
document.querySelectorAll('.filter-card').forEach(function (card) {
var byCategory = activeFilters.category === 'all' ||
card.getAttribute('data-category') === activeFilters.category;
var byPrice = activeFilters.price === 'all' ||
card.getAttribute('data-price-range') === activeFilters.price;
card.classList.toggle('is-hidden', !(byCategory && byPrice));
});
}
document.querySelectorAll('[data-group="category"]').forEach(function (btn) {
btn.addEventListener('click', function () {
activeFilters.category = btn.getAttribute('data-filter');
applyFilters();
});
});
document.querySelectorAll('[data-group="price"]').forEach(function (btn) {
btn.addEventListener('click', function () {
activeFilters.price = btn.getAttribute('data-filter');
applyFilters();
});
});
Для диапазонов цены удобнее заранее раскладывать товары по корзинам (data-price-range=“0-25000”, “25000-50000” и так далее), а не сравнивать числа в скрипте - так легче контролировать границы и не зависеть от того, как менеджер вбил цену в карточку. С чекбоксами вместо кнопок логика та же, только вместо одного значения в activeFilters храню массив выбранных категорий и проверяю через includes.
Фильтр и адресная строка: ссылки на готовые подборки
Без синхронизации с URL фильтр красиво работает, но ссылку на конкретную подборку не отправить - при обновлении страницы всё сбрасывается на «Все». Для рекламы и рассылок это неудобно: если ведёте трафик на «диваны до 30 000», логичнее слать на прямую ссылку с уже примененным фильтром, а не заставлять пользователя щёлкать кнопки заново.
function updateUrl() {
var params = new URLSearchParams(window.location.search);
params.set('category', activeFilters.category);
history.replaceState(null, '', '?' + params.toString());
}
window.addEventListener('DOMContentLoaded', function () {
var params = new URLSearchParams(window.location.search);
var initial = params.get('category');
if (initial) {
activeFilters.category = initial;
applyFilters();
}
});
history.replaceState не создаёт новую запись в истории браузера при каждом клике, поэтому кнопка «назад» не превращается в перебор всех фильтров подряд. Для сложных сценариев, где фильтр должен ещё и подтягивать данные из CRM или пересчитывать остатки на складе, такую интеграцию обычно делаю уже не на голом JS, а с бэкендом - это отдельная услуга по кастомной доработке Tilda, потому что там появляются запросы к API и обработка ошибок, которых в статье не покажешь в двух скриптах.
Частые проблемы при внедрении и как их обойти
Разметка и скрипт - это половина дела, вторая половина - грабли, на которые наступают почти все, кто делает такой фильтр впервые.
| Проблема | Причина | Решение |
|---|---|---|
| Фильтр не работает на мобильной версии | Tilda рендерит отдельный DOM для мобильного макета, атрибуты не продублированы | Проверять data-атрибуты в обеих версиях верстки перед публикацией |
| Карточки «моргают» при первой загрузке | Скрипт выполняется до полной отрисовки Zero Block | Вешать обработчик на событие готовности Tilda вместо обычного DOMContentLoaded |
| После фильтрации ломается сетка, остаются пустые места | display: none оставляет карточку в потоке grid/flex с некорректным подсчётом колонок | Использовать flex-wrap и не задавать карточкам жёсткую ширину в процентах от общего числа |
| Фильтр конфликтует с корзиной T‑Store | Скрипты T‑Store переинициализируют DOM после добавления товара в корзину | Инициализировать фильтр повторно на событие обновления каталога магазина |
Отдельно слежу за скоростью загрузки: если карточек больше сотни, лучше не гонять querySelectorAll по всему документу на каждый клик, а один раз сохранить список карточек в переменную при загрузке страницы, как в примерах выше - иначе на слабых мобильных фильтр начинает подтормаживать.
Когда стандартных блоков не хватает
Кастомный скрипт
от 3 000 ₽
Подробнее →Частые вопросы
Можно ли сделать фильтр на Тильде без программиста?
Простую фильтрацию по одной категории через кнопки и data-атрибуты можно собрать самому, если разобраться с блоком T123 и базовым JavaScript - шаблон из статьи выше покрывает большинство случаев. Как только добавляются несколько параметров одновременно, диапазоны цен, синхронизация с URL или связка с CRM, время на самостоятельную отладку обычно превышает стоимость заказа готового решения.
Работает ли такой фильтр на мобильной версии сайта?
Да, но верстку и data-атрибуты нужно проверять отдельно для мобильной версии - Tilda хранит десктопный и мобильный макет как разные наборы блоков, и если атрибут добавлен только в десктопной версии, на телефоне фильтр просто не найдёт карточки для сравнения.
Как фильтр повлияет на SEO, если контент скрывается через display:none
Если весь текст карточек физически присутствует в HTML-коде страницы, а скрывается только визуально через CSS, поисковики его индексируют - Google и Яндекс давно умеют рендерить JavaScript и читать финальный DOM. Проблем не будет, если не прятать контент через display:none изначально в верстке блока, тогда его вообще не отдаёт сервер - фильтр должен скрывать уже отрисованные карточки, а не подгружать их динамически без серверного рендеринга.
Сколько стоит внедрение кастомного фильтра на Tilda?
Простая фильтрация по одному параметру с базовой разметкой обходится от 3 000 ₽ - это разовая доработка существующего блока. Комплексный фильтр с несколькими критериями, синхронизацией с URL и интеграцией с CRM или остатками склада - это уже полноценная кастомная доработка, от 40 000 ₽ в зависимости от количества параметров и источников данных.