Ленивая загрузка изображений в галерее на 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. Сравните скорость, число запросов и отсутствие пустых областей, затем перенесите готовую схему в свой проект.