Про infinite scrolling

Бесконечный скролл, т.е. постепенный показ и подгрузка данных как реакция на действия пользователя, закрепился в SPA-приложениях. Я работаю с angular, но в целом это может и к backbone относиться..

Технически, данные на frontend хранятся в какой-то коллекции/сервисе. То что показывается пользователю — подмножество этих данных, т.е. может быть отфильтрованным, отсортированным и изменяемым на лету. Таких подмножеств в приложении может быть несколько, в зависимости от отображения (view).

В одном месте у вас товары одного бренда, в другом месте у вас заказанные товары, в третьем — товары конкретного пользователя.

Что значит «ещё»?

Это значит, что вопрос подгрузки новых данных — нетривиальная задача.

Сначала я подумал что лучше всего запрашивать данные по времени. У каждого ряда данных будет timestamp, как у твиттера, соответсвенно запрос будет высылаться «подгрузи 20 рядов данных, старше X», а время будет браться у последнего ряда.

Но тут возникает несколько проблем.

  1. Что если у данных одно и то же время? Если на backend добавить вторую сортировку по ID, и передавать ID последнего ряда вместе со временем, то это всё-равно не спасает положения, т.к. значительно усложняется вычисление OFFSET в SQL запросе.
  2. Если запрос зависит от последнего показанного ряда, то получается что запрос формируется на основе подмножества, т.е. зависит от view. Это плохо, т.к. завязывает модель, контроллер и view в единое целое. Альтернатива — дублировать фильтрацию которая происходит для view в модели.
  3. Что если данные обновились со времени последнего запроса? Надо ли их добавлять в начало списка?
  4. Что делать если данные отсортированы не по времени, а по алфавиту? И они вдруг поменялись? 

Вобщем приходишь к тому, что для универсального решения, надо по-прежнему работать с OFFSET и ORDER BY, как то было с постраничной навигацией. При этом offset высылается в зависимости от нужды view и он равен размеру подмножества.На случай обновившихся данных, я использую socket.io, который оповещает что надо обновить. Добавившиеся данные я сразу внедряю в список, а update происходит автоматически

Когда загружать?

ngInfiniteScroll — простенькая ангулярная директива для ангуляра, которая триггерит нашу функцию подгрузки если вы достаточно проскроллили вниз (scrollTop() близок к высоте элемента). Проблемы в ней две — она привязана ко всему окну (window). Обычно же у нас может быть несколько контейнеров на странице где надо подгружать данные. Есть и более простой пример

И вторая проблема следующая из неё — если вы запросили слишком мало данных за запрос (LIMIT 5), и/или их высота динамическая и меньше чем высота контейнера, то ваш scroll никогда не появится и не запустит постоянное дополнение списка. Пришлось это поисправлять, каждый раз расчитывая высоту содержания контейнера.

Тут главное не заDDOSить сервер из-за неправильного расчёта высоты элементов и рекурсии. По-хорошему вызов подгрузки надо начинать до достижения нижнего края.

Search

Есть небольшая проблема, если у вас есть фильтрация коллекции с поиском. Я решаю эту проблему отдельным мини-кэшем и отдельными запросами. Если вы ищете что-то по ключевому слову, то результаты не добавляются в основную коллекцию списка, поскольку это повлияет на offset при нормальном режиме. Search и отфильтрованные данные у меня подгружаются без постраничной навигации, т.е. их количество ограничено

Когда использовать не стоит

Бесконечный скролл напрямую зависит от скролла. Замечали сайты на которых открываешь в телефоне страничку с контактами, а там google map на всю высоту экрана? И не добраться до текста с телефонами ниже.. Бесконечный скролл, если он достаточно высокий, аналогично блокирует данные ниже себя.

Во-вторых, бесконечность дезориентирует и лишает контроля. Вы не знаете сколько вы уже проскроллили, дажа примерно глядя на размер бегунка, не можете понять сколько осталось до конца. Аналогично, вы не можете перепрыгнуть в «значительно более далёкие» версии данных.

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

Само собой, если у вас SPA, то могут быть проблемы с индексацией поисковиками. Гугл предлагает гибридный вариант, где данные таки будут доступны и по страницам тоже.

Наконец, если у вас что-то случилось с ajax-запросом и нет хорошей обработки ошибок, бесконечный скролл ломает глубину просмотра. А если сервер достаточно нестабильный, то риск что пользователь никогда не сможет добраться до нужных данных увеличивается с каждым запросом. Например если вероятность ответа сервера HTTP 500 оценивается в 5%, то после 15ти последовательных запросов вероятность что лента будет сломана уже (100 — 100 × 0.9515) = 55%.

Тем не менее, если вы делаете крутой динамический сайт, который может обновляется блоками с нестатичными размерами и с нестандартной навигацией вверх/вниз и вправо/влево, постепенная подгрузка musthave.