Вёрстка 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 sprite
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 вёрстка нового проекта с нуля включается в общий timeline вёрстки без значительного увеличения сроков — при условии что исходники предоставлены в векторе или в 2x-разрешении.







