Бесконечная прокрутка: освоение API Intersection Observer
![](/static/storage/17308551163767568075294833713183824855.png)
API Intersection Observer предоставляет способ асинхронного наблюдения за изменениями в пересечении (видимости) целевого элемента с элементом-предком или областью просмотра документа верхнего уровня. Он обычно используется для реализации бесконечной прокрутки, запуска анимации при попадании элемента в область просмотра, ленивой загрузки изображений и т.д.
Это упрощает процесс запуска действий, когда элементы становятся видимыми, устраняя необходимость ручной обработки событий и частых опросов.
В этой статье мы реализуем бесконечную прокрутку с использованием API Intersection Observer в стандартном Javascript. Infinite scrolling
— это шаблон проектирования взаимодействия, в котором страница загружает контент по мере того, как пользователь прокручивает его вниз, что позволяет пользователю исследовать большой объем контента без четкого конца.
Мы хотим, чтобы целевой элемент в нашем случае Loading More Items
, как показано ниже, стал полностью видимым на 100% в окне нашего браузера, затем мы извлекали сообщения, думая об этом, по сути, как «ощущение» или обнаружение того, когда пользователь прокрутил до конца текущего контента. Этому обнаружению способствует метод IntersectionObserver API
, который обнаруживает целевой элемент внизу контента (в нашем случае). Когда этот целевой элемент пересекается с областью просмотра (т.е. появляется в поле зрения, становится полностью видимым, как на рисунке ниже), он запускает функцию обратного вызова для загрузки большего количества контента (получения большего количества сообщений).
![](/static/storage/321935825320492509925091347068363110775.avif)
Познакомимся с API Intersection Observer
1. Создайте IntersectionObserver — observer
будет следить за элементами.
let observer = new IntersectionObserver(callbackFunc, options)
callbackFunc
— эта функция будет вызвана, когда целевой элемент пересекается с областью просмотра устройства или указанным элементом.
Другими словами, когда целевой элемент станет видимым или когда его видимость изменится, будет вызвана эта функция. Когда мы говорим о целевом элементе, «пересекающемся с областью просмотра», мы имеем в виду, когда часть или весь этот элемент становится видимым в окне браузера пользователя.
options
— давайте контролировать обстоятельства, при которых вызывается callbackFunc
наблюдателя, путем определения threshold (порога). Это похоже на установку правил инструкций observer
для наблюдения за целевым элементом до тех пор, пока целевой элемент не станет полностью видимым, наполовину или даже на дюйм видимым, а затем наблюдатель сообщит вам, по сути, вызывая метод callbackFunc
. Впоследствии вы можете что-то сделать с этим целевым элементом и т.д.
const options = { root: null, threshold: 0.5 };
threshold: 0.5
— мы говорим, что обратный вызов сработает, когда будет видно 50% целевого элемента.
Поэтому observer’s callback function
срабатывает, когда выполняются условия пересечения (определяемые threshold
).
По умолчанию root
имеет значение null
, здесь мы передаем его явно, но если свойство не будет включено будет неявно присвоено значение null
. Примечание: вы можете установить root
как любой элемент.
2. Observer — мы используем метод observe
, чтобы начать мониторинг/наблюдение за целевым элементом. Вы можете иметь один или несколько целевых элементов.
observer.observe(targetElement);
Метод observe
используется для начала наблюдения за конкретным целевым элементом. При вызове он сообщает экземпляру IntersectionObserver
отслеживать указанный элемент и сообщать всякий раз, когда он становится видимым или невидимым в области просмотра или в определенном root элементе.
Свяжем все с помощью четкого примера
Мы реализуем простое веб-приложение, которое динамически загружает сообщения при прокрутке пользователем.
1. Определите простую структуру HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Infinite Scroll Example</title>
<style>
#content {
max-width: 600px;
margin: 0 auto;
}
.item {
padding: 20px;
border: 1px solid #ccc;
margin: 10px 0;
}
</style>
</head>
<body>
<div id="content">
<!-- Posts will be dynamically loaded here -->
</div>
<!-- This will be our target element -->
<div id="loading" style="text-align: center; margin: 20px; background-color: rgb(173, 160, 185);">
Loading more items...
</div>
<script src="InfiniteScroll.js"></script>
</body>
</html>
2. Создайте файл JavaScript и назовите его InfiniteScroll.js
Давайте инициализируем некоторые важные переменные.
let currentPage = 1; // это поможет нам отслеживать текущую страницу данных
let loading = false; // это предотвратит множественные выборки данных одновременно
const contentDiv = document.getElementById('content'); // наш пост будет динамически загружен здесь
const loadingDiv = document.getElementById('loading'); // это будет наш целевой элемент
Мы собираемся определить нашу функцию, которая будет отвечать за получение сообщения. Будем использовать конечную точку API:
https://jsonplaceholder.typicode.com/posts?_limit=10&_page=${page}
Давайте разберемся: у нас есть базовый URL-адрес https://jsonplaceholder.typicode.com/posts
, который указывает на ресурс «post» в API JSONPlaceholder и предоставляет данные, связанные с сообщениями.
У нас также есть параметры запроса ?_limit=10&_page=${page}
, которые фильтруют запрос.
_limit=10
: указывает API ограничить ответ 10 сообщениями._page=${page}
: указывает, какую страницу данных вы хотите получить.
Например, _page=1
мы получим первый набор из 10 сообщений, _page=2
получим следующие 10 сообщений и так далее. Мы изменим переменную страницы динамически, следовательно, наша функция будет выглядеть так:
const getPosts = async (page) => {
try {
let response = await fetch(`https://jsonplaceholder.typicode.com/posts?_limit=10&_page=${page}`);
if (!response.ok) {
throw new Error("HTTP error! Status: " + response.status);
}
return await response.json();
} catch (e) {
throw new Error("Failed to fetch services: " + e.message);
}
}
- Функция, которая будет динамически добавлять данные на нашу веб-страницу. мы собираемся сделать это просто:
const appendData = (data) => {
data.forEach(item => {
const div = document.createElement('div');
div.className = 'item';
div.innerHTML = `<h3>${item.title}</h3><p>${item.body}</p>`;
contentDiv.appendChild(div);
});
}
- Настройка наблюдателя пересечения
const observer = new IntersectionObserver(async (entries) => {
if (entries[0].isIntersecting && !loading) {
console.log(entries)
loading = true;
currentPage++;
try {
const data = await getPosts(currentPage);
appendData(data);
} catch (e) {
console.log(e.message);
}
loading = false;
}
}, { threshold: 1.0 });
Пройдемся по приведенному выше коду шаг за шагом
Сначала мы создаем экземпляр под названием observer
.
const observer = new IntersectionObserver(async (entries) => {}, { threshold: 1.0 });
Мы передаем два аргумента callbackFunc
и options
.
Если вы передаете threshold option
как { threshold: 1.0 }
при создании IntersectionObserver, это означает, что для параметра root
неявно установлено значение по умолчанию, то есть null
. В этом случае наблюдатель будет использовать область просмотра браузера root
для определения видимости. 1.0
означает, что когда цель видна на 100%, callbackFunc
немедленно вызывается.
Обратный вызов принимает 2 аргумента: entries
и тот observer
, который является самим InstersectionObserver. В нашем случае мы передаем только аргумент entries
.
Что такое entries
? Это массив объектов IntersectionObserverEntry
. Каждая запись представляет собой наблюдаемый или целевой элемент и содержит информацию о его пересечении с корнем или любым указанным элементом.
Давайте сначала начнем наблюдать за нашим целевым элементом, а затем вернемся к приведенному выше объяснению, это будет иметь смысл.
observer.observe(loadingDiv);
Здесь мы используем метод observe
, который при вызове сообщает экземпляру IntersectionObserver
отслеживать указанный элемент и сообщать об этом всякий раз, когда он становится видимым 100%
или 1.0
когда мы устанавливаем threshold
значение.
Когда видимость целевого элемента изменяется в соответствии с указанным порогом, callback function
срабатывает массив объектов IntersectionObserverEntry
, и мы получаем их в виде записей, которые предоставляют подробную информацию о пересечении.
Возвращаясь к функции обратного вызова, когда срабатывает console.log(entries)
у нас есть что-то, как показано ниже. Вы можете ясно видеть, что запись представляет собой наблюдаемый элемент (целевой элемент) и содержит информацию о его пересечении с корнем. Поскольку мы наблюдаем один целевой элемент, наш массив записей содержит только один IntersectionObserverEntry
объект.
![](/static/storage/35377509664574599077665885623998130728.avif)
time
: это отметка времени, указывающая, когда произошло пересечение/когда целевой элемент стал полностью видимым в окне браузера.target
: это сам наблюдаемый элемент. Обратите внимание, что теперь у вас есть свобода манипулировать этим элементом, например, изменять его цвет и т.д.boundingClientRect
: положение и размер наблюдаемого элемента.intersectionRect
: площадь пересечения относительно корня.rootBounds
: размер корня (окна просмотра) в нашем случае окна браузераisIntersecting
: логическое значение, указывающее, пересекается ли наблюдаемый элемент в данный момент (видим ли он) с корнем (или областью просмотра) в соответствии с указанным пороговым значением.
Затем внутри функции обратного вызова это то, что мы делаем:
(entries[0].isIntersecting && !loading)
(entries[0])
: проверяет, пересекается ли первая запись с областью просмотра (isIntersecting is true
), и гарантирует, чтоloading
равняетсяfalse
это предотвратит одновременную выборку нескольких сообщений.loading = true
: мы устанавливаемloading
флагtrue
, чтобы указать, что выполняется выборка данных.currentPage++
: увеличивает переменнуюcurrentPage
для получения следующей страницы данных.const data = await getServices(currentPage)
: мы вызываем асинхронную функциюgetPosts
для получения сообщений для увеличенной текущей страницы.appendData(data)
: добавляет полученные сообщения (данные) в DOM с помощью функцииappendData
.loading = false
: мы сбрасываем флаг загрузкиfalse
после завершения загрузки и добавления сообщений. Это позволяет выполнять последующие выборки, когдаloadingDiv
снова пересекается (становится полностью видимым) с окном просмотра.
Проследим за ходом реализации нашей бесконечной прокрутки
![](/static/storage/107177212234625488650016385698318504764.avif)
Наконец, у нас есть прослушиватель событий окна:
window.addEventListener('DOMContentLoaded', async () => {
try {
const posts = await getPosts(currentPage);
if (posts) {
appendData(posts);
} else {
console.log('posts not found or undefined');
}
} catch (e) {
console.log(e.message);
}
});
Выполняется, когда содержимое DOM полностью загружено. Вызывает getPosts
для получения начальных сообщений (currentPage = 1
) и добавляет их с помощью AppendData
.
Полный код
let currentPage = 1; // this will help us keep track of the current page of data
let loading = false; // this will prevent multiple fetches of data at the same time
const contentDiv = document.getElementById('content'); // our post will be dynamically loaded here
const loadingDiv = document.getElementById('loading'); // this will be our target element
const getPosts = async (page) => {
try {
let response = await fetch(`https://jsonplaceholder.typicode.com/posts?_limit=10&_page=${page}`);
if (!response.ok) {
throw new Error("HTTP error! Status: " + response.status);
}
return await response.json();
} catch (e) {
throw new Error("Failed to fetch services: " + e.message);
}
}
const appendData = (data) => {
data.forEach(item => {
const div = document.createElement('div');
div.className = 'item';
div.innerHTML = `<h3>${item.title}</h3><p>${item.body}</p>`;
contentDiv.appendChild(div);
});
}
const observer = new IntersectionObserver(async (entries) => {
if (entries[0].isIntersecting && !loading) {
console.log(entries)
loading = true;
currentPage++;
try {
const data = await getPosts(currentPage);
appendData(data);
} catch (e) {
console.log(e.message);
}
loading = false;
}
}, { threshold: 1.0 });
observer.observe(loadingDiv);
window.addEventListener('DOMContentLoaded', async () => {
try {
const posts = await getPosts(currentPage);
if (posts) {
appendData(posts);
} else {
console.log('posts not found or undefined');
}
} catch (e) {
console.log(e.message);
}
});
Последние мысли
В этой статье мы рассмотрели мощный API IntersectionObserver и его роль в создании плавной бесконечной прокрутки. Используя этот API, вы можете повысить удобство использования своих веб-приложений, сделав их более динамичными и привлекательными для пользователей. Вот краткий обзор того, что мы рассмотрели
Ключевые идеи:
API IntersectionObserver:
- Предоставляет эффективный способ мониторинга видимости целевых элементов в области просмотра.
- Упрощает процесс запуска действий, когда элементы становятся видимыми, устраняя необходимость ручной обработки событий и частых опросов.
Бесконечная прокрутка:
- Обеспечивает непрерывную загрузку контента при прокрутке пользователем, улучшая удобство просмотра, обеспечивая постоянный поток нового контента без необходимости перезагрузки страницы или разбиения на страницы вручную.
Функции и параметры обратного вызова:
- Вызывается
callbackFunc
, когда видимость целевого элемента изменяется в зависимости от указанных пороговых значений. - Объект
options
позволяет точно настраивать поведение наблюдателя, например устанавливать корень и порог.
Заключение
Овладев API IntersectionObserver, вы сможете создавать сложные и удобные веб-приложения, обеспечивающие бесперебойную и интерактивную работу. Независимо от того, создаете ли вы ленту новостей, сайт электронной коммерции или любое другое приложение с большим количеством контента, бесконечная прокрутка на базе IntersectionObserver гарантирует, что пользователи будут оставаться вовлеченными, а ваше приложение будет работать эффективно.
Так что вперед, погрузитесь в редактор кода и начните реализовывать бесконечную прокрутку в своих проектах уже сегодня. Ваши пользователи будут благодарны вам за плавный и непрерывный просмотр!