loading lazy

loading="lazy" -- нативный HTML-атрибут для ленивой загрузки изображений и iframe. Браузер откладывает загрузку ресурса до момента, когда он приближается к viewport.

Зачем нужно

Средняя веб-страница содержит десятки изображений. Без lazy loading браузер загружает все сразу при открытии страницы, даже те, что внизу за пределами экрана. Это замедляет начальную загрузку, тратит трафик и ухудшает Core Web Vitals (LCP, FCP). loading="lazy" решает это одним атрибутом.

Где используется

  • Длинные страницы с множеством изображений (блоги, каталоги)
  • Карточки товаров в интернет-магазинах
  • Встраивание iframe (YouTube, карты)
  • Бесконечный скролл (infinite scroll)

Предпосылки

Базовый синтаксис

<!-- Ленивая загрузка изображения -->
<img
  src="product.jpg"
  alt="Товар"
  width="400"
  height="300"
  loading="lazy"
>

<!-- Ленивая загрузка iframe -->
<iframe
  src="https://www.youtube.com/embed/dQw4w9WgXcQ"
  width="560"
  height="315"
  loading="lazy"
  title="Видео"
></iframe>

Значения атрибута loading

<!-- lazy: загрузить когда приблизится к viewport -->
<img src="photo.jpg" loading="lazy" alt="Фото">

<!-- eager: загрузить немедленно (поведение по умолчанию) -->
<img src="hero.jpg" loading="eager" alt="Герой">

<!-- По умолчанию (если не указано) — eager -->
<img src="photo.jpg" alt="Фото">

fetchpriority -- приоритет загрузки

<!-- Высокий приоритет: LCP-изображение (hero) -->
<img
  src="hero-banner.jpg"
  alt="Главный баннер"
  fetchpriority="high"
  width="1200"
  height="600"
>

<!-- Низкий приоритет: некритичное изображение -->
<img
  src="decoration.jpg"
  alt=""
  fetchpriority="low"
  loading="lazy"
>

<!-- auto (по умолчанию): браузер решает сам -->
<img src="photo.jpg" alt="Фото" fetchpriority="auto">

Комбинация loading + fetchpriority

<!-- Hero-изображение: загрузить первым, с высоким приоритетом -->
<img
  src="hero.jpg"
  alt="Главный баннер"
  loading="eager"
  fetchpriority="high"
  width="1200"
  height="600"
>

<!-- Изображения ниже fold: lazy + auto -->
<img
  src="feature-1.jpg"
  alt="Функция 1"
  loading="lazy"
  width="400"
  height="300"
>

<!-- Декоративное изображение: lazy + low -->
<img
  src="pattern.jpg"
  alt=""
  loading="lazy"
  fetchpriority="low"
>

Правила: когда НЕ использовать lazy

<!-- НЕ ставь lazy на LCP-изображение (hero, banner) -->
<!-- Плохо: -->
<img src="hero.jpg" loading="lazy" alt="Hero">

<!-- Хорошо: -->
<img src="hero.jpg" fetchpriority="high" alt="Hero">

<!-- НЕ ставь lazy на изображения above the fold -->
<!-- (те, что видны без прокрутки) -->

<!-- НЕ ставь lazy на все подряд -->
<!-- Первые 2-3 изображения -- eager (или без атрибута) -->

Правило: loading="lazy" только для изображений below the fold (ниже видимой области).

Как браузер определяет момент загрузки

Viewport (видимая область)
┌─────────────────────────┐
│  img (eager) -- загружен │
│  img (eager) -- загружен │
└─────────────────────────┘
↕ ~1250px (threshold)
┌─────────────────────────┐
│  img (lazy) -- загружается│ ← приблизился к viewport
└─────────────────────────┘

┌─────────────────────────┐
│  img (lazy) -- ждёт      │ ← далеко от viewport
└─────────────────────────┘

Threshold (порог) зависит от:

  • Скорости соединения (4G → раньше, 2G → позже)
  • Браузера (каждый определяет свой threshold)
  • Обычно ~1250px до viewport

Обязательно: width и height

<!-- Без width/height: layout shift при загрузке (CLS) -->
<img src="photo.jpg" loading="lazy" alt="Фото">
<!-- Браузер не знает размер → место не зарезервировано → сдвиг контента -->

<!-- С width/height: место зарезервировано -->
<img
  src="photo.jpg"
  loading="lazy"
  alt="Фото"
  width="400"
  height="300"
>
<!-- Браузер вычисляет aspect ratio → резервирует место → нет сдвига -->
/* CSS aspect-ratio как альтернатива */
img {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
}

Lazy loading с picture и srcset

<picture>
  <source type="image/avif" srcset="photo.avif">
  <source type="image/webp" srcset="photo.webp">
  <img
    src="photo.jpg"
    alt="Фото"
    loading="lazy"
    width="800"
    height="600"
  >
  <!-- loading="lazy" на <img> внутри <picture> -->
</picture>

<!-- srcset + lazy -->
<img
  src="photo-800.jpg"
  srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
  sizes="(max-width: 600px) 100vw, 50vw"
  loading="lazy"
  alt="Фото"
  width="800"
  height="600"
>

Intersection Observer (JavaScript fallback)

Для более тонкого контроля или поддержки старых браузеров:

// Кастомная lazy loading через Intersection Observer
const lazyImages = document.querySelectorAll('img[data-src]');

const observer = new IntersectionObserver(
  (entries) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        const img = entry.target;
        img.src = img.dataset.src;
        if (img.dataset.srcset) {
          img.srcset = img.dataset.srcset;
        }
        img.removeAttribute('data-src');
        observer.unobserve(img);
      }
    });
  },
  { rootMargin: '200px' } // Начать загрузку за 200px до viewport
);

lazyImages.forEach((img) => observer.observe(img));
<!-- HTML для JS-подхода -->
<img
  data-src="photo.jpg"
  data-srcset="photo-400.jpg 400w, photo-800.jpg 800w"
  src="placeholder.svg"
  alt="Фото"
  width="400"
  height="300"
>

Частые ошибки

Ошибка Проблема Решение
loading="lazy" на LCP Замедляет главное изображение fetchpriority="high" вместо lazy
Нет width/height Layout shift (плохой CLS) Указать размеры
Lazy на все изображения Above-the-fold загружаются поздно Lazy только для below-the-fold
loading="lazy" на CSS background-image Атрибут не работает для CSS Intersection Observer
Нет alt Недоступно для SR Описательный alt

Практика

  1. Добавь loading="lazy" ко всем изображениям ниже первого экрана
  2. Убедись, что hero-изображение имеет fetchpriority="high"
  3. Проверь CLS: все lazy-изображения должны иметь width и height
  4. Открой DevTools → Network → отфильтруй Img → прокрути страницу и наблюдай загрузку
  5. Сравни LCP с lazy и без него на hero-изображении

Связанные темы

Ресурсы