Infinite scroll

Библиотека Unpoly предоставляет мощные инструменты для динамического обновления страниц без полной перезагрузки, включая реализацию бесконечной прокрутки (infinite scroll). Эта функциональность позволяет подгружать новые данные по мере прокрутки пользователя, повышая интерактивность и снижая нагрузку на сервер.


Основные принципы

Бесконечная прокрутка в Unpoly строится на сочетании:

  1. Асинхронных запросов (up.link, up.visit или up.render).
  2. Событий прокрутки и позиционирования элементов.
  3. Фрагментного обновления DOM через селекторы Unpoly (up-target, up-on-load).

Ключевой принцип: новые данные загружаются и вставляются в документ без перезагрузки страницы, а URL может обновляться при необходимости для корректного использования истории браузера.


Разметка для бесконечной прокрутки

Стандартный шаблон включает контейнер, куда будут добавляться новые элементы, и элемент-загрузчик:

<div id="items-container" up-target="this">
  <div class="item">Элемент 1</div>
  <div class="item">Элемент 2</div>
  <div class="item">Элемент 3</div>
</div>

<button id="load-more" 
        up-target="#items-container" 
        up-append=".item" 
        up-url="/items?page=2" 
        up-on-click="loadNextPage">
  Загрузить ещё
</button>

Ключевые атрибуты:

  • up-target: указывает контейнер, в который будут вставляться новые элементы.
  • up-append: определяет, какие элементы вставлять из полученного фрагмента.
  • up-url: адрес следующей страницы данных.
  • up-on-click: привязка обработчика для кастомной логики (может быть использована для автоматической загрузки при скролле).

Автозагрузка при прокрутке

Чтобы элементы подгружались автоматически при достижении нижней границы контейнера, используется событие scroll в сочетании с методами Unpoly:

function setupInfiniteScroll() {
  const container = document.getElementById('items-container');

  window.addEventListener('scroll', () => {
    const scrollPosition = window.scrollY + window.innerHeight;
    const threshold = container.offsetTop + container.offsetHeight;

    if (scrollPosition > threshold - 100) {
      loadNextPage();
    }
  });
}

function loadNextPage() {
  const button = document.getElementById('load-more');
  if (!button) return;

  up.follow(button); // инициирует запрос, как при клике
}

Особенности реализации:

  • up.follow(element) выполняет тот же запрос, что и нажатие кнопки, включая все up-* атрибуты.
  • threshold - 100 добавляет небольшой запас, чтобы загрузка начиналась заранее и пользователь не видел пустой зоны.

Подгрузка и вставка фрагментов

Unpoly поддерживает точечное обновление DOM. Для бесконечной прокрутки важно правильно настроить селекторы:

  • up-append: добавляет новые элементы в конец контейнера.
  • up-prepend: вставляет элементы в начало.
  • up-replace: заменяет существующее содержимое контейнера.

Пример использования up-append:

<div id="messages" up-target="this">
  <div class="message">Сообщение 1</div>
</div>

<div id="load-messages" 
     up-target="#messages" 
     up-append=".message" 
     up-url="/messages?page=2">
</div>

При каждом запросе новые .message элементы будут добавляться последовательно, сохраняя старые.


Управление состоянием страницы

Бесконечная прокрутка часто подразумевает работу с историей браузера. Unpoly позволяет:

  • Обновлять URL с помощью up:history:push.
  • Использовать up-on-load для инициализации новых элементов после вставки.

Пример:

<div id="articles" up-target="this" up-on-load="initLazyImages()">
  <article class="post">Пост 1</article>
</div>

Событие up:after-append может использоваться для динамических действий после добавления новых элементов:

up.on('up:after-append', '#articles', (event) => {
  console.log('Новые посты добавлены:', event.fragments);
});

Оптимизация и производительность

Для больших списков важно учитывать:

  1. Дебаунс событий scroll:
let scrollTimeout;
window.addEventListener('scroll', () => {
  if (scrollTimeout) clearTimeout(scrollTimeout);
  scrollTimeout = setTimeout(() => loadNextPage(), 200);
});
  1. Контроль загрузки: не запускать новый запрос, пока предыдущий не завершён.
  2. Ленивая загрузка изображений с помощью loading="lazy" или кастомных функций в up-on-load.
  3. Постраничная навигация: добавлять параметр page в URL и корректно увеличивать его на сервере.

Интеграция с серверной пагинацией

На сервере необходимо отдавать HTML-фрагменты, соответствующие селектору up-append. Пример для Express.js:

app.get('/items', (req, res) => {
  const page = parseInt(req.query.page) || 1;
  const items = getItems(page); // массив элементов
  res.render('items_fragment', { items });
});

Фрагмент items_fragment содержит только новые .item элементы без обертки страницы. Это минимизирует объём данных и ускоряет загрузку.


Реактивные улучшения

  • Использование up:fragment:loading для отображения индикатора загрузки.
  • Поддержка отмены предыдущих запросов с up.abort.
  • Возможность вставки элементов в разные контейнеры с разными селекторами.

Бесконечная прокрутка в Unpoly позволяет строить динамичные, отзывчивые интерфейсы, объединяя преимущества фрагментного обновления и событийной модели библиотеки. При правильной настройке селекторов, контроля запросов и оптимизации рендеринга достигается плавная и эффективная подгрузка данных.