Проблема

Функциональность бесконечной прокрутки обычно реализуется путем прослушивания события window scroll, а затем выполнения вычислений для определения того, где находится пользователь на странице. Все это происходит в основном потоке, и в зависимости от того, что еще происходит на странице, это может сделать приложение нервным и медленным.

Решение

… войдите в Intersection Observer .

Цитирование документов MDN

API Intersection Observer позволяет коду регистрировать функцию обратного вызова, которая выполняется всякий раз, когда элемент, который они хотят отслеживать, входит или выходит из другого элемента (или «окна просмотра) или когда величина, на которую они пересекаются, изменяется на запрошенную величину. Таким образом, сайтам больше не нужно ничего делать в основном потоке, чтобы отслеживать такого рода пересечения элементов, а браузер может оптимизировать управление пересечениями по своему усмотрению».

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

Хватит слов.. давайте посмотрим, что делает приложение, и для супер нетерпеливого читателя вроде меня вот код:

Если глядя на гифку, вы не ослепли... поздравляю!

Сначала нам нужно решить, где инициализировать наблюдатель пересечения. Для реагирующих приложений это место, где DOM впервые доступен для запросов, а именно componentDidMount .

Наш первоначальный DOM выглядит довольно наивно, и кажется, что ничего не происходит:

render() {
 return <div className="container">
   <div id= "end-sentinel0"></div>
  </div>
}

Но обратите внимание на довольно причудливо выглядящий идентификатор dom, читаемый как end-sentinel${id}. Идентификатор продолжает меняться по мере вставки новых лениво вставленных строк изображений.

Этот конкретный целевой элемент отслеживается на предмет пересечения с корневым элементом, которым в нашем случае является порт просмотра, инициализированный как:

let observerOptions = {
  root: null,
  rootMargin: "0px",
  threshold: 1.0
};

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

configureInsertedElement() {
 return <React.Fragment>
   <div className="row"
    <div className="col-1-3 image-no-show-wrapper">
     <img className="image" src=   {`https://picsum.photos/200/200random=${firstImageInRow}.jpg`}/
    </div>
    ...
    ... 
  </React.Fragment>
}

Однако, как только новая строка вставлена, IO необходимо отменить наблюдение за старым элементом и искать новый, что делается путем увеличения идентификатора inend-sentinel${id}. Все это происходит в обратном вызове, который был инициализирован при загрузке страницы. Это также место, куда мы вставляем только что настроенный элемент DOM.

Надеюсь, комментарии к коду помогут понять, что происходит.

Этот подход ленивого включения целевых элементов заставляет IO регистрировать только один обратный вызов за раз.

Известные проблемы

Я заметил, что если вы прокручиваете очень быстро, как безумно быстро, IO не может зарегистрировать обратный вызов. Однако для большинства практических целей я буду игнорировать его.

Однако, если у вас есть приложение, которое также позволяет пользователю удерживать полосу прокрутки и достигать определенного места… подумайте о функции временной шкалы, вы можете перемещать прокрутку с декабря 2019 года на август 2014 года. Я бы порекомендовал дополнить это решение прослушивателем прокрутки, поскольку IO наверняка не будет прослушивать пересечение, поэтому не сможет запустить обратный вызов.

Спасибо за чтение и, надеюсь, это было полезно.