Skip to main content

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

Адаптивні зображення дають браузеру змогу самостійно обрати потрібний файл для кожного пристрою, враховуючи ширину вікна, щільність пікселів і підтримку форматів.

Теорія

TL;DR

  • srcset + sizes: перелічуєш доступні файли з їхніми розмірами, кажеш браузеру скільки місця займе зображення на екрані — він рахує і обирає сам.
  • picture: ти пишеш явні media queries, браузер точно їх виконує. Ти контролюєш, яке зображення завантажується на кожному breakpoint.
  • Одне зображення різних розмірів → srcset. Різні кадри або формати для різних пристроїв → picture.
  • Телефон 375px з 2x екраном потребує файл 750px, а не 375px. Браузер розраховує це через sizes.
  • Аналогія: srcset — меню з порціями, де офіціант сам обирає під твої потреби. picture — коли ти вказуєш пальцем на конкретну страву.

Швидкий приклад

html
<!-- Браузер обирає зображення залежно від вікна і щільності пікселів --> <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

html
<!-- Неправильно: браузер вважає що зображення займає 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: Змішування дескрипторів ширини і щільності пікселів

html
<!-- Неправильно: браузер ігнорує весь 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

html
<!-- Неправильно: 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 для простого масштабування

html
<!-- Надмірно складно для завдання, яке вирішує 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 для товарної сітки

html
<!-- Повна ширина на мобільному, половина на планшеті, третина на десктопі --> <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

html
<!-- 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, виграє.

Перемикання форматів без зміни кадру

html
<!-- Той самий кадр, просто 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 -->

Цей патерн — стандартний спосіб подавати сучасні формати з безпечним запасним варіантом. Серверна логіка не потрібна.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?