Адаптивные изображения: picture, srcset, sizes

Как отдавать каждому устройству ровно ту картинку, которая ему нужна — и не грузить лишние мегабайты.

Адаптивные изображения — это набор атрибутов (srcset, sizes) и тег <picture>, которые позволяют браузеру самому выбрать оптимальный файл картинки под экран, плотность пикселей и ширину области.

Обычный <img src="photo.jpg"> отдаёт один и тот же файл всем: и Retina-ноутбуку, и дешёвому смартфону на медленном 3G. В результате телефон качает картинку шириной 2000 пикселей, чтобы показать её в колонке шириной 320 — это потерянный трафик и медленная загрузка. Адаптивные изображения решают именно эту проблему, перекладывая выбор файла на браузер, который знает про экран всё.

Зачем это знать на практике

Картинки — обычно самая тяжёлая часть страницы по байтам. На мобильном трафике лишний мегабайт превращается в секунды ожидания и в реальные деньги пользователя. Правильный srcset экономит до 70% веса изображений на маленьких экранах, не теряя резкости на больших. Это напрямую влияет на скорость, на показатели Core Web Vitals и на позиции в поиске.

srcset по плотности пикселей

Самый простой случай — фиксированный размер картинки, но разные экраны по чёткости. Дескриптор x описывает плотность пикселей устройства:

<img src="logo.png"
     srcset="logo.png 1x, [email protected] 2x, [email protected] 3x"
     width="200" height="60" alt="Логотип">

На обычном мониторе (1x) браузер возьмёт logo.png, на Retina (2x) — вдвое более детальный [email protected]. Атрибут src остаётся запасным для совсем старых браузеров. Этот вариант хорош для логотипов и иконок, чей размер на странице не меняется.

srcset по ширине + sizes

Куда чаще картинка растягивается: на телефоне она во всю ширину, а на десктопе занимает треть экрана. Тогда плотности мало — нужно описать сами файлы по ширине дескриптором w и подсказать браузеру, какого размера будет место под картинку, через sizes:

<img src="hero-800.jpg"
     srcset="hero-400.jpg 400w,
             hero-800.jpg 800w,
             hero-1600.jpg 1600w"
     sizes="(max-width: 600px) 100vw, 50vw"
     width="800" height="500" alt="Горный пейзаж">

Читается так: «hero-400.jpg имеет ширину 400 пикселей, hero-800.jpg — 800 и так далее». А sizes говорит: «при ширине экрана до 600px картинка займёт всю ширину окна (100vw), иначе — половину (50vw)». Браузер перемножает sizes на плотность своего экрана, получает нужную ширину в пикселях и выбирает ближайший подходящий файл из srcset.

Как браузер считает выбор

Допустим, экран телефона шириной 375px с плотностью 2x. По sizes срабатывает первое условие — 100vw, то есть 375 CSS-пикселей. Умножаем на плотность 2 — нужно ≈750 реальных пикселей. Браузер берёт hero-800.jpg как ближайший сверху. На десктопе 1440px (50vw = 720 CSS-пикселей, плотность 1) он тоже возьмёт 800w. Логика всегда «не меньше требуемого», чтобы не было размытия.

picture и art direction

srcset отдаёт один и тот же кадр в разных размерах. Но иногда на мобильном нужен другой кадр: на десктопе широкая панорама, а на телефоне — вертикальный кроп с лицом крупным планом. Это называется art direction, и для него существует тег <picture>:

<picture>
  <source media="(max-width: 600px)" srcset="portrait.jpg">
  <source media="(min-width: 601px)" srcset="landscape.jpg">
  <img src="landscape.jpg" alt="Команда на встрече">
</picture>

Браузер проверяет <source> сверху вниз и берёт первый, чьё условие media истинно. Обязательный <img> в конце — это и запасной вариант, и носитель атрибута alt: именно к нему привязываются размеры и доступность.

Современные форматы: WebP и AVIF

Тот же <picture> решает вторую задачу — отдать новый формат тем, кто его понимает, и откатиться на JPEG для остальных:

<picture>
  <source type="image/avif" srcset="photo.avif">
  <source type="image/webp" srcset="photo.webp">
  <img src="photo.jpg" alt="Чашка кофе" width="600" height="400">
</picture>

Браузер выберет первый type, который умеет показать. AVIF сжимает заметно сильнее JPEG при том же качестве, WebP — золотая середина с почти всеобщей поддержкой, а photo.jpg остаётся для самых старых клиентов.

ФорматСжатиеПоддержка
JPEGбазовоеабсолютно везде
WebPна 25–35% меньшепочти все браузеры
AVIFна 50% меньшесовременные браузеры

Ленивая загрузка: loading=lazy

Картинки внизу длинной страницы не нужны, пока пользователь до них не доскроллит. Атрибут loading="lazy" откладывает их загрузку до приближения к области видимости:

<img src="gallery-12.jpg" loading="lazy"
     width="400" height="300" alt="Фото из галереи">

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

Как это работает под капотом

Алгоритм выбора источника браузер запускает ещё на этапе предзагрузочного сканирования — до построения всего дерева страницы, чтобы начать качать картинку как можно раньше. Для <img srcset sizes> он берёт ширину из sizes, домножает на плотность экрана и ищет в srcset кандидата с подходящим дескриптором w. Для <picture> логика другая: это явный выбор — первый <source>, у которого совпали media и поддерживается type, побеждает безусловно, и браузер уже не «думает» сам. Поэтому picture используют, когда вы хотите контролировать выбор (другой кадр, другой формат), а голый srcset — когда доверяете эвристике браузера.

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

  • Путать x и w. Дескриптор x — для фиксированного размера и разной плотности; w — для растягивающихся картинок и работает только в паре с sizes.
  • srcset с w без sizes. Без sizes браузер считает, что картинка во всю ширину окна, и часто качает слишком большой файл.
  • Использовать picture там, где хватит srcset. Если кадр один и меняется только размер — <picture> избыточен, достаточно srcset.
  • loading="lazy" на LCP-картинке. Главное изображение первого экрана от этого грузится позже и портит метрику скорости.
  • Нет width/height. Браузер не знает пропорций, страница дёргается при дозагрузке (layout shift).

Итоги

  • srcset с дескриптором x — для иконок и логотипов фиксированного размера на экранах разной чёткости.
  • srcset с w + sizes — для растягивающихся картинок: браузер сам выберет файл по ширине места.
  • <picture> с media — art direction: другой кадр под мобильный и десктоп.
  • <picture> с type — отдать AVIF/WebP поддерживающим, JPEG — остальным.
  • loading="lazy" экономит трафик внизу страницы, но не вешайте его на LCP-картинку; всегда указывайте width/height.
Проверьте себя
1. Когда нужен тег <picture> с атрибутом media, а не просто srcset на <img>?
AКогда нужно отдать другой КАДР (например вертикальный кроп) под разные размеры экрана
BКогда картинка не меняет размер на странице
CКогда нужно ускорить загрузку первой картинки
Dsrcset и picture полностью взаимозаменяемы
2. Что описывает дескриптор w в srcset (например hero-800.jpg 800w)?
AВес файла в килобайтах
BРеальную ширину файла-картинки в пикселях
CШирину области показа на экране
DПлотность пикселей устройства
3. Почему атрибут loading="lazy" не стоит вешать на главную картинку первого экрана?
Alazy работает только внутри <picture>
BГлавная картинка — это LCP-элемент, и ленивая загрузка замедлит её отрисовку и ухудшит метрику скорости
Clazy отключает атрибут alt
DБраузеры игнорируют lazy на JPEG