Атрибуты width и height у картинки — самая скучная оптимизация, о которой пишут все. И всё равно на половине сайтов их нет. Без них страдают сразу две вещи: вёрстка прыгает, пока картинки грузятся, а браузер качает картинки ниже сгиба раньше времени — прямо в окно первой отрисовки. Но и поставить размеры «как-нибудь» тоже нельзя: мы получили два боевых бага ровно в тот день, когда их расставили.
Это двадцать первая часть цикла «PageSpeed без мифов», первая в разделе про картинки.
Зачем размеры: две причины
1. Сдвиги вёрстки
Пока картинка не загрузилась, браузер не знает её высоту и отводит под неё ноль. Картинка пришла — всё, что ниже, съезжает вниз. PageSpeed считает такие сдвиги метрикой CLS. На одном сайте картинки без размеров давали CLS 0,33, 0,29 и 0,19 на трёх типах страниц — всё красное. После того как размеры появились в разметке, — ноль на всех трёх.
2. Картинки ниже сгиба попадают в окно отрисовки
loading="lazy" говорит браузеру: не качай, пока картинка далеко от экрана. Но «далеко» браузер оценивает по вёрстке, а без размеров все картинки при первом расчёте имеют нулевую высоту — вся страница «сжата» к первому экрану. Браузер решает, что картинки рядом, и качает их сразу. Ленивая загрузка молча превращается в обычную.
Размеры не обязаны совпадать с размером показа. Браузеру нужны пропорции: width="1600" height="900" у картинки, которую CSS растягивает на ширину колонки, отведут правильную высоту 16:9 при любой ширине — если выполнено условие из следующего раздела.
Баг первый: картинки вытянулись втрое
Атрибуты width и height — это стили с самым низким приоритетом. Если CSS компонента задаёт картинке только ширину (width: 100%), высота остаётся из атрибута. Картинка с height="494" будет высотой 494 пикселя при любой ширине.
У нас это прожило на рабочем сайте двое суток: карточки каталога стали 337×494 вместо 337×189 на компьютере и 291×494 вместо 291×163 на телефоне. Глазами заметили не сразу: картинки с object-fit не искажались, просто карточки стали втрое выше.
Лечение — одна строка в базовых стилях, рядом с привычным max-width:
img {
max-width: 100%;
height: auto; /* иначе высота приезжает из атрибута height */
}
Если у компонента своя высота картинки, его правило специфичнее и ничего не ломается. Проверьте, есть ли эта строка в вашей теме: max-width: 100% есть почти везде, height: auto рядом с ним — далеко не всегда.
Баг второй: размеры коробки вместо размеров файла
Миниатюры часто режутся «вписыванием в коробку»: заказали 610×494 без кадрирования — получили файл 610×343, потому что исходник шире. Если подставить в атрибуты размеры коробки, браузер отведёт место по чужим пропорциям, а после загрузки картинки переложит вёрстку. На товарной странице такая картинка — крупнейший элемент первого экрана, так что сдвиг бьёт сразу и по CLS, и по LCP.
Правильно — ставить пропорции настоящего файла. В WordPress они есть у вложения:
// размеры реального файла миниатюры, а не заказанной коробки
[$src, $w, $h] = wp_get_attachment_image_src($id, 'product-medium');
printf('<img src="%s" width="%d" height="%d" alt="" loading="lazy">',
esc_url($src), $w, $h);
Если миниатюры режет плагин или своя функция, вписывание считается в несколько строк: взять пропорции исходника и уменьшить до коробки. Там, где миниатюры кадрируются точно в заказанный размер, файл и так равен коробке — проблемы нет.
Как проверить, не листая весь сайт
Глазами такие баги ищутся плохо: картинка «нормальная», просто карточка выше, или вёрстка дёргается на долю секунды. Надёжнее три коротких проверки скриптом в браузере, на двух ширинах — 1440 и 390 пикселей:
- Пропорции на экране против пропорций файла. Для каждой картинки без
object-fitсравнить ширину/высоту отрисованного элемента сnaturalWidth/naturalHeight. Расходятся — картинка вытянута (первый баг). - Атрибуты против файла. Сравнить
width/heightв разметке сnaturalWidth/naturalHeight. Расходятся пропорции — после загрузки будет сдвиг (второй баг). - Геометрия до и после правки. Снять размеры всех картинок на старой и новой версии и сравнить. Так видно, что правка задела только то, что должна.
// проверка 1 и 2 в консоли браузера: какие картинки вытянуты или с неверными атрибутами
[...document.images].filter(i => i.naturalWidth && getComputedStyle(i).objectFit === 'fill').map(i => {
const r = i.getBoundingClientRect();
const shown = r.width / r.height, file = i.naturalWidth / i.naturalHeight;
const attr = i.getAttribute('width') / i.getAttribute('height');
return { src: i.currentSrc.split('/').pop(), stretched: Math.abs(shown / file - 1) > 0.03,
wrongAttrs: !!attr && Math.abs(attr / file - 1) > 0.03 };
}).filter(x => x.stretched || x.wrongAttrs)
Самая частая ошибка — проверить только главную. Размеры теряются в конкретных компонентах: карточках каталога, превью статей, галереях. Проверяйте каждый тип страницы.
А если картинка выводится фоном через CSS?
У фона нет ни атрибутов размеров, ни ленивой загрузки, ни приоритета — и браузер узнаёт о нём поздно. Для заметных картинок фон почти всегда проигрывает обычному <img> с object-fit: cover. Об этом — отдельная часть цикла.
Помогает ли aspect-ratio в CSS вместо атрибутов?
Да, если пропорции у всех картинок компонента одинаковые (например, все карточки 16:9): aspect-ratio на контейнере резервирует место так же. Но атрибуты работают и без CSS, и браузер учитывает их при раннем расчёте — их стоит ставить в любом случае.
Вёрстка прыгает при загрузке?
Пришлите адрес — проверю картинки на всех типах страниц и скажу, где теряются размеры и сколько это стоит в CLS.
// загружаю комментарии…