Адаптивні зображення: picture, srcset та sizes
Адаптивні зображення дають браузеру змогу самостійно обрати потрібний файл для кожного пристрою, враховуючи ширину вікна, щільність пікселів і підтримку форматів.
Теорія
TL;DR
srcset+sizes: перелічуєш доступні файли з їхніми розмірами, кажеш браузеру скільки місця займе зображення на екрані — він рахує і обирає сам.picture: ти пишеш явні media queries, браузер точно їх виконує. Ти контролюєш, яке зображення завантажується на кожному breakpoint.- Одне зображення різних розмірів →
srcset. Різні кадри або формати для різних пристроїв →picture. - Телефон 375px з 2x екраном потребує файл 750px, а не 375px. Браузер розраховує це через
sizes. - Аналогія:
srcset— меню з порціями, де офіціант сам обирає під твої потреби.picture— коли ти вказуєш пальцем на конкретну страву.
Швидкий приклад
<!-- Браузер обирає зображення залежно від вікна і щільності пікселів -->
<img
src="photo-small.jpg"
srcset="photo-small.jpg 400w, photo-medium.jpg 800w, photo-large.jpg 1600w"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
alt="Фото продукту"
>
<!-- Телефон 375px (1x): photo-small.jpg (100vw = 375px, 400w підходить) -->
<!-- Телефон 375px (2x): photo-medium.jpg (375 x 2 = 750px потрібно, 800w) -->
<!-- Десктоп 1920px: photo-large.jpg (33vw = 640px, 1600w покриє 2x) -->Атрибут src — запасний варіант для старих браузерів, що ігнорують srcset.
Ключова різниця
srcset з дескрипторами ширини каже браузеру, які файли є і яка ширина кожного. sizes каже, скільки місця зображення займе на екрані. Браузер комбінує обидва атрибути і обирає файл для завантаження. picture працює інакше: перебирає елементи <source> по media queries і завантажує перший, що збігся, а srcset всередині кожного source обробляє варіанти роздільної здатності. srcset для масштабування. picture коли сам вміст зображення змінюється.
Коли що використовувати
srcset+sizes: фото товарів, зображення в блозі, hero-зображення, що пропорційно масштабуютьсяpicture: hero-зображення з різними кадрами на мобільному і десктопі, перемикання форматів (WebP із запасним JPEG), коли вміст зображення змінюється залежно від пристрою- Обидва разом:
pictureвирішує який кадр показати,srcsetвсередині кожного<source>вирішує яку роздільну здатність цього кадру завантажити - Один
src: SVG, іконки, дрібні декоративні зображення, що не змінюються залежно від пристрою
Порівняльна таблиця
| Аспект | srcset + sizes | Елемент picture | Один src |
|---|---|---|---|
| Використання | Одне зображення різних розмірів | Різні зображення per breakpoint | Статичне, незмінне |
| Хто вирішує? | Браузер (viewport + DPR) | Ти (через media queries) | Немає вибору |
| Економія трафіку | Висока | Висока | Немає |
| Art direction | Ні | Так | Ні |
| Перемикання форматів | Ні | Так | Ні |
| Підходить для | Фото, галереї | Різні кадри, WebP/JPEG | Іконки, SVG |
Як браузер обирає зображення
Коли браузер бачить srcset + sizes, він читає sizes, щоб дізнатись ширину відображення в CSS-пікселях, множить на pixel ratio пристрою і шукає найменший файл у srcset, що покриє потрібну кількість пікселів без пікселізації. Для picture браузер перебирає елементи <source> зверху вниз, зупиняється на першому, де збіглися media і type, потім запускає той самий алгоритм для srcset всередині цього source. Рішення кешується і не переглядається без суттєвої зміни viewport.
Важливий нюанс: браузер може враховувати кешовані файли і мережеві умови. Якщо великий файл вже є в кеші, він може використати його замість завантаження теоретично оптимального варіанта.
Типові помилки
Помилка 1: Не вказаний атрибут sizes
<!-- Неправильно: браузер вважає що зображення займає 100vw -->
<img
src="photo.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1600.jpg 1600w"
alt="Фото"
/>
<!-- Правильно: описуємо реальну ширину відображення -->
<img
src="photo.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1600.jpg 1600w"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
alt="Фото"
/>Без sizes мініатюра на десктопі 1440px викличе завантаження повнорозмірного файлу. Витрата трафіку цілком реальна.
Помилка 2: Змішування дескрипторів ширини і щільності пікселів
<!-- Неправильно: браузер ігнорує весь srcset -->
<img
src="logo.png"
srcset="logo.png 1x, logo-2x.png 2x, logo-wide.png 800w"
alt="Логотип"
/>
<!-- Правильно: один тип дескрипторів -->
<img
src="logo.png"
srcset="logo.png 1x, logo-2x.png 2x"
alt="Логотип"
/>Дескриптори ширини (400w) потребують sizes. Дескриптори щільності (2x) абсолютні і не потребують. Якщо їх змішати, браузер повернеться до src.
Помилка 3: sizes не відповідає CSS
<!-- Неправильно: CSS обмежує зображення 600px, а sizes каже 100vw -->
<style>img { width: 100%; max-width: 600px; }</style>
<img
src="photo.jpg"
srcset="photo-400.jpg 400w, photo-1200.jpg 1200w"
sizes="100vw"
alt="Фото"
/>
<!-- На десктопі 1440px: браузер завантажить photo-1200.jpg, -->
<!-- але CSS відобразить його у 600px. Трафік витрачено даремно. -->
<!-- Правильно: sizes відповідає CSS -->
<img
src="photo.jpg"
srcset="photo-400.jpg 400w, photo-1200.jpg 1200w"
sizes="(max-width: 640px) 100vw, 600px"
alt="Фото"
/>Помилка 4: picture для простого масштабування
<!-- Надмірно складно для завдання, яке вирішує srcset -->
<picture>
<source media="(max-width: 640px)" srcset="photo-small.jpg" />
<source media="(max-width: 1024px)" srcset="photo-medium.jpg" />
<img src="photo-large.jpg" alt="Фото" />
</picture>
<!-- Простіше і гнучкіше -->
<img
src="photo-small.jpg"
srcset="photo-small.jpg 400w, photo-medium.jpg 800w, photo-large.jpg 1600w"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
alt="Фото"
/>Закодовані media queries в picture перестають працювати для пристроїв поза очікуваними breakpoints. srcset дає браузеру адаптуватись до будь-якого екрану автоматично.
Помилка 5: Очікування, що телефон завантажить найменший файл
Телефон 375px з 2x екраном завантажить файл 800w, а не 400w. Математика: 375 x 2 = 750px потрібно, тому браузер обирає 800w як найменший варіант без погіршення якості. Це правильна поведінка, не баг. При генерації файлів завжди враховуй DPR.
Де зустрічається в реальних проєктах
- Next.js компонент
<Image>: генеруєsrcsetавтоматично, ти тільки вказуєшsizesі розміри - Shopify:
picture+srcsetдля мобільного кадру і перемикання на WebP - WordPress з плагінами типу Smush: автоматично додають
srcsetдо всіх завантажених зображень - Gatsby
gatsby-plugin-image: генеруєsrcsetіз розмитими (blur) заповнювачами - Cloudinary та Imgix: CDN-сервіси, що генерують URL для
srcsetбез ручного створення файлів - CSS фони:
image-set()для тієї ж ідеї в CSS (background-image: image-set(url(photo.jpg) 1x, url(photo-2x.jpg) 2x))
Питання на співбесіді
Q: Чому не можна змішувати дескриптори ширини (400w) і щільності пікселів (2x) в одному srcset?
A: Кожен тип використовує різний алгоритм вибору. Дескриптори ширини потребують sizes для розрахунку ширини відображення. Дескриптори щільності абсолютні і не використовують sizes. Комбінація створює неоднозначність, яку браузер не вирішує, тому весь srcset ігнорується і браузер завантажує src.
Q: Якщо я використовую picture з media queries, чи потрібний srcset всередині <source>?
A: Так. picture вирішує, яку версію зображення використати (art direction). srcset всередині <source> обробляє варіанти роздільної здатності для цієї версії. На телефоні 2x браузер обирає мобільний source через picture, потім srcset обирає 2x варіант цього мобільного зображення.
Q: Телефон завантажив більше зображення, ніж очікувалось. Що перевіряєш першим?
A: Device pixel ratio. Більшість сучасних телефонів мають 2x або 3x, тому браузер запитує 750 до 1125px. Відкрий Chrome DevTools, вкладку Network, відфільтруй по Img і перевір який файл з srcset реально завантажився.
Q: Що станеться, якщо жоден <source> в <picture> не збіжиться?
A: Браузер використає <img> всередині <picture> як запасний варіант. Саме тому <img> обов'язковий всередині <picture>, а не опціональний.
Q: (Рівень Senior) Дизайнер передав зображення 3000x2000px для адаптивної сторінки. Як підходиш до задачі?
A: Перше питання: вміст зображення змінюється залежно від пристрою чи тільки розмір? Якщо тільки розмір — srcset + sizes. Якщо мобільний потребує портретний кадр — picture для art direction. Далі рахую реальні ширини відображення: мобільний 100vw (~375px), планшет 50vw (~512px), десктоп 33vw (~640px). Генерую файли 1x і 2x для кожного: шість файлів. Автоматизую через CDN або інструмент збірки, не вручну. Перевіряю в DevTools, що кожен пристрій завантажує правильний файл.
Приклади
srcset з sizes для товарної сітки
<!-- Повна ширина на мобільному, половина на планшеті, третина на десктопі -->
<img
src="product-800.jpg"
srcset="
product-400.jpg 400w,
product-800.jpg 800w,
product-1600.jpg 1600w
"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
alt="Сині кросівки для бігу"
>
<!-- Телефон 375px (1x) → product-400.jpg (375px потрібно) -->
<!-- Телефон 375px (2x) → product-800.jpg (750px потрібно) -->
<!-- Десктоп 1440px (1x) → product-800.jpg (1440 x 0.33 = ~475px) -->
<!-- Десктоп 1440px (2x) → product-1600.jpg (~950px потрібно) -->Значення sizes повинні відповідати реальному CSS. Якщо сітка змінюється з трьох колонок на чотири, оновлюй і sizes.
picture для art direction і WebP
<!-- Hero: портретний кадр на мобільному, ландшафт на десктопі, WebP де підтримується -->
<picture>
<!-- Мобільний: портретний кадр, WebP -->
<source
media="(max-width: 640px)"
type="image/webp"
srcset="hero-mobile.webp 375w, hero-mobile@2x.webp 750w"
/>
<!-- Мобільний: портретний кадр, JPEG -->
<source
media="(max-width: 640px)"
srcset="hero-mobile.jpg 375w, hero-mobile@2x.jpg 750w"
/>
<!-- Десктоп: повний ландшафт, WebP -->
<source
type="image/webp"
srcset="hero-desktop.webp 1200w, hero-desktop@2x.webp 2400w"
/>
<!-- Запасний для браузерів без підтримки picture -->
<img src="hero-desktop.jpg" alt="Команда в офісі" />
</picture>
<!-- Chrome на мобільному → hero-mobile.webp (або @2x) -->
<!-- Safari на мобільному → hero-mobile.jpg -->
<!-- IE11 → hero-desktop.jpg (ігнорує picture) -->Браузер перебирає <source> по порядку. Перший, де збіглися і media, і type, виграє.
Перемикання форматів без зміни кадру
<!-- Той самий кадр, просто WebP для браузерів що підтримують -->
<picture>
<source
type="image/webp"
srcset="photo.webp 400w, photo-2x.webp 800w, photo-3x.webp 1200w"
sizes="(max-width: 640px) 100vw, 50vw"
/>
<img
src="photo.jpg"
srcset="photo.jpg 400w, photo-2x.jpg 800w, photo-3x.jpg 1200w"
sizes="(max-width: 640px) 100vw, 50vw"
alt="Пейзажне фото"
/>
</picture>
<!-- Chrome, Firefox, сучасний Edge → WebP (зазвичай на 25-35% менший за JPEG) -->
<!-- Safari 13 і старіший → JPEG через img srcset -->
<!-- IE11 → photo.jpg із img src -->Цей патерн — стандартний спосіб подавати сучасні формати з безпечним запасним варіантом. Серверна логіка не потрібна.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.