Оптимизация изображений
Изображения — обычно самый тяжёлый контент на странице (50-70% трафика). Оптимизация изображений — самый простой способ ускорить сайт.
Зачем нужно
Средняя веб-страница весит ~2MB, из которых ~1MB — изображения. Неоптимизированные изображения: замедляют LCP, расходуют трафик пользователя, увеличивают время загрузки на мобильных. Правильная оптимизация может ускорить сайт в 2-3 раза.
Где используется
Все сайты с изображениями: e-commerce, блоги, лендинги, SPA, портфолио. Особенно критично для мобильных пользователей.
Предпосылки
HTML, CSS, Web Vitals, Browser rendering flow
Форматы изображений
| Формат | Сжатие | Прозрачность | Анимация | Поддержка | Для чего |
|---|---|---|---|---|---|
| JPEG | Lossy | Нет | Нет | 100% | Фото |
| PNG | Lossless | Да | Нет | 100% | Иконки, скриншоты |
| WebP | Both | Да | Да | 97% | Замена JPEG/PNG |
| AVIF | Lossy | Да | Да | 92% | Лучшее сжатие |
| SVG | Vector | Да | Да | 100% | Иконки, логотипы |
| GIF | Lossless | 1-bit | Да | 100% | Простые анимации |
WebP — основной выбор
JPEG 100KB → WebP ~70KB (на 25-35% меньше при том же качестве)
PNG 200KB → WebP ~120KB (на 30-40% меньше)
AVIF — будущее
JPEG 100KB → AVIF ~50KB (на 50% меньше!)
Но: медленнее кодируется, поддержка ~92%
Когда какой формат
| Контент | Формат | Почему |
|---|---|---|
| Фото | WebP / AVIF | Лучшее сжатие |
| Иконки, логотипы | SVG | Масштабируется, маленький размер |
| Скриншоты с текстом | WebP (lossless) / PNG | Чёткий текст |
| Простые иконки | SVG / CSS | Не нужен растр |
| Фоновые паттерны | SVG / CSS gradients | Бесконечное масштабирование |
Сжатие
Инструменты
# Sharp (Node.js) — самый популярный
npm install sharp
# Squoosh CLI
npm install @squoosh/cli
# imagemin
npm install imagemin imagemin-webp imagemin-avif
Sharp — конвертация и сжатие
const sharp = require('sharp');
// JPEG → WebP
await sharp('photo.jpg')
.webp({ quality: 80 }) // 80% качества
.resize(800, 600) // Ресайз
.toFile('photo.webp');
// PNG → AVIF
await sharp('screenshot.png')
.avif({ quality: 50 })
.toFile('screenshot.avif');
// Batch-обработка
const fs = require('fs');
const path = require('path');
const inputDir = './images';
const outputDir = './optimized';
fs.readdirSync(inputDir).forEach(async (file) => {
if (file.match(/\.(jpg|jpeg|png)$/i)) {
await sharp(path.join(inputDir, file))
.webp({ quality: 80 })
.toFile(path.join(outputDir, file.replace(/\.\w+$/, '.webp')));
}
});
Responsive Images (srcset / sizes)
<!-- ПЛОХО: одно изображение для всех экранов -->
<img src="hero-2000px.jpg" alt="Hero">
<!-- Мобильный телефон загружает 2000px изображение! -->
<!-- ХОРОШО: браузер выбирает подходящий размер -->
<img
src="hero-800.jpg"
srcset="
hero-400.jpg 400w,
hero-800.jpg 800w,
hero-1200.jpg 1200w,
hero-2000.jpg 2000w"
sizes="
(max-width: 600px) 100vw,
(max-width: 1200px) 50vw,
800px"
alt="Hero image">
Как это работает
srcset — список доступных размеров (400w = 400px ширина)
sizes — подсказка, сколько места изображение займёт на экране
Браузер выбирает оптимальный вариант, учитывая:
- Ширину viewport
- Плотность пикселей (Retina 2x, 3x)
- sizes подсказку
Picture element — разные форматы
<picture>
<!-- Браузер выберет первый поддерживаемый формат -->
<source srcset="photo.avif" type="image/avif">
<source srcset="photo.webp" type="image/webp">
<img src="photo.jpg" alt="Фото" width="800" height="600">
</picture>
<!-- Разные изображения для разных экранов -->
<picture>
<source media="(max-width: 600px)" srcset="photo-mobile.webp">
<source media="(max-width: 1200px)" srcset="photo-tablet.webp">
<img src="photo-desktop.webp" alt="Фото" width="1200" height="800">
</picture>
Lazy Loading
<!-- Нативный lazy loading -->
<img src="photo.jpg" loading="lazy" alt="Фото" width="400" height="300">
<!-- НЕ делай lazy loading для LCP-изображения! -->
<img src="hero.jpg" loading="eager" alt="Hero" fetchpriority="high">
// Intersection Observer для продвинутого lazy loading
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
}, { rootMargin: '200px' }); // Начать загрузку за 200px до появления
document.querySelectorAll('img[data-src]').forEach(img => {
observer.observe(img);
});
CDN для изображений
<!-- Cloudinary — обработка на лету -->
<img src="https://res.cloudinary.com/demo/image/upload/w_400,f_auto,q_auto/photo.jpg">
<!--
w_400 — ширина 400px
f_auto — автоформат (WebP для Chrome, JPEG для Safari)
q_auto — автокачество
-->
<!-- imgix -->
<img src="https://myapp.imgix.net/photo.jpg?w=400&auto=format,compress">
Preload для LCP-изображения
<head>
<!-- Предзагрузка LCP-изображения — критически важно! -->
<link rel="preload" as="image" href="hero.webp" type="image/webp">
<!-- С srcset -->
<link rel="preload" as="image"
imagesrcset="hero-400.webp 400w, hero-800.webp 800w"
imagesizes="100vw">
</head>
SVG-оптимизация
# SVGO — оптимизатор SVG
npx svgo icon.svg -o icon.min.svg
# Результат: удаляет метаданные, комментарии, лишние атрибуты
# Обычно -30-50% размера
<!-- Инлайн SVG для мелких иконок (экономим HTTP-запрос) -->
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
</svg>
Частые ошибки
1. Огромные изображения без ресайза
<!-- ПЛОХО: 4000x3000 фото для превью 200x150 -->
<img src="original-4000x3000.jpg" style="width: 200px;">
<!-- ХОРОШО: серверный ресайз + srcset -->
<img src="thumb-200x150.webp" srcset="thumb-400.webp 2x" style="width: 200px;">
2. Нет width/height → CLS
<!-- ПЛОХО: без размеров — скачок при загрузке (CLS) -->
<img src="photo.jpg">
<!-- ХОРОШО: размеры заданы — место зарезервировано -->
<img src="photo.jpg" width="800" height="600">
3. Lazy loading для LCP
<!-- ПЛОХО: LCP-изображение загружается лениво -->
<img src="hero.jpg" loading="lazy">
<!-- ХОРОШО: eager + preload -->
<img src="hero.jpg" loading="eager" fetchpriority="high">
4. Только один формат
<!-- ПЛОХО: только JPEG -->
<img src="photo.jpg">
<!-- ХОРОШО: progressive enhancement -->
<picture>
<source srcset="photo.avif" type="image/avif">
<source srcset="photo.webp" type="image/webp">
<img src="photo.jpg" alt="Фото">
</picture>
Практика
- Конвертируй 5 JPEG-изображений в WebP через Sharp — сравни размеры
- Добавь
srcsetиsizesдля hero-изображения - Используй
<picture>для AVIF → WebP → JPEG fallback - Настрой lazy loading для всех изображений ниже fold
- Найди LCP-элемент на своём сайте и добавь
<link rel="preload">