Ленивая загрузка изображений в галерее на JavaScript

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

В основе такого решения лежит IntersectionObserver: он сообщает браузеру, какие изображения нужно подготовить к показу. Никакие сторонние библиотеки не требуются, а галерея остается обычной HTML-страницей с CSS и небольшим скриптом. Ниже разобраны подготовка разметки, наблюдение за прокруткой, подстановка файлов, адаптация под мобильные устройства и доступность.

Подготовка HTML-разметки

Для каждого снимка используйте элемент figure с изображением и подписью. В атрибуте data-src храните основной файл, а в data-srcset — варианты для разных плотностей экрана. Значение sizes подсказывает браузеру, какую ширину будет занимать картинка. До загрузки src содержит легкую встроенную заглушку, которая сохраняет место и пропорции.

<section class="gallery" aria-label="Фотографии">
  <figure class="gallery__item">
    <img
      src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=="
      data-src="images/forest-1200.jpg"
      data-srcset="images/forest-600.jpg 600w, images/forest-1200.jpg 1200w"
      sizes="(max-width: 640px) 100vw, 50vw"
      width="1200"
      height="800"
      alt="Сосновая тропа в утреннем тумане"
    >
    <figcaption>Лесная тропа</figcaption>
  </figure>
</section>

Атрибуты width и height задают пропорции заранее и защищают макет от накопления сдвигов, измеряемых показателем CLS, при появлении фотографий. Ссылка на локальный файл нужна только в атрибуте: после запуска скрипта браузер заменит ее реальным адресом. Для остальных карточек повторите структуру, не меняя классы и логику.

Наблюдение за областью просмотра

IntersectionObserver работает не с координатами прокрутки вручную, а с видимостью элементов. Наблюдатель создается для всех картинок с data-src. Когда прямоугольник пересекает расширенную область вокруг экрана, можно заранее начать запрос, не ожидая полного появления снимка в кадре. Для плавной загрузки достаточно порога 0.01.

Параметр rootMargin со значением 300px расширяет зону просмотра на 300 пикселей вверх и вниз, поэтому тяжелый файл начинает грузиться чуть раньше. Порог threshold означает минимальную долю видимой части. После пересечения наблюдатель отключается для элемента через unobserve, чтобы не отправлять повторные события и удерживать ненужные ссылки.

Подстановка источников и обработка ошибок

Функция loadImage сначала назначает обработчики событий load и error, затем переносит значения из data-srcset в srcset и устанавливает реальный src. Класс is-loaded запускает плавное появление, а is-error позволяет оформить состояние сбоя. События с опцией once не накапливают лишние подписчики. Скрипт разместите в конце body или подключите с defer.

const images = document.querySelectorAll("img[data-src]");

function loadImage(image) {
  const source = image.dataset.src;
  if (!source) return;

  image.addEventListener(
    "load",
    () => image.classList.add("is-loaded"),
    { once: true }
  );
  image.addEventListener(
    "error",
    () => image.classList.add("is-error"),
    { once: true }
  );

  if (image.dataset.srcset) {
    image.srcset = image.dataset.srcset;
  }
  image.src = source;
}

if ("IntersectionObserver" in window) {
  const observer = new IntersectionObserver((entries) => {
    entries.forEach((entry) => {
      if (!entry.isIntersecting) return;
      loadImage(entry.target);
      observer.unobserve(entry.target);
    });
  }, { rootMargin: "300px 0px", threshold: 0.01 });

  images.forEach((image) => observer.observe(image));
} else {
  images.forEach(loadImage);
}

Если файл недоступен, браузер не должен останавливать обработку остальных картинок. Класс is-error позволяет оформить сбой нейтральной рамкой, не подменяя снимок случайным изображением. URL и подписи из внешних источников проверяйте и экранируйте перед вставкой в DOM, чтобы галерея не стала источником нежелательного кода.

Адаптация галереи под разные экраны

Для адаптивной галереи задайте grid или flex и укажите минимальную ширину карточки. Изображения сделайте блочными, ширину задайте в процентах, а высоту оставьте автоматической, чтобы сохранить пропорции. Свойство object-fit: cover подходит для единообразных миниатюр, но требует одинаковой высоты контейнера. Атрибуты размеров при этом остаются важными.

.gallery { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem; }
.gallery img { display: block; width: 100%; height: auto; opacity: .55; transition: opacity .25s; background: #eee; }
.gallery img.is-loaded { opacity: 1; }
.gallery img.is-error { opacity: 1; outline: 2px solid #b33; }

Не отправляйте полноразмерный файл каждому посетителю: srcset и sizes позволяют выбрать подходящий вариант для телефона, планшета или широкого монитора. При изменении ширины окна браузер пересчитывает выбор, опираясь на реальные размеры сетки. Проверяйте результат в режиме устройства и на медленной сети, чтобы заметить лишнюю загрузку. Сжатие в WebP или AVIF дополнительно снижает вес файлов.

Доступность и полноценное взаимодействие

Каждая фотография должна иметь краткий alt, описывающий сцену, а не повторяющий подпись. Для декоративных изображений укажите пустую строку alt; если картинка важна для смысла, альтернативный текст должен передавать этот смысл. Элемент figure и figcaption помогают связать визуальный ряд с текстовым описанием.

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

Отказоустойчивость и отключение JavaScript

Прогрессивное улучшение подразумевает, что страница остается полезной без JavaScript. Для этого предусмотрите блок noscript с обычными изображениями и заполните его ссылками на те же файлы. Если браузер не поддерживает IntersectionObserver, скрипт загрузит все файлы сразу, а не оставит пустые места. Проверка этого сценария важна для части поисковых роботов и редких клиентов.

Отдельный сценарий — отключенные анимации. В режиме prefers-reduced-motion отключите переход opacity, но оставьте видимыми итоговые изображения. Не используйте тяжелые эффекты размытия как постоянную замену загрузчику: они расходуют ресурсы графического процессора. Плавность здесь вторична, поэтому корректное состояние важнее анимации.

Проверка скорости, качества и приватности

Проверяйте страницу через локальный сервер, а не открытием файла, чтобы корректно работали кэш, сетевые запросы и инструменты разработчика. В панели Network включите медленное соединение и ограничение CPU, затем прокрутите каталог. Должны загружаться подходящие файлы без дублей, а изображения из соседних экранов — заранее. Замеры Lighthouse покажут влияние на LCP и CLS.

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

Если первое изображение является главным элементом первого экрана, его лучше загрузить сразу, указав обычный src, loading="eager" и fetchpriority="high". Отложенная загрузка особенно полезна для каталога, расположенного ниже первого экрана. Такой подход сокращает время появления главного элемента и улучшает показатель LCP.

Примените схему на своей странице

Для дополнительных сведений о WordPress, браузерах и оптимизации сайта посмотрите дополнительные материалы. Там можно найти смежные разборы инструментов и настройки, полезные при работе с изображениями, кэшем и сайтами на разных CMS.

Соберите тестовую страницу с двадцатью снимками разного размера, добавьте подписи и запустите скрипт. Прокрутите страницу до конца, проверьте Network, переключитесь на мобильный профиль и отключите JavaScript. Сравните скорость, число запросов и отсутствие пустых областей, затем перенесите готовую схему в свой проект.

Иконка пользователя в серых тонах

Настройка Windows

Доступ к скрытым параметрам конфигурации, отключение телеметрии, работа с диспетчером задач и тонкая настройка меню «Пуск» в стиле Windows XP для Windows 7.

Иконка браузера в серо-синих тонах

Веб-браузеры

Скрытые страницы конфигурации в Firefox, Chrome и Opera, быстрый текстовый редактор прямо в браузере, эффект Lightbox JS для изображений и настройка внешнего вида Firefox.

Иконка папки в нейтральных серых тонах

WordPress и блоги

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

Изометрический 3D-рендер: светлый веб-сайт с серо-голубым заголовком, парящий над чистой белой поверхностью Изометрический 3D-рендер: тёмно-серая иконка социального букмаркинга на светлом фоне

Обратная связь

Есть вопрос или предложение по темам блога? Напишите — автор на связи.