Изображения в вёрстке: formats, srcset и ленивая загрузка на практике
Одна и та же картинка на 4K-мониторе и на смартфоне не должна весить одинаково. Разбираем, как отдавать нужный размер в нужном формате и не грузить то, что за пределами экрана.

Картинка-герой на 2000×1200 пикселей в PNG весит около 2,5 МБ. Тот же кадр в AVIF при визуально неотличимом качестве — 180–250 КБ. На мобильном экране шириной 400 CSS-пикселей браузеру и вовсе хватит версии на 800 пикселей по ширине. Если вы отдаёте всем один файл, то одновременно платите за перерасход трафика, проваленный LCP и низкие оценки в PageSpeed. Разберём три инструмента, которые решают это без JavaScript-костылей: современные форматы, атрибут srcset и нативную ленивую загрузку.
Задача звучит просто — «показать картинку», но у неё три независимых оси: какой формат, какого размера и когда именно грузить. Очевидное решение — один <img src> с большим файлом — проваливает все три. Ниже — что выигрываешь и чем платишь на каждой оси.
Форматы: чем платить за вес
По состоянию на конец 2024 года актуальный выбор — четыре формата. JPEG и PNG понимают все браузеры без исключений. WebP поддерживается везде, кроме совсем древних движков. AVIF даёт лучшее сжатие, но поддержка чуть уже и кодирование заметно медленнее.
| Формат | Прозрачность | Типичный вес* | Поддержка | Когда брать |
|---|---|---|---|---|
| JPEG | Нет | 100% | Все браузеры | Фолбэк, фотографии без прозрачности |
| PNG | Да | 150–300% | Все браузеры | Логотипы, скриншоты, резкие края |
| WebP | Да | 60–70% | Практически везде | Универсальная замена JPEG и PNG |
| AVIF | Да | 40–55% | Chrome, Firefox, Safari 16.4+ | Тяжёлые hero-изображения, где вес критичен |
*Относительно того же кадра в JPEG при сравнимом качестве. Цифры ориентировочные и зависят от контента: на плоских градиентах AVIF выигрывает сильнее, на шумных фотографиях — меньше.
Отдавать разные форматы разным браузерам умеет элемент <picture>. Браузер идёт по <source> сверху вниз и берёт первый, чей type он понимает. Последний <img> — обязательный фолбэк.
<picture>
<source srcset="hero.avif" type="image/avif">
<source srcset="hero.webp" type="image/webp">
<img src="hero.jpg" alt="Вид на горный хребет на рассвете"
width="1600" height="900">
</picture>
Атрибутыwidthиheightна<img>— не про размер на экране, а про пропорции. Браузер резервирует место заранее и не дёргает вёрстку, когда картинка догрузится. Без них вы получаете скачок CLS — и штраф в Core Web Vitals.
srcset и sizes: один тег — несколько файлов
Форматы решают «в чём», но не «какого размера». Retina-экран смартфона имеет плотность 2–3 физических пикселя на CSS-пиксель. Значит блок шириной 400 CSS-пикселей на таком экране требует картинку 800–1200 физических пикселей, а на обычном десктопе — 400. Отдавать всем максимальную версию — переплата трафиком.
Здесь работает пара srcset + sizes. Есть два синтаксиса.
Дескриптор x — для фиксированного размера
Если картинка всегда рендерится одним и тем же CSS-размером (иконка, аватар), различаются только версии под плотность:
<img src="avatar.jpg"
srcset="avatar.jpg 1x, avatar@2x.jpg 2x, avatar@3x.jpg 3x"
width="48" height="48"
alt="Аватар пользователя">
Браузер сам выберет файл под плотность экрана. На обычном мониторе загрузится avatar.jpg, на Retina — avatar@2x.jpg.
Дескриптор w — для отзывчивого размера
Если картинка занимает разную долю экрана на разных брейкпоинтах, укажите реальную ширину каждого файла через дескриптор w, а в sizes — какую CSS-ширину картинка займёт при разных условиях. Браузер сам посчитает, какой файл ему выгоднее скачать, учитывая и sizes, и плотность экрана.
<img src="photo-800.jpg"
srcset="photo-400.jpg 400w,
photo-800.jpg 800w,
photo-1200.jpg 1200w,
photo-1600.jpg 1600w"
sizes="(max-width: 600px) 100vw,
(max-width: 1000px) 50vw,
33vw"
width="1600" height="1067"
alt="Рабочее место фотографа">
Читается так: на экранах до 600px картинка занимает всю ширину вьюпорта, до 1000px — половину, дальше — треть. sizes — это подсказка, а не команда: браузер вправе взять версию крупнее, если она уже в кеше. Ключевая ошибка новичков — забыть sizes при использовании w: тогда браузер считает, что картинка на всю ширину, и грузит лишнее.
srcset и <picture> комбинируются: атрибут ставится на каждый <source>, чтобы отдавать и нужный формат, и нужный размер одновременно.
Ленивая загрузка: не грузить то, что не видно
Третья ось — время. Изображения ниже сгиба тянуть при первой отрисовке незачем: они конкурируют за канал с тем, что пользователь видит прямо сейчас. Раньше для этого писали JavaScript на IntersectionObserver. Сейчас есть нативный атрибут:
<img src="gallery-item.jpg"
loading="lazy"
width="600" height="400"
alt="Фото из галереи">
loading="lazy" откладывает загрузку, пока картинка не приблизится к вьюпорту. Поддержка — во всех актуальных браузерах. Значение loading="eager" (по умолчанию) грузит сразу.
Критично: не вешайте lazy на изображение над сгибом, особенно на LCP-элемент — hero-баннер. Ленивая загрузка отложит его старт, и метрика Largest Contentful Paint просядет вместо того, чтобы улучшиться. Первому экрану наоборот полезна подсказка приоритета:
<img src="hero.jpg"
fetchpriority="high"
width="1600" height="900"
alt="Главный баннер">
Когда что выбирать
- Иконка или аватар фиксированного размера —
srcsetс дескрипторомx, безsizes. - Контентная картинка, меняющая размер по брейкпоинтам —
srcsetсwплюсsizes. - Нужна прозрачность и резкие края (лого, UI) — PNG в фолбэке, WebP/AVIF в
<source>. - Hero над сгибом — без
lazy, сfetchpriority="high", размеров 3–4 подsrcset. - Всё, что ниже сгиба —
loading="lazy"обязательно.
Что ломается на практике
Три ошибки встречаются чаще остальных.
- Нет
width/height— вёрстка прыгает при догрузке, растёт CLS. Лечится указанием исходных пропорций даже при отзывчивой ширине; за реальный размер отвечает CSS (max-width: 100%; height: auto;). - Один
srcsetбез разных файлов — люди прописываютsrcset="photo.jpg 400w, photo.jpg 800w", забыв сгенерировать варианты. Толку ноль: файл один. Нужен реальный пайплайн ресайза (на сборке, в CDN или на бэкенде). - AVIF без фолбэка — если положить только AVIF в обычный
<img src>, в старом Safari или встроенном браузере приложения картинка не покажется. Всегда держите JPEG/PNG в последнем<img>внутри<picture>.
Что делать дальше
Сгенерируйте варианты размеров автоматически, а не руками: sharp для Node, Pillow для Python, ImageMagick в CI, либо ресайз на лету через CDN (Cloudflare Images, imgproxy). После внедрения замерьте LCP и CLS в Lighthouse или PageSpeed Insights до и после — на реальном мобильном профиле, а не на десктопе. Смотрите на поле «Properly size images» в отчёте: оно прямо покажет, где браузер тянет картинку крупнее, чем нужно. Отдельная тема — плейсхолдеры (blur-up, dominant color) для плавного появления; здесь их не разбираем.
Редактор изображений: Убрать лишнее, заменить фон, добавить объект, расширить кадр — на вашем изображении.
Спросить за 20 ₽Источники: MDN: Responsive images, web.dev: Optimize LCP
Частые вопросы
А если у меня старый браузер не понимает AVIF?
srcset не работает — грузится всегда самый большой файл. Почему?
Нужно ли ставить loading="lazy" на все изображения подряд?
Чем отличаются дескрипторы x и w в srcset?
Стоит ли полностью отказываться от JPEG в пользу AVIF?
Работает ли всё это без JavaScript?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.