Перейти к содержимому

4. Image Optimization

Изображения — это 60-80% веса большинства веб-страниц. Оптимизация картинок — самое быстрое ROI в веб-производительности.

ФорматСжатиеПрозрачностьАнимацияПоддержка
JPEGLossy99%
PNGLossless99%
WebPLossy/Lossless97%
AVIFLossy90%
SVGVector99%

Правило: WebP на 30% легче JPEG, AVIF на 50% легче JPEG при том же качестве.

<picture>
<!-- AVIF для поддерживающих браузеров -->
<source srcset="/hero.avif" type="image/avif">
<!-- WebP как fallback -->
<source srcset="/hero.webp" type="image/webp">
<!-- JPEG как последний fallback -->
<img src="/hero.jpg" alt="Hero image" width="800" height="600">
</picture>
<!-- Разные размеры для разных экранов -->
<img
src="/photo-800.jpg"
srcset="
/photo-400.jpg 400w,
/photo-800.jpg 800w,
/photo-1200.jpg 1200w,
/photo-2400.jpg 2400w
"
sizes="
(max-width: 600px) 100vw,
(max-width: 1200px) 50vw,
800px
"
alt="Responsive photo"
width="800"
height="600"
>
<!-- Нативный lazy loading (поддержка 92%+) -->
<img src="photo.jpg" loading="lazy" alt="Photo">
<!-- Не ленивая загрузка для первого экрана! -->
<img src="hero.jpg" loading="eager" fetchpriority="high" alt="Hero">
/* ✅ Адаптивные фоновые изображения */
.hero {
background-image: url('/hero-800.jpg');
}
@media (min-width: 800px) {
.hero {
background-image: url('/hero-1600.jpg');
}
}
/* ✅ WebP с fallback через @supports */
@supports (background-image: url('/test.webp')) {
.hero {
background-image: url('/hero.webp');
}
}
import Image from 'next/image';
// ✅ Автоматическая оптимизация: WebP, lazy, sizing
function Hero() {
return (
<Image
src="/hero.jpg"
alt="Hero"
width={800}
height={600}
priority // Для LCP элемента
quality={85} // Качество 0-100
placeholder="blur" // Blur placeholder
/>
);
}
// ✅ Fill mode для адаптивных контейнеров
function Cover() {
return (
<div style={{ position: 'relative', aspectRatio: '16/9' }}>
<Image
src="/cover.jpg"
alt="Cover"
fill
sizes="(max-width: 768px) 100vw, 50vw"
style={{ objectFit: 'cover' }}
/>
</div>
);
}
const sharp = require('sharp');
// Конвертация и сжатие
await sharp('input.jpg')
.resize(800, 600, { fit: 'inside', withoutEnlargement: true })
.webp({ quality: 85 })
.toFile('output.webp');
// Создание нескольких размеров
const sizes = [400, 800, 1200, 2400];
for (const width of sizes) {
await sharp('hero.jpg')
.resize(width)
.webp({ quality: 85 })
.toFile(`hero-${width}.webp`);
}
Окно терминала
npx @squoosh/cli --webp '{"quality":85}' *.jpg
npx @squoosh/cli --avif '{"cqLevel":33}' *.jpg
Окно терминала
# Конвертация в WebP
convert input.jpg -quality 85 output.webp
# Resize + optimize
convert input.jpg -resize 800x600> -strip -quality 85 output.jpg
# Batch conversion
mogrify -format webp -quality 85 *.jpg
/* ✅ Предотвращаем CLS — всегда задаём размеры */
.image-container {
aspect-ratio: 16 / 9;
overflow: hidden;
background: #f0f0f0; /* Placeholder */
}
.image-container img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
}
/* ✅ LQIP (Low Quality Image Placeholder) */
.image-with-lqip {
background-image: url('data:image/jpeg;base64,...'); /* tiny 10px version */
background-size: cover;
filter: blur(10px);
transition: filter 0.3s;
}
.image-with-lqip.loaded {
filter: none;
}
/* Один файл = один запрос */
.icon { width: 32px; height: 32px; background-image: url('/sprite.png'); }
.icon-home { background-position: 0 0; }
.icon-user { background-position: -32px 0; }
.icon-settings { background-position: -64px 0; }

Лучшее решение для иконок — SVG sprite:

<svg style="display: none">
<symbol id="icon-home" viewBox="0 0 24 24">
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
</symbol>
</svg>
<!-- Использование -->
<svg class="icon"><use href="#icon-home"/></svg>