Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Оптимизация изображений для веба: WebP, AVIF, srcset

Изображения в вебе: AVIF/WebP, адаптивные srcset и ленивая загрузка

Как отдавать картинки быстро: форматы AVIF/WebP против JPEG, picture и srcset под экраны, lazy loading, размеры против CLS и конвейер обработки.

Прямой ответ

Изображения — половина байтов среднего сайта, и три приёма сокращают их кратно. (1) Современные форматы: AVIF (AV1-based — на 30–50% меньше WebP при том же качестве — поддержка браузерами 90%+), WebP (25–35% меньше JPEG — поддержка универсальна) — отдаются через <picture> с фолбэком (<picture><source srcset="hero.avif" type="image/avif"><source srcset="hero.webp" type="image/webp"><img src="hero.jpg" ...></picture> — старый браузер возьмёт JPEG, современный — лучшее; либо Accept-переговоры на сервере/CDN — авто-конвертация одна URL — Cloudflare Polish/imgproxy-класс); JPEG/PNG — для несовместимых остатков и специфических кейсов (PNG — прозрачность без потерь, но тяжелее WebP-lossless). (2) Адаптивность: один файл «на все экраны» — расточительство (десктоп-герой 2560px/1.8 МБ на мобильном 375px — мобильный качает в 6 раз лишнее): srcset+sizes (браузер выбирает вариант под вьюпорт и плотность пикселей — srcset="hero-480.webp 480w, hero-960.webp 960w, hero-1920.webp 1920w" sizes="(max-width: 600px) 100vw, 50vw" — варианты 480/960/1920 покрывают нужды; генерируются при загрузке/сборке — конвейер ниже); 3–4 варианта достаточно (не «каждые 100px»). (3) Ленивость и приоритеты: loading="lazy" для всего ниже сгиба (не грузится до скролла — экономия трафика/LCP не мешает) и никогда для hero/LCP-элемента (ленивый герой грузится поздно — LCP деградирует — для главного loading="eager" fetchpriority="high" + preload — web vitals); decoding="async" (декодирование не блокирует рендер). И обязательное — width/height (или aspect-ratio) у каждого img (место зарезервировано до загрузки — CLS-прыжки eliminated — CLS-механика). Качество кодирования — не максимум: q=75–82 для фото (визуально неотличимо, байты вдвое — «качество 100» в веб-контексте — деньги пользователей впустую); прогрессивность/blurhash-плейсхолдеры — UX-уровень по желанию. Серверная часть: генерация вариантов — при загрузке (ETL картинки — Pillow-конвейер ниже) или on-the-fly сервисом (imgproxy/thumbor — URL-параметры, кеш на CDN — хранение + CDN-кеш вариантов — гибкость против инфраструктуры).

Форматы: что где применять

Сравнение практики: AVIF — лучший размер (фото −50% против JPEG), медленнее кодируется (CPU ×3–5 против WebP — при генерации не критично (однажды), при on-the-fly — кеш обязателен), поддержка 90%+ (Safari 16+/Chrome/Firefox — фолбэк нужен); WebP — универсальный выбор «по умолчанию» (поддержка ~все живые браузеры, размер −30% против JPEG, кодируется быстро, прозрачность+анимация); JPEG — совместимость-фолбэк и редкие кейсы (печать/внешние системы); PNG — скриншоты/графика с резкими границами и прозрачностью (но WebP-lossless обычно меньше — PNG оправдан для pixel-perfect графики/экспорта); SVG — иконки/логотипы/графика (вектор масштабируется бесконечно, байты мизерны — иконочные шрифты меркнут перед SVG-спрайтами — CSP-учёт inline-SVG — политика); анимация — не GIF (гигантский/палитра 256 — анимированный WebP/AVIF или видео-тег — «GIF на 15 МБ» — преступление против трафика). Мотивация качества: фото — q78–82 AVIF/WebP (A/B «качество против размера» — визуально порог ниже ожидаемого — тест SSIM/глазами на своей аудитории); текст-на-скриншотах — качество выше (артефакты на тексте заметнее — q85+); прозрачность с градиентами — WebP-lossless/PNG против артефактов lossy. Инструменты конвертации: Pillow (img.save("x.avif", quality=78) — AVIF-поддержка в свежих версиях), cwebp/avifenc CLI (конвейер в скриптах/CI — сборка ассетов), squoosh-класс для ручной проверки («до/после» глазами перед выбором q).

Адаптивная отдача: srcset, sizes, picture

Механика srcset (браузер решает сам): w-дескрипторы (фактическая ширина варианта) + sizes (сколько места займёт изображение в layout на разных вьюпортах — CSS-медиаусловия) — браузер вычисляет нужную плотность и качает оптимальный вариант (один раз решив — не перекачивает при ресайзе — поведение знать для тестов); типичный набор — 480/800/1200/1920 (покрытие мобильных/планшетов/десктопов/ретина — больше 4–5 вариантов — отдача падает (каждый — отдельный кеш-ключ на CDN — кеш)); picture — когда формат/арт-директорство важнее плотности (разные форматы source/type — как выше; кроп под мобильный — <source media="(max-width:600px)" srcset="hero-mobile.webp"> — «десктопная композиция нечитаема на 375px» — отдельный кроп законен); фоновые CSS-изображения — image-set() (аналог srcset для background — поддержка выросла; либо <img> вместо фона — семантика+lazy+srcset из коробки — «всё фоном» — легаси-приём); dpr-дескрипторы (srcset с 1x/2x — для фиксированных размеров (иконки) — w-подход для fluid); проверка — DevTools Network (какой вариант качается на мобильной эмуляции — «десктоп-вариант на 375px» — sizes написан неверно — частая ошибка — sizes без медиаусловий = 100vw всегда — половина пользы потеряна).

Ленивость, размеры и конвейер обработки

Lazy/приоритеты: loading="lazy" — браузерная ленивость (порог срабатывания свой у браузеров — сотни px до вьюпорта — для критичного по скроллу — JS-наблюдатели точнее, но встроенного хватает в 95% случаев); LCP-изображение — eager+fetchpriority=high (+preload в head — загрузка с первых байтов HTML — приоритеты); выше/ниже сгиба — граница по аналитике (Lighthouse «lazy-load кандидатов» подскажет); decoding="async" — всем не-LCP (главный поток свободен). Размеры/CLS: width+height атрибуты (браузер резервирует место с учётом aspect-ratio — сдвиги при загрузке исчезают; CSS max-width:100%;height:auto — отзывчивость при сохранении пропорций — стандартная пара); динамические вставки — aspect-ratio CSS (контейнер держит пропорции до картинки — рекламные/виджет-слоты — CLS-дисциплина). Конвейер обработки (Pillow-пример для загрузки пользователями): загрузка → валидация (тип/размер/безопасность — pillow-проверка «реально изображение» против переименованного файла — загрузки-безопасность — image-троллинг реальным кодом) → нормализация (EXIF-orientation поворот, strip метаданных — геолокация в EXIF — приватность — удалять; ICC-профиль — сохранить или конвертировать sRGB) → генерация вариантов (4 ширины × AVIF+WebP+JPEG-фолбэк — 12 файлов на изображение — очередь (Celery) — CPU-тяжело, не в веб-запросе) → загрузка в S3/MinIO (хранилище) с версионированными путями (hash в имени — кеш вечный — заголовки) → CDN-кеш; on-the-fly альтернатива (imgproxy: URL /w:800/avif/1/hero.jpg — вариант на лету + CDN-кеш результата) — для непредсказуемых размеров (CMS-редакторы кропают произвольно) — инфраструктурная компонента против гибкости — выбор по продукту.

Практический сценарий: медиа-каталог — изображения ×5 легче

Исходник: каталог 40k фото (загрузки поставщиков — JPEG 2000–4000px, 0.8–3 МБ каждое) — страницы категорий по 2.5 МБ изображений, LCP карточки 4.1 с, мобильные жалуются на трафик; хранилище — MinIO, отдача напрямую nginx'ом. Конвейер (неделя работы): (1) batch-обработка существующих (Celery-задача, 40k× — ночь): Pillow → 4 ширины (480/800/1200/1920) × AVIF q78 + WebP q80 + JPEG q82-фолбэк, EXIF strip, sRGB — исходники сохранены (редактирование/реэкспорт возможны — хранилище-версии); объём каталога: 46 ГБ исходников → 180 ГБ вариантов, но средний отдаваемый файл: 1.4 МБ → 68 КБ (AVIF-480/800 — мобильные; 1200 — десктоп-сетка) — трафик отдачи −95%; (2) шаблоны карточек — <picture> с AVIF/WebP/JPEG + srcset/sizes (сетка карточек — sizes="(max-width:600px) 50vw, 25vw" — 4 варианта, не 12 — сетке хватает 480/800); (3) hero карточки товара — eager+fetchpriority+preload, остальное — lazy+async, width/height везде (CLS категорий 0.18→0.02 — «прыгающая сетка» при загрузке исчезла); (4) новые загрузки — тот же конвейер автоматически (очередь загрузки → варианты за 3–8 с — UI показывает blurhash-плейсхолдер до готовности — UX-ожидания — статус готовности через SSE); (5) CDN (подключение) — кеш вариантов (хеш в пути — immutable год) — hit ratio 99%. Замеры: LCP карточки — 4.1→1.6 с (полевые p75 — web vitals), страница категории — 2.5 МБ→0.35 МБ, мобильный трафик сессии — −78% (аналитика по устройствам); качество — A/B-опрос выборки 30 изображений «до/после» командой — визуально неотличимо на рабочих размерах (q78 AVIF подтверждён); находка-бонус: EXIF-геолокация в 12% пользовательских загрузок (адреса съёмки уезжали публично — strip закрыл приватностный инцидент, который никто не искал); стоимость: ночной batch (одноразово, 6 часов CPU — процессы — multiprocessing на 8 ядрах), +140 ГБ хранилища (дешевле сэкономленного трафика в разы).

FAQ

AVIF против WebP — что выбрать в 2026? Оба, каскадом: AVIF первым source (−30–50% против JPEG — поддержка 90%+ — большинство трафика возьмёт его), WebP вторым (остальные современные), JPEG фолбэком (древние/специфичные); «только WebP» — недополучение сжатия для 90% аудитории; «только AVIF без фолбэков» — потеря старых браузеров (корпоративные/legacy-устройства живы в b2b); генерация тройки — конвейерная рутина (стоимость — CPU при загрузке, не в рантайме).

Нужны ли srcset, если сайт только мобильный/только десктопный? Да, но меньше: только-мобильный — 2 варианта (375px и 750px ретина — одна ширина layout, две плотности); только-десктопный — 2–3 (1200/1920/2560 — мониторы разные); «один размер на всех» оправдан для фиксированных элементов (иконки/аватарки 48px — один файл ×2 плотности); проверка фактического — Network-панель на разных эмуляциях (какой вариант реально качается — srcset без нужды не мешает, но и не работает «сам» — sizes должен соответствовать layout).

Как быть с пользовательскими загрузками произвольного размера (CMS/маркетплейс)? Конвейер обязателен (не отдавать оригиналы никогда — пользователь зальёт 8 МБ фото 6000px — все посетители заплатят): нормализация при приёме (максимальная ширина 2560 — ресайз сразу, EXIF strip, валидация типа), варианты — очередь при загрузке (или on-the-fly imgproxy+CDN-кеш — непредсказуемые кропы редакторов), дедупликация по контент-хешу (одно фото загружено 40 продавцами — один объект, 40 ссылок — S3-хеш); «отдаём как залили, оптимизация потом» — «потом» не наступает, а трафик платится сейчас.

Стоит ли конвертировать изображения в рантайме на каждый запрос? Почти никогда: кодирование AVIF — сотни мс CPU на изображение (1000 запросов в секунду × конвертация — сервер сгорает на перекодировке одного и того же); правильно: генерация один раз (при загрузке/сборке — варианты статичны) или on-the-fly с кешем (первый запрос конвертирует, CDN/локальный кеш отдаёт остальным — imgproxy+CDN — эффективный паттерн); Accept-переговоры на CDN (Cloudflare-автоконвертация с кешем) — то же «один раз»; рантайм без кеша — антипаттерн, нагружающий CPU впустую (профилирование покажет конвертацию в топе — типичная находка аудита).

Источники: MDN (image types), web.dev (responsive images), документация Pillow — ссылки в шапке.