Оптимизация изображений в HTML

Итак, у вас готова ваша прекрасная веб-страница. Вы добавляете фоновое изображение и…

.hero {
  /* 🚩 */
  background-image: url('/image.png');
}

ПОГОДИТЕ!

А вы знаете, что это может ухудшить производительность? Причем по нескольким причинам.

Почему как правило стоит избегать background-image в CSS

Оптимальный размер изображения

В настоящее время существует огромное количество размеров экранов и разрешений. И нет никакой причины выдавать всем посетителям сайта один и тот же файл с изображением. Если у вас не SVG, конечно.

Вы можете сказать: «О! Медиа-запросы! Я вручную укажу диапазон размеров размеров экрана и изображений» —

/* 🚩 */
.hero { background-image: url('/image.png'); }
@media only screen and (min-width: 768px) {
  .hero { background-image: url('/image-768.png'); }
}
@media only screen and (min-width: 1268px) {
  .hero { background-image: url('/image-1268.png'); }
}

Ну, это проблематично. Таким образом вы учитываете только размер экрана, но не разрешение. А еще это многословно.

Вы можете возразить: «А я знаю классный трюк для этого: можно использовать image-set для указания разных размеров изображения для разных разрешений» —

/* 🚩 */
.hero {
  background-image: image-set(url("/image-1x.png") 1x, url("/image-2x.png") 2x);
}

И вы будете правы, это дает некоторые преимущества. Но, вообще говоря, нам нужно учитывать и размер экрана, и разрешение.

Поэтому мы могли бы написать какой-нибудь раздутый CSS, сочетающий медиа-запросы и image-set, но это только усложняет задачу. Такой подход предполагает, что нам нужно точно знать размер изображения для каждого экрана, даже если макет сайта изменится со временем.

И все это не поддерживает многие важные вещи. Например, ленивую загрузку, новейшие форматы для поддерживаемых браузеров, подсказки приоритетов, асинхронное декодирование и многое другое…

И в довершение всего, у нас также есть проблема с цепочками запросов:

Уход от цепочек запросов

При использовании тега изображения ссылка на src находится прямо в HTML. Таким образом, браузер может получить исходный HTML, просканировать его на наличие изображений и сразу же начать получать высокоприоритетные изображения.

В случае загрузки изображений в CSS браузер должен просканировать ваш HTML, получить CSS, затем найти, что к элементу применен background-image, и только после всего этого получить изображение. Это займет больше времени.

Здесь мы предполагаем, что, вы используете внешние таблицы стилей (ссылка rel="styleshset"), как это делает большинство, а не встроенные стили.

Да, кое-что можно сделать. Например, использовать встроенные правила CSS, предварительную загрузку изображений и предварительное подключение к источникам. Но далее мы рассмотрим дополнительные преимущества тега img в HTML. К сожалению, их нельзя получить с помощью background-image в CSS.

Когда следует использовать фоновое изображение

Прежде чем перейти к обсуждению наиболее оптимальной загрузки изображений, стоит остановиться на исключении из общего правила.

Допустим, у вас есть очень маленькое изображение, которым вы хотите замостить фон. Такого эффекта легко достичь с помощью background-repeat. А вот тег img в этом случае вам не поможет (насколько я знаю).

Но если размер вашего изображения превышает, скажем, 50px, я бы рекомендовал не задавать его в CSS и использовать тег img.

Оптимальная загрузка изображений

Поплакавшись о трудностях использования background-image в CSS, давайте поговорим о реальных решениях.

В современном HTML тег img предоставляет нам ряд полезных атрибутов для оптимальной загрузки изображений. Давайте рассмотрим их.

Ленивая загрузка с учетом особенностей браузера

Первый удивительный атрибут, который мы получаем в теге img для улучшения производительности изображений, — это loading=lazy:

<!-- 😍 -->
<img 
  loading="lazy"
  ... 
>

Это уже огромное улучшение. Теперь ваши посетители не будут автоматически загружать изображения, которые даже не находятся в области просмотра. Да и производительность тут отличная, поскольку ленивая загрузка полностью реализована браузерами и не требует JS. Кстати, она поддерживается всеми современными браузерами.

Обратите внимание на одну важную деталь. В идеале не стоит лениво загружать изображения, которые будут находиться в области просмотра браузера при первоначальной загрузке.

Наиболее важные изображения нужно загружать как можно быстрее, а все остальные пускай загружаются только по мере необходимости.

PS: loading=lazy также работает с iframes.

Оптимальный размер для всех размеров и разрешений экрана

Использование srcset для изображений очень важно. Вам нужно обеспечить оптимальный размер изображения для разных размеров и разрешений экрана:

<img 
  srcset="
    /image.png?width=100 100w,
    /image.png?width=200 200w,
    /image.png?width=400 400w,
    /image.png?width=800 800w
  "
  ...
>

Важно отметить, что это более мощная версия того, что вы получаете с image-set в CSS, поскольку в img srcset можно использовать единицы измерения w.

Примечание редакции Techrocks. Об единицах измерения области просмотра можно почитать в статье «Единицы измерения в CSS».

Здесь полезно то, что мы учитываем как размер, так и разрешение.

Предположим, в настоящее время изображение отображается на устройстве с плотностью пикселей 2х. При использовании вышеуказанного srcset браузер будет знать, что нужно взять изображение 400w. Это изображение шириной 400px, поэтому оно отлично отображается при плотности пикселей 2х. Аналогично, на устройстве с плотностью пикселей 1х будет захвачено изображение 200w.

Современные форматы с тегом picture

Вы, наверное, заметили, что в наших примерах мы используем формат .png. Он поддерживается любым браузером, но почти никогда не является наиболее оптимальным форматом изображения.

Именно здесь добавление picture вокруг нашего img позволит нам указать более современные и оптимальные форматы, такие как webp, и поддерживаемые браузеры будут отдавать предпочтение именно им:

<picture>
  <source 
    type="image/webp"
    srcset="
      /image.webp?width=100 100w,
      /image.webp?width=200 200w,
      /image.webp?width=400 400w,
      /image.webp?width=800 800w
    " />
  <img ... />
</picture>

Опционально можно также поддерживать дополнительные форматы, например AVIF:

<picture>
  <source 
    type="image/avif"
    srcset="/image.avif?width=100 100w, /image.avif?width=200 200w, /image.avif?width=400 400w, /image.avif?width=800 800w, ...">
  <source 
    type="image/webp"
    srcset="/image.webp?width=100 100w, /image.webp?width=200 200w, /image.webp?width=400 400w, /image.webp?width=800 800w, ...">
  <img ...>
</picture>

Не забывайте про aspect-ratio

Важно помнить, что мы также хотим избежать смещения макета. Оно происходит при загрузке картинки, если вы не указали точный размер изображения. Избежать смещения можно двумя способами.

Первый — указать для изображения атрибуты ширины и высоты. Также часто будет хорошей идеей установить для изображения в CSS значение height как auto. Это нужно, чтобы изображение правильно реагировало на изменение размера экрана:

<img 
  width="500" 
  height="300" 
  style="height: auto" 
  ...
>

В качестве альтернативы вы также можете просто использовать новое свойство aspect-ratio в CSS. Благодаря этому у вас всегда будет правильное соотношение сторон. При таком варианте вам не нужно знать точную ширину и высоту изображения:

<img style="aspect-ratio: 5 / 3; width: 100%" ...>

aspect-ratio также отлично сочетается с object-fit и object-position. Они очень похожи на background-size и background-position для фоновых изображений.

.my-image {
  aspect-ratio: 5 / 3;
  width: 100%;
  /* Fill the available space, even if the 
     image has a different intrinsic aspect ratio */
  object-fit: cover; 
}

Асинхронное декодирование изображений

Кроме того, вы можете указать decoding="async" для изображений. Это позволит браузеру убрать декодирование изображения из основного потока. MDN рекомендует использовать это для изображений вне экрана.

<img decoding="async" ... >

Подсказки ресурсов

Последняя и более продвинутая опция — fetchpriority. Она может быть полезна для подсказки браузеру, имеет ли изображение сверхвысокий приоритет.

<img fetchpriority="high" ...>

Также с ее помощью можно понизить приоритет изображений. Это полезно, когда у вас есть не слишком важные изображения, которые, тем не менее, должны выводиться на первом экране. Например — изображения на других страницах карусели:

<div class="carousel">
  <img class="slide-1" fetchpriority="high">
  <img class="slide-2" fetchpriority="low">
  <img class="slide-3" fetchpriority="low">
</div>

Добавляйте alt-текст, ребята

Да, alt-текст имеет решающее значение для доступности и SEO, и им не стоит пренебрегать:

<img
  alt="Builder.io drag and drop interface"
  ...
>

Изображения, которые добавлены исключительно для красоты (например, абстрактные формы, цвета или градиенты), можно явно пометить как презентационные с помощью атрибута role:

<img role="presentation" ... >

Атрибут sizes

Важное замечание касательно атрибута srcset, упомянутого выше. Браузеры должны знать, какого размера будет изображение, чтобы выбрать оптимальный размер.

То есть, после рендеринга изображения браузер знает его фактический размер, умножает его на плотность пикселей и подбирает наиболее близкое по размеру изображение в srcset.

Но такие браузеры, как chrome, имеют сканер предварительной загрузки. Он ищет теги img в HTML при первоначальной загрузке страницы и сразу же начинает их предварительную выборку.

Дело в том, что это происходит еще до того, как страница отобразилась. Наш CSS еще даже не был получен, поэтому нет никаких указаний на то, как будет отображаться изображение и какого размера. В результате браузеру приходится делать некоторые предположения.

По умолчанию браузер считает, что все изображения имеют размер 100vw, то есть полную ширину страницы. Это больше настоящего размера (возможно, немного, а может быть и намного больше). Так что это далеко не оптимально.

Вот тут-то и пригодится атрибут sizes:

<img 
  srcset="..."
  sizes="(max-width: 800px) 100vw, 50vw"
  ...
>

С помощью этого атрибута мы можем указать браузеру, насколько большим должно быть наше изображение при различных размерах окна.

Можно указать точное значение в пикселях, например 500px. Либо можно задать значение относительно окна. Например, 50vw означает, что изображение должно отображаться примерно на 50% ширины окна).

Приведенный выше пример кода велит браузеру предполагать, следующее:

  1. Для любого экрана шириной до 800px изображение заполняет весь экран (100vw),
  2. Для любого другого (большего) размера изображение заполняет половину экрана (50vw).

Предварительная выборка делается исходя из этих предположений.

Таким образом, в этом примере экран шириной 900px не будет охвачен первым пунктом ((max-width: 800px)). Он будет соответствовать второму пункту, который указывает, что для больших экранов изображение будет отображаться на 50vw.

Так как 50vw * 900px = 450px, браузер будет стремиться к изображению шириной 450px для дисплея с плотностью пикселей 1х, 900px для дисплея с плотностью пикселей 2х и т. д. Затем он будет искать ближайшее совпадение в наборе srcset и использовать его в качестве изображения для предварительной выборки.

Мы можем добавить столько пунктов, сколько захотим, например:

<img 
  srcset="..."
  sizes="(max-width: 400px) 200px, (max-width: 600px) 20vw, 50vw"
  ...
>

В этом примере для экрана шириной 350px будет получено изображение шириной 200px, соответствующее текущему размеру экрана: (max-width: 400px) 200px.

Если этот экран имеет плотность пикселей в 2 раза больше, браузер будет знать, что изображение будет отображаться на 200px, но умножит это значение на 2 и получит изображение в 400px, чтобы подогнать изображение под более высокое разрешение.

Подведем итоги

Много новой информации, правда? Давайте соберем все воедино.

Вот отличный пример очень оптимизированного для загрузки изображения:

<picture>
  <source 
    type="image/avif"
    srcset="/image.avif?width=100 100w, /image.avif?width=200 200w, /image.avif?width=400 400w, /image.avif?width=800 800w" />
  <source 
    type="image/webp"
    srcset="/image.webp?width=100 100w, /image.webp?width=200 200w, /image.webp?width=400 400w, /image.webp?width=800 800w" />
  <img 
    src="/image.png"
    srcset="/image.png?width=100 100w, /image.png?width=200 200w, /image.png?width=400 400w, /image.png?width=800 800w"
    sizes="(max-width: 800px) 100vw, 50vw"
    style="width: 100%; aspect-ratio: 16/9"
    loading="lazy"
    decoding="async"
    alt="Builder.io drag and drop interface"
  />
</picture>

Это хороший вариант по умолчанию. Он лучше всего подходит для изображений, которые не обязательно должны загружаться первыми при первоначальной загрузке страницы.

Для изображений с наивысшим приоритетом следует убрать load="lazy" и decoding="async". Также нужно добавить fetchpriority="high", если это изображение с абсолютным наивысшим приоритетом:

      style="width: 100%; aspect-ratio: 16/9"
-     loading="lazy"
-     decoding="async"
+     fetchpriority="high"
      alt="Builder.io drag and drop interface"

Использование изображения для фона

Ах да, чуть не забыл, что мы начали эту статью с рассказа о фоновом изображении.

Оптимизация, обсуждаемая здесь, применима к любому типу изображений (фон, передний план и т.д.). Но чтобы заставить img вести себя как background-image, требуется немного CSS (а именно — абсолютное позиционирование и свойство object-fit).

Вот упрощенный пример, который вы можете попробовать сами:

<div class="container">
  <picture class="bg-image">
    <source type="image/webp" ...>
    <img ...>
  </picture>
  <h1>I am on top of the image</h1>
</div>
<style>
  .container { position: relative; }
  h1 { position: relative; }
  .bg-image { position: absolute; inset: 0; }
  .bg-image img { width: 100%; height: 100%; object-fit: cover; }
</style>

Вредно ли использование такого количества дополнительного HTML для производительности?

И да, и нет, но в основном нет.

Легко забыть, насколько велики размеры изображений (в байтах). Добавив несколько байт в HTML, вы можете сэкономить тысячи или даже миллионы байт на этих изображениях, загружая гораздо более оптимизированные версии.

Также давайте не будем забывать о gzip. Дополнительная разметка, которую вы добавите для каждого изображения, быстро станет очень избыточной, а gzip отлично с этим справится.

Поэтому, хотя раздутость DOM и размер полезной нагрузки определенно всегда должны вызывать беспокойство, я бы предположил, что в данном случае компромисс будет в вашу пользу.

Более простой способ

В наше время почти никогда не бывает нужды писать все вышеперечисленные безумные вещи вручную. Такие фреймворки, как NextJS и Qwik, а также платформы Cloudinary и Builder.io предлагают компоненты изображений, которые упрощают эту задачу и выглядят примерно так, как показано ниже:

<!-- 😍 -->
<Image 
  src="/image.png" 
  alt="Builder.io drag and drop interface" />

С их помощью вы сможете автоматически получить большинство, если не все, из вышеперечисленных оптимизаций (включая генерацию всех размеров и форматов изображений).

Заключение

Используйте img в HTML вместо background-image в CSS всегда, когда это возможно. Используйте ленивую загрузку, srcset, теги picture и другие оптимизации, о которых мы говорили выше, чтобы предоставлять изображения наиболее оптимальным способом. Помните о высоком и низком приоритете изображений и соответствующим образом настраивайте свои атрибуты.

Или просто используйте хороший фреймворк (например, NextJS или Qwik) и/или хорошие платформы (например, Cloudinary или Builder.io), и все будет просто.

Перевод статьи «Optimal Images in HTML».

[customscript]techrocks_custom_after_post_html[/customscript]

[customscript]techrocks_custom_script[/customscript]

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх