Если вы не пещерный человек или не ненавидите Джеффа Безоса (не имеет значения, даже если вы его ненавидите), вы, должно быть, совершали покупки в Интернете. Когда вы ищете товар в этих интернет-магазинах, вам предоставляется список результатов. Если вы не такие странные, как я, и не ищете «зуб тигра, пропитанный человеческой кровью» , что слишком конкретно, есть шанс, что ваш поисковый запрос найдет тысячи продуктов. Как вы думаете, загружает ли ваш браузер все эти 1000+ элементов одновременно? Нет, это будет слишком много данных для браузера, чтобы обрабатывать и вычислять пользовательский интерфейс. Тем не менее, вы продолжаете получать предмет по мере того, как достигаете конца своего результата. Это называется бесконечной прокруткой.
Вот пример бесконечной прокрутки на главной странице YouTube.

Есть несколько методов для достижения этого с помощью JavaScript, но наиболее эффективным является использование удобного веб-API, называемого IntersectionObserver. Давайте посмотрим, как использовать этот API для реализации собственного мини-списка с бесконечной прокруткой.
Идея состоит в том, чтобы наблюдать за html-элементом каждый раз, когда он появляется в области просмотра, а затем запускать обратный вызов. Вот как создать этот наблюдатель.
Давайте создадим html-файл, который выглядит так.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Infinte Scroll</title>
</head>
<style>
.product {
padding: 20px;
border: 1px solid black;
margin-bottom: 20px;
opacity: 0;
margin-left: 100px;
transition: all 300ms;
}
.visible {
opacity: 1;
margin-left: 0px;
}
</style>
<body>
<div id="container">
<div class="product">Product</div>
<div class="product">Product</div>
<div class="product">Product</div>
<div class="product">Product</div>
<div class="product">Product</div>
<div class="product">Product</div>
<div class="product">Product</div>
<div class="product">Product</div>
<div class="product">Product</div>
<div class="product">Product</div>
<div class="product">Product</div>
<div class="product">Product</div>
<div class="product">Product</div>
<div class="product">Product</div>
<div class="product">Product</div>
<div class="product">Product</div>
<div class="product">Product</div>
<div class="product">Product</div>
<div class="product">Product</div>
</div>
<script src="./index.js"></script>
</body>
</html>
Здесь у нас есть список карточек товаров, которые по умолчанию скрыты (обратите внимание: opacity: 0; внутри тега стиля). У нас также есть тег скрипта, указывающий на index.js. Сейчас создадим этот index.js.
Сначала давайте получим все карточки продуктов по имени их класса.
const cards = document.querySelectorAll('.product');
Теперь давайте создадим экземпляр наблюдателя пересечения.
const productCards= document.querySelectorAll('.product');
const observer = new IntersectionObserver((entries) => {
// Todo
}, {
threshold: 0.95,
});
productCards.forEach((card) => {
observer.observe(card);
})
Конструктор этого класса IntersectionObserver принимает два параметра. Первые параметры - это обратный вызов, который сам имеет параметр entries. записи здесь представляют все элементы, за которыми будет наблюдать этот наблюдатель. В этом случае каждая запись будет содержать карточку продукта.
Еще одним параметром конструктора является объект. Эти объекты обозначают опции для данного конкретного наблюдателя. В этом случае у нас есть только порог. порог здесь представляет собой число, которое может принимать любое значение от 0 до 1.это означает процент наблюдаемого элемента, который должен быть виден в окне просмотра, чтобы он считался > пересекаются. мы присвоили ему значение 0,95, что означает, что когда 95 % любой карточки продукта будет отображаться в области просмотра, эта карточка будет считаться пересекающейся с областью просмотра.
Вернемся к нашему файлу index.html. У нас также есть еще один класс стиля, visible, определенный в теге styled. Этот стиль, примененный к нашей карточке продукта, сделает карточку снова видимой с небольшой анимацией (свойство transition).
Давайте внесем изменения в наш обозреватель таким образом, чтобы все карточки товаров, которые находятся не менее чем на 95% в области просмотра, были видны, а остальные были скрыты.
const productCards = document.querySelectorAll('.product');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
entry.target.classList.toggle('visible', entry.isIntersecting);
})
}, {
threshold: 0.95
});
productCards.forEach((card) => {
observer.observe(card);
});
Вот как это выглядит в действии.

Однако есть одна загвоздка. Когда мы прокручиваем вверх, поскольку у нас есть логика для переключения видимого класса, когда элемент пересекается, он также удаляет этот класс, когда это не так. Итак, когда мы прокручиваем вверх, мы видим, что наши карты исчезают сверху. Чтобы это исправить, мы перестанем наблюдать за картами, которые уже пересеклись.
const productCards = document.querySelectorAll('.product');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
entry.target.classList.toggle('visible', entry.isIntersecting);
// unobserving entries which have already intersected
if (entry.isIntersecting) observer.unobserve(entry.target);
})
}, {
threshold: 0.95
});
productCards.forEach((card) => {
observer.observe(card);
});
Тогда ладно ! пока что у нас есть…мусор. Это даже близко не к бесконечной прокрутке. мы не загружаем новые карты, когда доходим до конца прокрутки. Но мы сделали это, чтобы изучить основы наблюдателя за пересечением. Давайте теперь превратим это в настоящий список карточек с бесконечной прокруткой.
Во-первых, мы создадим еще одного наблюдателя, который будет просто наблюдать за последней картой.
const lastCardObserver = new IntersectionObserver((entries) => {
const lastCard = entries[0];
if (!lastCard.isIntersecting) return;
loadMoreCards();
lastCardObserver.unobserve(lastCard.target);
lastCardObserver.observe(document.querySelector('.product:last-child'));
}, {
threshold: 0.95
});
lastCardObserver.observe(document.querySelector('.product:last-child'));
Здесь, поскольку мы наблюдаем только одну карточку, у нас будет только один элемент в массиве entries. Когда эта карта пересекается с областью просмотра, мы загружаем еще несколько карт. После этого мы отменим наблюдение за «последней последней картой» и начнем наблюдать за «новой последней картой». Теперь рассмотрим функцию loadMoreCards.
function loadMoreCards() {
const container = document.getElementById('container');
for (let i = 0; i < 10; i++) {
const element = document.createElement('div');
element.classList.add('product');
element.innerText = 'Product';
observer.observe(element);
container.appendChild(element);
}
}
Здесь мы создаем 10 новых карт и наблюдаем за ними нашим первоначальным наблюдателем. В конце мы добавляем эти карты в контейнер.
Вот как это выглядит сейчас!

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