Pull to refresh
98.7
Нетология
Меняем карьеру через образование

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

Level of difficultyEasy
Reading time8 min
Views14K
Original author: Steve Sewell

Допустим, у вас есть классная страница и вы добавляете фоновое изображение:

.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, но это усложнит задачу. И в таком случае нам нужно знать точные размеры изображения для каждого отдельного экрана, и учитывать, что макет сайта может измениться.

Этот подход также упускает важные нюансы: ленивую загрузку (Lazy Loading), текущую поддержку браузерами форматов нового поколения, подсказки приоритета (Priority Hints), асинхронное декодирование и многое другое.

А ещё у нас остаётся актуальной проблема с цепочками запросов.

С тегом изображения ссылка на src находится прямо в HTML. В этом случае браузер получит исходный HTML-код, поищет картинки и начнёт загружать изображения с высоким приоритетом.

При загрузке изображений в CSS и использовании внешних таблиц стилей (link rel=”stylesheet” вместо встроенных стилей) браузер должен просканировать HTML, получить CSS, определить условие, что background-image применяется к элементу. Только после этого он сможет загрузить картинку. Получается долго.

Процесс можно ускорить благодаря встраиванию CSS, предварительной загрузке изображений и предварительного подключения к доменам. А можно использовать ряд преимуществ тега img в HTML, о которых пойдёт речь ниже.

Исключение из правил

Во всех правилах есть исключения.

Чтобы замостить фон очень маленьким изображением, лучше воспользоваться background-repeat. Тег img для размножения картинки не подходит.

Для любого другого изображения больше 50px не рекомендуется задавать размер в CSS — во всех этих случаях лучше использовать тег img

Преимущества тега img

Нативная ленивая загрузка изображений. Атрибут loading=lazy, добавленный к элементу img, откладывает загрузку элементов до тех пор, пока они не попадут в область просмотра.

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

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

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

P. S. loading=lazy также работает с элементами iframe.  

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

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

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

Поддержка современных форматов. Обернув тег img в picture, можно указать современные и более оптимальные форматы, такие, как webp. Поддерживающие эти форматы браузеры предпочтут их, прочитав условие в теге source:

<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>

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

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

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

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

<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" для изображений, что позволит браузеру переместить декодирование изображения из основного потока. Подойдёт для изображений за пределами загружаемого экрана.

<img decoding="async" ... >

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

<img fetchpriority="high" ...>

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

<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. Но браузеры, подобные Chrome, используют сканер предзагрузки для первоначальной загрузки страницы. Сканер ищет теги img в HTML и начинает загрузку с них.

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

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

Здесь-то нам и пригодится атрибут sizes:

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

Он сообщает браузеру, насколько большим должно быть наше изображение при разных размерах экрана. Это может быть точное значение в пикселях либо в зависимости от окна: например, 500px или 50vw (изображение занимает примерно 50% ширины экрана).

В примере выше экран шириной 900px будет выполнять только последнее условие, так как впереди стоящие условия предназначены для экранов меньше 800px. Для экрана шириной 900px будет отображаться изображение на 50vw (оно будет заполнять только половину экрана).

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

Примеры оптимизированных для загрузки изображений

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

<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>

Для загрузки изображений с наивысшим приоритетом, например, основного содержимого страницы, из приведённого кода удаляем loading="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"

Для векторных форматов, например, SVG, не нужно указывать несколько размеров и форматов. Полностью удаляем теги <picture> и <source>, а также атрибуты srcset и sizes

<!-- for SVG -->
<img 
  src="/image.svg"
  style="width: 100%; aspect-ratio: 16/9"
  loading="lazy"
  decoding="async"
  alt="Builder.io drag and drop interface"
/>

Для высокоприоритетных SVG применяются те же правила: удаляем loading и decoding, по желанию добавляем fetchpriority="high" для основного контента.

И, наконец, мы добрались до фонового изображения. Описанные в этой статье способы оптимизации изображений можно применить к любому типу изображений: фон, передний план и т. д. Но чтобы заставить 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" />

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

Обратите внимание, что в большинстве случаев вам всё равно нужно указывать высокий приоритет изображения:

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

Если используете атрибут sizes, придётся также прописать его вручную:

<!-- Manually speify sizes -->
<Image 
  sizes="(max-width: 500px) 200px, 50vw"
  src="/image.png" 
  alt="Builder.io drag and drop interface" />

Подытожим:

  • По возможности используйте img в HTML вместо background-image в CSS. 

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

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

  • Помните об атрибутах высокого и низкого приоритета загрузки изображений, настраивайте их соответствующим образом.

Tags:
Hubs:
Total votes 14: ↑14 and ↓0+14
Comments10

Articles

Information

Website
netology.ru
Registered
Founded
2011
Employees
501–1,000 employees
Location
Россия