Атрибуты 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 пикселей:

  1. Пропорции на экране против пропорций файла. Для каждой картинки без object-fit сравнить ширину/высоту отрисованного элемента с naturalWidth/naturalHeight. Расходятся — картинка вытянута (первый баг).
  2. Атрибуты против файла. Сравнить width/height в разметке с naturalWidth/naturalHeight. Расходятся пропорции — после загрузки будет сдвиг (второй баг).
  3. Геометрия до и после правки. Снять размеры всех картинок на старой и новой версии и сравнить. Так видно, что правка задела только то, что должна.
// проверка 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.

· полезно?
АЧ
Алексей Чикун веб-разработчик, команда TRIOHEAD

Делаю сайты и сервисы целиком: от вёрстки до сервера. Пишу о том, что проверил на своих проектах.

Комментарии

// загружаю комментарии…

Комментарии публикуются после проверки. Ссылки и HTML не принимаются.