Перейти к содержимому

Подгрузка списка порциями - кнопка «Показать ещё» и прокрутка

Догружаем список без перезагрузки страницы: штатная подгрузка в каталоге, своя порция через контроллер, адреса страниц для поиска и защита от повторов в выдаче.

Что нужно знать заранее

В каталоге подгрузка уже есть, и начинают всегда с неё. Компоненты списка умеют показывать кнопку догрузки и подгружать следующую страницу при прокрутке до низа списка.

Своя подгрузка нужна лишь там, где список собирается собственным кодом проекта. Тогда порцию отдаёт действие контроллера, а браузер дописывает полученную разметку в конец контейнера.

Адреса страниц списка обязаны продолжать работать и после включения любой догрузки. Поисковый робот и посетитель без скриптов должны получать те же товары обычной постраничной навигацией.

Шаги

  1. Проверить, решается ли задача штатными параметрами того компонента, который выводит список.
  2. Завести действие контроллера, которое отдаёт готовую разметку ровно одной порции списка.
  3. Собрать выборку порции с постраничкой и устойчивой сортировкой, без повторов записей.
  4. Дописать разметку в контейнер списка и обновить адрес страницы в истории браузера.
  5. Спрятать кнопку, когда порции кончились, и оставить рабочими адреса всех страниц.

Решение

Включаем штатную подгрузку в каталоге:

$APPLICATION->IncludeComponent('bitrix:catalog.section', '', [
'IBLOCK_ID' => $iblockId,
'PAGE_ELEMENT_COUNT' => 12,
'LAZY_LOAD' => 'Y', // кнопка догрузки под списком
'LOAD_ON_SCROLL' => 'Y', // догрузка при прокрутке до конца списка
'MESS_BTN_LAZY_LOAD' => 'Показать ещё',
]);
// штатная подгрузка сохраняет обычную постраничную навигацию для поисковых роботов

Отдаём свою порцию действием контроллера:

public function pageAction(int $page = 1): string
{
ob_start();
$GLOBALS['APPLICATION']->IncludeComponent('local:news.portion', '',
['IBLOCK_ID' => 5, 'PAGE' => max(1, $page)], false, ['HIDE_ICONS' => 'Y']);
return ob_get_clean(); // действие возвращает готовую разметку порции строкой
}

Разметку собирает тот же компонент, что и первую страницу списка. Иначе вёрстка догруженных карточек однажды разойдётся с исходной, и заметит это уже заказчик.

Выбираем порцию с устойчивой сортировкой:

$nav = ['nPageSize' => 12, 'iNumPage' => $page, 'checkOutOfRange' => true];
$res = CIBlockElement::GetList(['SORT' => 'ASC', 'ID' => 'DESC'],
['IBLOCK_ID' => $iblockId, 'ACTIVE' => 'Y'], false, $nav, ['ID', 'NAME', 'DETAIL_PAGE_URL']);
printf("страниц всего: %d\n", $res->NavPageCount);
// вторым полем сортировки берут идентификатор: он разводит записи с равным весом

Сортировка только по одному полю даёт повторы между порциями. Записи с одинаковым значением база возвращает в произвольном порядке, и один товар приезжает дважды, а другой не приезжает вовсе.

Дописываем порцию и правим адрес:

BX.ajax.runAction('local:news.list.page', { data: { page: nextPage } })
.then((response) => {
document.querySelector('#list').insertAdjacentHTML('beforeend', response.data);
history.replaceState(null, '', '?PAGEN_1=' + nextPage); // адрес отражает показанное
if (nextPage >= totalPages) { button.remove(); } // порции кончились
});

Обновление адреса возвращает посетителя туда же после перезагрузки. Без него человек, открывший товар и нажавший «назад», снова видит первую порцию и уходит с сайта.

Кэшируем порцию с номером страницы в ключе:

$cache = \Bitrix\Main\Data\Cache::createInstance();
if ($cache->initCache(3600, 'portion_' . $iblockId . '_' . $page, '/news/portions')) {
$items = $cache->getVars();
} elseif ($cache->startDataCache()) { /* выборка и $cache->endDataCache(['items' => $items]); */ }
// номер страницы обязан входить в ключ, иначе все порции отдадут первую двенадцатку

Типичные проблемы

Один товар приезжает в двух порциях подряд.

Сортировка идёт по одному полю, и записи с равным значением возвращаются в произвольном порядке. Вторым полем сортировки добавляют идентификатор элемента, и порядок выдачи становится устойчивым.

Кнопка догружает одну и ту же порцию.

Номер страницы не входит в ключ кэша либо не увеличивается на стороне браузера. Проверяют оба места сразу: ключ кэша порции и счётчик страниц в скрипте.

Поисковый робот видит только первую страницу.

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

После возврата назад список снова начинается сверху.

Адрес страницы не обновляется при догрузке, и браузер открывает исходный первый список. Номер показанной страницы пишут в адрес браузера при каждой догрузке порции.

Вёрстка догруженных карточек отличается от первых.

Порция собирается отдельным куском разметки в контроллере, а не тем же компонентом. Порцию отдают тем же компонентом и тем же шаблоном, что и первую страницу.

Частые вопросы

Когда хватает штатной подгрузки?

Почти всегда в каталоге: у списков товаров есть кнопка догрузки и догрузка при прокрутке. Своё решение нужно спискам, собранным собственным кодом.

Что вернуть из действия: разметку или данные?

Разметку, если карточки собирает компонент, и данные, если разметку рисует скрипт. Смешанный вариант рано или поздно разводит вёрстку по двум местам.

Как не потерять индексацию?

Оставить рабочие адреса страниц списка и обновлять адрес при догрузке. Робот пройдёт по обычной навигации, а посетитель получит бесконечную ленту.

Почему товары повторяются между порциями?

Сортировка неустойчива: записи с одинаковым весом каждый раз выстраиваются по-разному. Вторым полем сортировки берут идентификатор элемента.

Нужно ли кэшировать порции?

Да, если выборка тяжёлая: порция кэшируется так же, как обычная страница списка. Номер страницы и все условия фильтра обязаны входить в ключ кэша.

Смежное

Первоисточник