Изображения составляют огромный процент содержания веб-страниц. Однако чем больше изображений, тем медленнее загружается страница.
Большое количество изображений на сайте значительно влияет на его производительность. Отложенная загрузка — это концепция, разработанная для ограничения количества загружаемых ресурсов во время загрузки страницы.
Что такое отложенная загрузка?
Самыми важными составляющими производительности приложений являются время отклика и потребление ресурсов.
Отложенная загрузка помогает снизить риск возникновения некоторых проблем с производительностью веб-приложений до минимума. Она контролирует следующие параметры:
- Время отклика — это время, которое требуется для того, чтобы веб-приложение загрузилось, а UI-интерфейс начал реагировать на запросы пользователей. Отложенная загрузка оптимизирует время отклика с помощью разделения кода и загрузки необходимого бандла.
- Потребление ресурсов. Если загрузка веб-сайта длится более трех секунд, 70% пользователей покидают веб-сайт. При отложенной загрузке объем загружаемых ресурсов сокращается благодаря загрузке только необходимого на данном этапе бандла кода.
Отложенная загрузка ускоряет время загрузки приложения, загружая ресурсы по запросу.
Преимущества отложенной загрузки:
- Высокая производительность при начальной загрузке.
- Загрузка меньшего количества ресурсов при начальной загрузке.
Отложенная загрузка изображений с помощью Intersection Observer
Загрузка изображений или ресурсов, не находящихся в поле зрения пользователя, не имеет смысла. В первую очередь загружаются изображения в окне просмотра, затем при прокрутке страницы также загружаются и помещаются в представление изображения, которые попадают в окно просмотра.
Рассмотрим изображение выше. Веб-браузер и веб-страница загружены. Изображения #IMG_1
и #IMG_2
находятся в окне просмотра (видны пользователю и находятся в рамках представления браузера).
Изображения #IMG_3
и #IMG_4
находятся вне зоны видимости пользователя. Таким образом, производительность сайта заметно улучшится, если #IMG_1
и #IMG_2
будут загружены в первую очередь, а #IMG_ 3
и #IMG_4
позже по очереди при прокрутке вниз.
Но как определить, когда элемент появляется в представлении? Современный браузер предоставляет новый Intersection Observer API, с помощью которого можно определить, когда элемент попадает в окно просмотра.
Intersection Observer
Intersection Observer API предоставляет асинхронное наблюдение изменений в видимости элементов или относительной видимости двух элементов по отношению друг к другу.
Для отложенной загрузки изображений нужно определить элемент с шаблоном разметки:
<img class="lzy_img" src="lazy_img.jpg" data-src="real_img.jpg" />
Так выглядит отложено загруженный элемент изображения.
Класс определяет элемент как отложено загруженный элемент img. Атрибут src передает отложено загруженному изображению исходное изображение до загрузки реального изображения. Data-src содержит реальное изображение, которое будет загружено в элемент при появлении в окне просмотра браузера.
Теперь переходим к написанию логики отложенной загрузки. Как было сказано выше, для определения видимости элемента по отношению к документу в браузере мы используем Intersection Observer.
Сначала создаем экземпляр Intersection Observer:
const imageObserver = new IntersectionObserver(...);
IO принимает функцию в конструкторе, которая обладает двумя параметрами: первый содержит массив, состоящий из элемента для наблюдения, а второй содержит экземпляр IO.
const imageObserver = new IntersectionObserver((entries, imgObserver) => {
//...
});
- entries: содержит в массиве элементы, находящиеся в окне просмотра браузера.
- imgObserver: экземпляр IntersectionObserver.
Поскольку entries — это массив, то для него необходимо выполнить цикл, чтобы получить находящиеся внутри элементы и выполнить отложенную загрузку для каждого из них.
const imageObserver = new IntersectionObserver((entries, imgObserver) => {
entries.forEach((entry) => {
//...
})
});
Затем нужно проверить, находится ли каждый entry в окне просмотра. Если entry пересекается с окном просмотра, то устанавливаем значение атрибута data-src для атрибута src в элементе img.
const imageObserver = new IntersectionObserver((entries, imgObserver) => {
entries.forEach((entry) => {
if(entry.isIntersecting) {
const lazyImage = entry.target
lazyImage.src = lazyImage.dataset.src
}
})
});
Проверяем, находится ли entry в окне видимости браузера if(entry.isIntersecting) {...}
. Если да, то сохраняем экземпляр HTMLImgElement элемента img в переменной lazyImage. Затем устанавливаем атрибут src, присвоив его значению набора данных src
. При этом изображение, сохраненное в data-src
, загружается в элемент img. Предыдущее изображение lazy_img.jpg
заменяется в браузере реальным изображением.
Теперь нужно вызвать imageObserver для начала наблюдения за элементами img:
imageObserver.observe(document.querySelectorAll('img.lzy_img'));
Объединяем все элементы с классом lzy_img
в документе document.querySelectorAll('img.lzy_img')
и передаем в imageObserver.observer(...)
.
imageObserver.observer(...)
выбирает массив элементов и прослушивает их, чтобы узнать о пересечении их видимости с браузером.
Чтобы увидеть демо отложенной загрузки, нужно запустить проект Node:
mkdir lzy_img
cd lzy_img
npm init -y
Создайте файл index.html:
touch index.html
Добавьте в него следующее:
<html>
<title>Lazy Load Images</title>
<body>
<div>
<div style="">
<img class="lzy_img" src="lazy_img.jpg" data-src="img_1.jpg" />
<hr />
</div>
<div style="">
<img class="lzy_img" src="lazy_img.jpg" data-src="img_2.jpg" />
<hr />
</div>
<div style="">
<img class="lzy_img" src="lazy_img.jpg" data-src="img_3.jpg" />
<hr />
</div>
<div style="">
<img class="lzy_img" src="lazy_img.jpg" data-src="img_4.jpg" />
<hr />
</div>
<div style="">
<img class="lzy_img" src="lazy_img.jpg" data-src="img_5.jpg" />
<hr />
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
const imageObserver = new IntersectionObserver((entries, imgObserver) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const lazyImage = entry.target
console.log("lazy loading ", lazyImage)
lazyImage.src = lazyImage.dataset.src
}
})
});
const arr = document.querySelectorAll('img.lzy_img')
arr.forEach((v) => {
imageObserver.observe(v);
})
})
</script>
</body>
</html>
У нас есть 5 отложенных изображений, каждое из которых содержит lazy_img.jpg, а также несколько реальных изображений для загрузки.
Необходимо создать свои изображения:
lazy_img.jpg
img_1.jpg
img_2.jpg
img_3.jpg
img_4.jpg
img_5.jpg
Я создал lazy_img.jpg в Windows Paint, а реальные изображения (img_*.jpg
) взял из Pixabay.com.
Обратите внимание, что я добавил console.log в функцию обратного вызова IntersectionObserver. Благодаря этому можно узнать, для каких изображений применяется отложенная загрузка.
Для использования файла index.html нужно установить http-сервер:
npm i http-server
Теперь добавляем свойство start
в раздел сценариев в package.json.
"scripts": {
"start": "http-server ./"
}
Теперь запустите npm run start
в терминале.
Откройте браузер и перейдите к 127.0.0.1:8080
. Загруженный index.html будет выглядеть следующим образом:
Изображения показывают lazy_img.jpg, и поскольку <img class="lzy_img" src="lazy_img.jpg" data-src="img_1.jpg" />
находится в окне просмотра, то загружается реальное изображение img_1.jpg
.
Другие изображения не загружаются, так как они не находятся в представлении браузера.
Здесь есть небольшая проблема. При прокрутке img в представление загружается его изображение, однако при следующей прокрутке того же img в представление, браузер снова пытается загрузить реальное изображение.
Это серьезно повлияет на производительность сайта.
Чтобы решить эту проблему, нужно отписать IntersectionObserver от img, для которого уже загружено реальное изображение, а также удалить класс lzy из элемента img.
Редактируем код следующим образом:
<script>
document.addEventListener("DOMContentLoaded", function() {
const imageObserver = new IntersectionObserver((entries, imgObserver) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const lazyImage = entry.target
console.log("lazy loading ", lazyImage)
lazyImage.src = lazyImage.dataset.src
lazyImage.classList.remove("lzy")
imgObserver.unobserve(lazyImage)
}
})
});
// ...
})
</script>
Полная версия кода
<html>
<title>Lazy Load Images</title>
<body>
<div>
<div style="">
<img class="lzy_img" src="lazy_img.jpg" data-src="img_1.jpg" />
<hr />
</div>
<div style="">
<img class="lzy_img" src="lazy_img.jpg" data-src="img_2.jpg" />
<hr />
</div>
<div style="">
<img class="lzy_img" src="lazy_img.jpg" data-src="img_3.jpg" />
<hr />
</div>
<div style="">
<img class="lzy_img" src="lazy_img.jpg" data-src="img_4.jpg" />
<hr />
</div>
<div style="">
<img class="lzy_img" src="lazy_img.jpg" data-src="img_5.jpg" />
<hr />
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
const imageObserver = new IntersectionObserver((entries, imgObserver) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const lazyImage = entry.target
console.log("lazy loading ", lazyImage)
lazyImage.src = lazyImage.dataset.src
lazyImage.classList.remove("lzy_img");
imgObserver.unobserve(lazyImage);
}
})
});
const arr = document.querySelectorAll('img.lzy_img')
arr.forEach((v) => {
imageObserver.observe(v);
})
})
</script>
</body>
</html>
Заключение
Мы рассмотрели отложенную загрузку изображений с помощью IntersecionObserver API, а затем реализовали демо-версию с настройкой кода отложенной загрузки в JS.
Перевод статьи Chidume Nnamdi ????: Lazy Loading Images using the Intersection Observer API