Tilda · 7 мин чтения

Lazy load в Тильде: как включить отложенную загрузку изображений

Открываю очередной отчёт Google PageSpeed Insights по сайту клиента на Тильде и вижу знакомую картину: за первую секунду загрузки браузер тянет полтора десятка изображений из блока-портфолио T598, хотя пользователь долистает до него в лучшем случае через минуту. Lazy load в Тильде убирает эту проблему - картинки вне экрана начинают грузиться только тогда, когда до них реально долистали. Разберу, что Тильда откладывает сама, а что нужно включать через атрибут loading или собственный скрипт, и почему на мобильном трафике это часто закрывает половину проблем с LCP.

Зачем включать отложенную загрузку изображений в Тильде

Средняя страница на Тильде с несколькими блоками-портфолио и галереей весит 3-5 МБ, и картинки в этом весе - 60-70%. Если все они грузятся одновременно с первым экраном, браузер конкурирует за канал между hero-изображением, которое реально нужно пользователю прямо сейчас, и десятком карточек снизу страницы, которые он увидит не факт что вообще. На 3G и слабом Wi-Fi в кафе это добавляет секунды к Largest Contentful Paint, а с 2021 года Core Web Vitals - часть ранжирующих факторов Google, так что задержка бьёт не только по конверсии, но и по позициям.

На практике для интернет-магазина на Тильде с каталогом в 400+ товаров я видел LCP в районе 4,2 секунды на мобильной сети - из-за того, что карточки товаров грузились скопом, без разбора, что видно, а что нет. После включения отложенной загрузки и сжатия изображений в WebP метрика ушла в диапазон 1,8-2,1 секунды на том же наборе устройств в тесте Lighthouse. Разница ощутима и заметна в отчётах Search Console уже через 2-3 недели после переиндексации.

Что в Тильде уже работает лениво по умолчанию

В каждый проект на Тильде подключается собственный скрипт платформы, который навешивает атрибут data-original на изображения в стандартных блоках и подгружает их по мере прокрутки - это касается T‑блоков с обычными виджетами картинок: обложки, карточки товаров, галереи, слайдеры из конструктора. Разработчику здесь ничего включать не нужно, механизм работает из коробки на всех тарифах.

Проблема начинается там, где верстальщик выходит за пределы стандартных блоков. Zero Block с произвольной HTML-версткой, кастомные секции через T123 с вставленным вручную кодом, фоновые изображения, заданные через CSS background-image, iframe-виджеты и видео-обложки - всё это платформа не трогает, потому что для неё это просто произвольный код, а не её собственный виджет. Если в Zero Block верстается страница с десятком картинок на HTML и CSS без единого нативного тильдовского виджета, вся отложенная загрузка ложится на плечи того, кто пишет код.

Как вручную добавить loading=“lazy” для картинок в HTML-блоках и Zero Block

Самый быстрый способ для кастомных изображений в Zero Block или в HTML-блоке T123 - атрибут loading="lazy" в теге img. Поддержка есть во всех современных браузерах, дополнительных библиотек не требуется.

<img src="https://static.tildacdn.com/tild1234/photo.jpg"
     alt="Фото товара"
     width="800"
     height="600"
     loading="lazy">

Атрибуты width и height здесь не для галочки - без них браузер не знает заранее, сколько места займёт картинка, и при подгрузке страница дёргается, а это уже штраф по Cumulative Layout Shift. На своих проектах ставлю их всегда, даже когда картинка растягивается по ширине контейнера через CSS - реальные пропорции браузер всё равно берёт из атрибутов для расчёта места под элемент.

Важный момент: изображения первого экрана - hero-баннер, логотип, картинку, которая формирует LCP - лайзи-лоадом не трогаю вообще. Если повесить loading="lazy" на элемент, который и так должен отрисоваться первым, браузер сначала проверит его видимость, а это лишняя миллисекунда там, где économить нечего, зато потерять на LCP можно легко.

Кастомный скрипт на IntersectionObserver для сложных случаев

Атрибута loading="lazy" достаточно для статичной верстки, но не спасает, когда картинки добавляются в DOM динамически - например, в JS-слайдере с бесконечной прокруткой или в галерее, которая подгружает карточки через AJAX. Браузер в такой ситуации не всегда успевает применить нативный лайзи-лоад корректно, особенно в старых версиях Safari на iOS. Здесь пишу собственный обработчик на IntersectionObserver, который сам решает, когда подменить data-src на реальный src.

document.addEventListener('DOMContentLoaded', function () {
  const images = document.querySelectorAll('img[data-src]');

  const observer = new IntersectionObserver(function (entries, obs) {
    entries.forEach(function (entry) {
      if (entry.isIntersecting) {
        const img = entry.target;
        img.src = img.dataset.src;
        img.removeAttribute('data-src');
        obs.unobserve(img);
      }
    });
  }, {
    rootMargin: '300px 0px',
    threshold: 0.01
  });

  images.forEach(function (img) {
    observer.observe(img);
  });
});

rootMargin: '300px' здесь ключевой параметр - он заставляет браузер подгружать картинку заранее, за 300 пикселей до того, как она реально появится на экране, чтобы пользователь не видел пустой прямоугольник в момент прокрутки. Для медленных сетей ставлю значение побольше, 500-600px, для быстрого десктопного трафика хватает и 150-200px.

Если не хочется писать такой скрипт с нуля под каждый проект, у меня в библиотеке готовых скриптов для Тильды есть отработанные варианты под разные типы блоков - с учётом капризов Zero Block и Safari.

Бесплатный материал

🎁 Полезный скрипт в подарок

Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.

Без спама. Отписка в 1 клик.

Отложенная загрузка фоновых изображений и видео-обложек

Фоновые картинки, заданные через CSS-свойство background-image, атрибутом loading не управляются вообще - браузер не видит их как отдельный ресурс до момента, пока не начнёт применять стили к элементу, и лайзи-логика тут работает иначе. Рабочая схема та же самая с IntersectionObserver, только вместо src подменяется инлайн-стиль:

const bgObserver = new IntersectionObserver(function (entries, obs) {
  entries.forEach(function (entry) {
    if (entry.isIntersecting) {
      const el = entry.target;
      el.style.backgroundImage = 'url(' + el.dataset.bg + ')';
      obs.unobserve(el);
    }
  });
}, { rootMargin: '300px 0px' });

document.querySelectorAll('[data-bg]').forEach(function (el) {
  bgObserver.observe(el);
});

С видео-обложками (блок T410 и подобные) похожая история: вставленный напрямую YouTube-плеер тянет за собой скрипты и обложку в высоком разрешении ещё до взаимодействия пользователя. Практичнее показывать статичную превью-картинку с наложенной кнопкой play и подгружать сам iframe только по клику - так называемый facade-паттерн. На одном лендинге с шестью видео-блоками эта замена сама по себе снизила вес первой загрузки страницы почти на 2 МБ.

Ошибки, из-за которых lazy load не даёт эффекта

Чаще всего отложенная загрузка не срабатывает или создаёт новые проблемы по одним и тем же причинам:

Ошибка Последствие Как чинить
Нет width/height у img Прыжки вёрстки при подгрузке (рост CLS) Всегда прописывать реальные пропорции в атрибутах
rootMargin слишком маленький Пользователь видит серый placeholder долю секунды при быстрой прокрутке Увеличить запас до 300-500px
Lazy load повешен на hero-изображение Рост LCP вместо его снижения Первый экран грузить без задержки, обычным src
Картинка внутри display:none IntersectionObserver не срабатывает, элемент никогда не считается видимым Использовать visibility или переносить логику показа на класс, а не на display

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

Как проверить, что lazy load реально работает

Проверяю на вкладке Network в DevTools с включённым троттлингом (Slow 3G или Fast 3G) - при правильной настройке в момент открытия страницы должны подгружаться только изображения первого экрана, остальные появляются в списке запросов по мере прокрутки вниз. Второй способ - вкладка Coverage, показывает, сколько ресурсов реально не используется на старте.

Отдельно смотрю аудит «Defer offscreen images» в Google PageSpeed Insights - если после настройки предупреждение исчезло или экономия упала до нуля, значит атрибуты и скрипт отработали как надо. На одном проекте с галереей на 60+ фотографий после внедрения кастомного скрипта на IntersectionObserver показатель LCP в мобильном отчёте снизился с 5,1 до 2,3 секунды, а вес первой загрузки страницы - с 4,8 МБ до 1,1 МБ.

Если задача не ограничивается парой картинок, а нужна доработка целого блока или интеграция с CRM, эквайрингом или СДЭК рядом с оптимизацией картинок, у меня фиксированный прайс: точечная правка скрипта - от 3 000 ₽, комплексная доработка с интеграциями - от 40 000 ₽. Полный список услуг по Тильде смотрите на странице услуг.

Когда стандартных блоков не хватает

Кастомный скрипт

от 3 000 ₽

Подробнее →

Частые вопросы

Тормозит ли lazy load первый экран сайта на Тильде?

При правильной настройке - нет. Проблема возникает, только когда лайзи-лоад по ошибке применяют к hero-изображению или другому элементу, формирующему LCP. Первый экран всегда грузится напрямую, без атрибута loading и без задержки через observer.

Нужен ли отдельный скрипт, если в Тильде уже подключён встроенный lazy load?

Зависит от того, где лежат картинки. Стандартные виджеты платформы - карточки, галереи из конструктора - отложенную загрузку получают автоматически. Zero Block, кастомный HTML и фоновые изображения через CSS этим механизмом не покрываются, для них нужен либо атрибут loading=“lazy”, либо собственный скрипт на IntersectionObserver.

Совместим ли lazy load с индексацией картинок в поиске?

Да, Google рендерит страницу с выполнением JavaScript и корректно обрабатывает как атрибут loading=“lazy”, так и подмену src через IntersectionObserver. Важно сохранять атрибут alt на всех изображениях и не убирать реальный src полностью из разметки без валидного data-атрибута - иначе часть краулеров, которые не исполняют JS, картинку просто не увидит.

Сколько стоит настройка lazy load под конкретный блок на Тильде?

Точечная доработка - например, добавить атрибуты и небольшой скрипт под один блок с галереей - обойдётся от 3 000 ₽. Если нужна более широкая оптимизация всей страницы с фоновыми изображениями, видео-блоками и проверкой Core Web Vitals, оцениваю такую задачу от 40 000 ₽ в зависимости от количества блоков и глубины интеграции.

Есть задача?

Обсудим в мессенджере

Расскажите, что нужно сделать — отвечу в течение 4 часов в рабочее время. Первая консультация бесплатно.

Самозанятый Калинкин Н. А. · работаю с физлицами и юрлицами

Продолжая пользование настоящим сайтом Вы выражаете своё согласие на обработку Ваших персональных данных (файлов куки) с использованием Yandex.Metrika.
Понятно