Бесконечная прокрутка в Slim Select реализуется через механизм динамической подгрузки опций по мере взаимодействия пользователя со списком. Библиотека изначально ориентирована на работу с ограниченными наборами данных, однако поддержка удалённых источников и кастомной логики загрузки позволяет строить поведение, аналогичное infinite scroll, без явной постраничной навигации.
Ключевая идея заключается в том, что список опций не загружается целиком при инициализации, а дополняется порциями данных по мере необходимости. Это особенно актуально при работе с API, возвращающими большие коллекции данных, где загрузка полного массива приводит к лишним затратам памяти и сетевого трафика.
Slim Select не предоставляет встроенного полноценного infinite scroll в стиле виртуализированных списков, но позволяет реализовать его через комбинацию:
Основной принцип заключается в расширении списка через метод добавления опций и управлении моментом, когда необходимо инициировать следующий запрос.
При использовании удалённого API Slim Select конфигурируется через
ajax:
new SlimSelect({
select: '#select',
ajax: function (search, callback) {
fetch(`/api/items?query=${search}`)
.then(res => res.json())
.then(data => {
callback(data.items);
});
}
});
В классическом сценарии возвращается весь набор совпадений. Для имитации бесконечной прокрутки этого недостаточно, так как отсутствует контроль постраничной выдачи.
Infinite scroll в контексте Slim Select строится поверх пагинации. API должно поддерживать параметры:
limit — количество элементовoffset или page — смещениеcursor — указатель на последнюю записьПример запроса:
function fetchPage({ search, page }) {
return fetch(`/api/items?search=${search}&page=${page}&limit=20`)
.then(res => res.json());
}
Далее состояние страницы хранится локально:
let currentPage = 1;
let isLoading = false;
let hasMore = true;
Slim Select позволяет динамически обновлять список через callback. Базовая загрузка первой страницы:
new SlimSelect({
select: '#select',
ajax: (search, callback) => {
currentPage = 1;
hasMore = true;
fetchPage({ search, page: currentPage })
.then(data => {
hasMore = data.items.length === 20;
callback(data.items);
});
}
});
На этом этапе реализуется только первичная подгрузка. Для бесконечной прокрутки требуется механизм продолжения загрузки при достижении конца списка.
Slim Select рендерит выпадающий список в DOM, что позволяет
отслеживать прокрутку через контейнер списка. Используется событие
scroll:
const select = new SlimSelect({
select: '#select',
ajax: (search, callback) => {
loadFirstPage(search, callback);
}
});
setTimeout(() => {
const dropdown = document.querySelector('.ss-content .ss-list');
dropdown.addEventListener('scroll', () => {
const nearBottom =
dropdown.scrollTop + dropdown.clientHeight >= dropdown.scrollHeight - 10;
if (nearBottom && !isLoading && hasMore) {
loadNextPage();
}
});
}, 0);
Проверка -10 пикселей используется для компенсации
инерции прокрутки и особенностей рендера.
Функция догрузки расширяет список без его сброса:
function loadNextPage() {
isLoading = true;
currentPage += 1;
fetchPage({ search: currentSearch, page: currentPage })
.then(data => {
if (data.items.length < 20) {
hasMore = false;
}
select.add(data.items);
})
.finally(() => {
isLoading = false;
});
}
Метод add используется для добавления новых элементов
без очистки текущего списка.
При изменении поискового запроса состояние пагинации должно сбрасываться, иначе новые данные будут некорректно смешиваться со старыми:
let currentSearch = '';
function loadFirstPage(search, callback) {
currentSearch = search;
currentPage = 1;
fetchPage({ search, page: currentPage })
.then(data => {
hasMore = data.items.length === 20;
callback(data.items);
});
}
Изменение запроса всегда приводит к полной перезагрузке списка.
При быстром скролле возможны множественные вызовы загрузки. Для предотвращения этого используется флаг блокировки:
if (isLoading || !hasMore) return;
Дополнительно применяется debounce для scroll-события:
function debounce(fn, delay) {
let t;
return function () {
clearTimeout(t);
t = setTimeout(() => fn.apply(this, arguments), delay);
};
}
Применение:
dropdown.addEventListener('scroll', debounce(handleScroll, 100));
Cursor-based подход более устойчив к изменению данных на сервере. Вместо номера страницы используется курсор последнего элемента:
let cursor = null;
function fetchPage({ search, cursor }) {
let url = `/api/items?search=${search}&limit=20`;
if (cursor) {
url += `&cursor=${cursor}`;
}
return fetch(url).then(res => res.json());
}
Обновление состояния:
function loadNextPage() {
isLoading = true;
fetchPage({ search: currentSearch, cursor })
.then(data => {
cursor = data.nextCursor;
hasMore = !!data.nextCursor;
select.add(data.items);
})
.finally(() => {
isLoading = false;
});
}
Cursor-подход устраняет проблемы с дубликатами и изменением порядка элементов на сервере.
При смене контекста (например, нового поиска) требуется полная очистка:
select.setData([]);
currentPage = 1;
cursor = null;
hasMore = true;
После этого инициируется новый запрос первой страницы.
Поскольку Slim Select не отображает встроенный loader для infinite scroll, состояние загрузки реализуется вручную через добавление временного элемента:
function showLoader() {
select.add([{ text: 'Загрузка...', value: '__loading__' }]);
}
function hideLoader() {
select.setData(select.getData().filter(i => i.value !== '__loading__'));
}
Использование:
function loadNextPage() {
if (isLoading) return;
isLoading = true;
showLoader();
fetchPage(...)
.then(...)
.finally(() => {
hideLoader();
isLoading = false;
});
}
При работе с быстрым скроллом и большими наборами данных важна минимизация сетевых вызовов:
search+pageAbortControllerПример кеширования:
const cache = new Map();
function fetchCached(url) {
if (cache.has(url)) {
return Promise.resolve(cache.get(url));
}
return fetch(url)
.then(res => res.json())
.then(data => {
cache.set(url, data);
return data;
});
}
Реализация infinite scroll через DOM-контейнер Slim Select имеет ряд особенностей:
.ss-contentПри больших объёмах данных предпочтительнее ограничивать общее количество загруженных элементов или реализовывать серверную фильтрацию с агрессивной пагинацией.
На практике используется смешанный подход:
Такая модель позволяет добиться поведения, близкого к нативному infinite scroll, сохраняя совместимость с архитектурой Slim Select и не нарушая его внутреннюю логику управления списком опций.