Допустим, у вас есть классная страница и вы добавляете фоновое изображение:
.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) для упрощения и ускорения своей работы.
Помните об атрибутах высокого и низкого приоритета загрузки изображений, настраивайте их соответствующим образом.