Верстка retina-ready графіки для веб-сайтів
Екрани з щільністю пікселів 2x і вище давно стали стандартом — MacBook Pro, iPhone, флагманські пристрої Android. Сайт, верстаний без врахування retina, виглядає розпливчастим на цих пристроях: логотипи "плавають", іконки втрачають чіткість, скріншоти продукту виглядають низької якості. Верстка retina-ready усуває цю проблему систематично, не жертвуючи продуктивністю.
Що робить графіку "retina-ready"
Звичайна растрова графіка PNG 100×100 px на дисплеї з devicePixelRatio: 2 розтягується до 200 фізичних пікселів. Браузер інтерполює — звідси розпливчастість. Існує кілька рішень, кожне з яких застосовується у своєму контексті:
| Метод | Коли застосовувати |
|---|---|
srcset + sizes для <img> |
Контентні зображення |
CSS image-set() |
Фонові зображення |
| SVG | Іконки, логотипи, ілюстрації |
<picture> + <source> |
Коли потрібна art direction |
| CSS-спрайти 2x | Застарілі проекти з IE11 |
SVG як перший вибір
Для елементів інтерфейсу SVG — єдино правильний підхід. Векторна графіка масштабується без втрат за будь-якого DPI. Іконки через <use xlink:href> або компонентизація через React:
// Icon.tsx — системна іконка через SVG спрайт
import { FC } from 'react';
interface IconProps {
name: string;
size?: number;
className?: string;
}
const Icon: FC<IconProps> = ({ name, size = 24, className }) => (
<svg width={size} height={size} className={className} aria-hidden="true">
<use href={`/sprites/icons.svg#${name}`} />
</svg>
);
srcset для растрових зображень
Коли SVG недоступний — фотографії, скріншоти, складні ілюстрації — використовується srcset:
<img
src="hero-800.jpg"
srcset="hero-800.jpg 1x, hero-1600.jpg 2x, hero-2400.jpg 3x"
width="800"
height="450"
alt="Головний екран програми"
loading="lazy"
decoding="async"
/>
Для адаптивних випадків з різними кадрами:
<picture>
<source
media="(min-width: 1024px)"
srcset="hero-desktop-1x.webp 1x, hero-desktop-2x.webp 2x"
type="image/webp"
/>
<source
media="(min-width: 1024px)"
srcset="hero-desktop-1x.jpg 1x, hero-desktop-2x.jpg 2x"
/>
<source
srcset="hero-mobile-1x.webp 1x, hero-mobile-2x.webp 2x"
type="image/webp"
/>
<img
src="hero-mobile-1x.jpg"
srcset="hero-mobile-1x.jpg 1x, hero-mobile-2x.jpg 2x"
width="390"
height="260"
alt="Головний екран"
/>
</picture>
Фонові зображення через CSS image-set
.hero-banner {
background-image: image-set(
url('/img/banner-1x.webp') 1x,
url('/img/banner-2x.webp') 2x
);
background-size: cover;
}
/* Fallback для старіших браузерів */
@supports not (background-image: image-set(url('') 1x)) {
.hero-banner {
background-image: url('/img/banner-2x.webp');
}
}
Ланцюг інструментів підготовки графіки
Sharp для автоматичного генерування розмірів
У production-пайплайні зображення готуються не вручну. Збирач генерує всі варіанти з вихідника:
// scripts/optimize-images.mjs
import sharp from 'sharp';
import { glob } from 'glob';
import path from 'path';
const SOURCE_DIR = 'src/assets/images';
const OUTPUT_DIR = 'public/img';
const files = await glob(`${SOURCE_DIR}/**/*.{png,jpg}`);
for (const file of files) {
const name = path.basename(file, path.extname(file));
const outDir = path.dirname(file).replace(SOURCE_DIR, OUTPUT_DIR);
// 1x WebP
await sharp(file).resize({ width: 800 }).webp({ quality: 82 })
.toFile(`${outDir}/${name}-1x.webp`);
// 2x WebP
await sharp(file).resize({ width: 1600 }).webp({ quality: 78 })
.toFile(`${outDir}/${name}-2x.webp`);
// 1x JPEG fallback
await sharp(file).resize({ width: 800 }).jpeg({ quality: 85, mozjpeg: true })
.toFile(`${outDir}/${name}-1x.jpg`);
// 2x JPEG fallback
await sharp(file).resize({ width: 1600 }).jpeg({ quality: 80, mozjpeg: true })
.toFile(`${outDir}/${name}-2x.jpg`);
}
Vite-плагін для автоматичних srcset
Для Next.js існує вбудована оптимізація через next/image. Для Vite-проектів — vite-imagetools:
// vite.config.ts
import { defineConfig } from 'vite';
import { imagetools } from 'vite-imagetools';
export default defineConfig({
plugins: [
imagetools({
defaultDirectives: new URLSearchParams({
format: 'webp;jpg',
quality: '82',
}),
}),
],
});
Використання в компоненті:
import heroSrcset from './hero.jpg?w=800;1600&format=webp&as=srcset';
<img srcSet={heroSrcset} src="/hero-fallback.jpg" alt="Hero" />
SVG-спрайт: збирання та оптимізація
SVGO видаляє непотрібні атрибути та скорочує шляхи. Спрайт збирається з окремих файлів:
// scripts/build-sprite.mjs
import { optimize } from 'svgo';
import { glob } from 'glob';
import fs from 'fs/promises';
const icons = await glob('src/assets/icons/*.svg');
let symbols = '';
for (const file of icons) {
const id = path.basename(file, '.svg');
const raw = await fs.readFile(file, 'utf8');
const { data } = optimize(raw, {
plugins: ['preset-default', { name: 'removeViewBox', active: false }],
});
// Перетворити <svg> на <symbol>
const symbol = data
.replace(/<svg([^>]*)>/, `<symbol id="${id}"$1>`)
.replace('</svg>', '</symbol>');
symbols += symbol;
}
const sprite = `<svg xmlns="http://www.w3.org/2000/svg" style="display:none">${symbols}</svg>`;
await fs.writeFile('public/sprites/icons.svg', sprite);
Тестування на retina-дисплеях
Інструменти:
- Chrome DevTools → Sensors → Device pixel ratio: встановити 2 або 3
- Firefox: через
about:config→layout.css.devPixelsPerPx - Реальні пристрої: iPhone 13+, MacBook Pro M1+, Samsung Galaxy S22+
Чек-лист перед здачею:
- Всі іконки у форматі SVG або мають 2x-версію
- Логотип у форматі SVG
- Контентні зображення використовують
srcsetз 2x - Фонові зображення використовують
image-set() - CSS не містить
background-imageз одним 1x-файлом без fallback - Шрифти — системні або web fonts, не растрові
Термін виконання
Базова retina-адаптація існуючої верстки (заміна растрових іконок на SVG, додавання srcset до зображень): 1–2 дні. Повна retina-ready верстка нового проекту з нуля включається в загальний графік верстки без значного збільшення строків — за умови що вихідні файли надані в векторі або в 2x-дозволі.







