Infinite Scroll в мобильном приложении: правильная реализация

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

Разработка и поддержка любых видов мобильных приложений:

Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

Это лишь некоторые из типы мобильных приложений, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента.

Услуги, которые мы предлагаем
Показано 1 из 1Все 1734 услуг
Infinite Scroll в мобильном приложении: правильная реализация
Средний
от 1 дня до 3 дней
Часто задаваемые вопросы

Infinite scroll в мобильном приложении: где ломается лента

Infinite scroll выглядит как простой приём: пользователь доходит до низа ленты контента — приложение подгружает следующую порцию. На практике бесконечная прокрутка ломается в четырёх предсказуемых местах, и каждое из них заказчик видит как «приложение тормозит» или «карточки задваиваются».

Первый сбой — дубли карточек. Подгрузка данных дёргается по нескольким событиям сразу: скролл, обновление списка, возврат из фона. Два запроса уходят параллельно, ответы приходят в разном порядке, и в ленте появляются одинаковые элементы. Лечится это не «защитой от дурака», а единым источником правды: страницы нумеруются, ответы склеиваются по идентификатору, а повторный запрос блокируется до завершения предыдущего.

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

Третий — рывки при быстрой прокрутке. Пока данные грузятся, в ленте пустота или подсказка загрузки, и палец проскакивает мимо ещё не отрисованных карточек. Здесь работает предзагрузка за экран и заглушки-скелетоны фиксированной высоты: список не меняет геометрию, прокрутка остаётся плавной.

Четвёртый — утечки памяти на длинной сессии. Час листания — и в памяти висят тысячи отрисованных карточек и подвисших запросов. Решение — переиспользование элементов и отмена запросов, которые уже не нужны.

Разбираем, как это закрывается в реальной разработке.

Что даёт корректная реализация подгрузки контента

Корректно сделанный infinite scroll — это не «лента, которая просто докручивается», а предсказуемая система подгрузки: страница запрашивается заранее, до того как пользователь дойдёт до края, элементы не дублируются при повторных запросах, а память не растёт линейно с глубиной прокрутки. Разница видна в метриках: отклик приложения остаётся ровным на сотнях экранов вниз, а не деградирует к третьей-четвёртой сотне элементов.

Первый измеримый эффект — стабильность списка. Когда ключи элементов уникальны, а вставка новых страниц идёт без пересборки уже отрисованных, пропадают дубли и «прыжки» позиции при возврате на экран. Пользователь, свернувший приложение на середине ленты и вернувшийся через час, оказывается там же, где остановился, — доля отказов при возврате падает, потому что экран не перезагружается с нуля.

Второй эффект — предсказуемое потребление памяти. Лента удерживает в памяти окно вокруг видимой области, а не все загруженные элементы: отработавшие уходят из дерева, кэш изображений ограничен по размеру. На длинных сессиях это снимает рост потребления и связанные с ним принудительные выгрузки приложения системой.

Третий — ровный отклик приложения на прокрутку. Подгрузка следующей страницы запускается с запасом, параллельно с инерцией скролла, а не в момент упора в низ; при медленной сети показывается скелетон, а не пустой экран. Вместе это даёт производительность ленты, которая не зависит от того, как долго пользователь листает, — и именно её мы закладываем в реализацию.

Варианты подгрузки: страницы, курсор, окно просмотра

Есть три способа отдавать данные в ленту, и выбор между ними определяет, как приложение поведёт себя на быстром скролле и при живых правках контента.

Вариант Когда подходит Ограничения
Постраничная загрузка со смещением Статичные каталоги, редкие вставки, простые списки При добавлении записей в начало ленты элементы дублируются и «прыгают»: смещение страниц считается от начала, а оно уже сдвинулось
Курсорная пагинация Живые ленты, чаты, маркетплейсы с частыми правками Нужен стабильный ключ сортировки и поддержка курсора на стороне сервиса; переход на произвольную страницу невозможен
Окно просмотра Ограниченный набор данных, который уже пришёл целиком Память растёт вместе с объёмом, а свежие записи не подтягиваются без перезапроса

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

Курсорная пагинация держит позицию по последнему полученному элементу, а не по его порядковому номеру. Вставки и удаления выше курсора не сдвигают выборку, поэтому дублей нет даже при активных правках. Цена — серверная логика: нужен стабильный ключ сортировки, а переход «сразу на середину» списка становится неочевидным.

Окно просмотра работает иначе: данные приходят одним набором, а на экран попадает только видимая часть. Это снимает нагрузку с сервера, но перекладывает её на память устройства — на больших объёмах приложение начинает тормозить. Предзагрузка следующей порции за 1-2 экрана до конца списка сглаживает ожидание, но не отменяет ограничений выбранной схемы.

Этапы внедрения: от аудита ленты до передачи команде

Внедрение infinite scroll начинается не с кода, а с аудита ленты: смотрим, как приложение сейчас отдаёт данные, где кэшируются страницы, какие запросы уходят при быстром скролле. На этом шаге становится видно, что именно тормозит — размер ответа, отсутствие пагинации на бэкенде или лишние перерисовки списка. Без аудита любая настройка превращается в подгонку симптомов.

Последовательность работ

  1. Аудит ленты. Разбираем текущий поток данных: сколько запросов уходит на один экран, как ведёт себя приложение при потере сети, что происходит с памятью при длинной прокрутке. Фиксируем узкие места в отчёте.
  2. Схема загрузки. Проектируем контракт: размер страницы, курсор вместо смещения, поведение при дублирующихся записях, стратегия кэша и повторных попыток. Схема согласуется с бэкендом — иначе клиент и сервер начнут расходиться в ожиданиях.
  3. Настройка. Реализуем подгрузку с упреждающим запросом за 1-2 экрана до конца списка, индикатором загрузки и защитой от параллельных одинаковых запросов. Отдельно закрываем сценарии: пустой ответ, ошибка сети, конец ленты.
  4. Тестирование прокрутки. Прогоняем на реальных сценариях — медленная сеть, резкий флик до низа, возврат из фона, поворот экрана. Проверяем, что позиция сохраняется, а память не растёт бесконечно.
  5. Передача кода. Отдаём команде заказчика репозиторий с изменениями, описание контракта загрузки, инструкцию по правке размера страницы и чек-лист проверок перед релизом.

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

Артефакты: конфигурации, схемы состояний, регламенты

Разработка ленты не заканчивается рабочим экраном. Чтобы приложение можно было развивать, передавать другой команде и безопасно менять поведение подгрузки, мы фиксируем решения в артефактах — от модели данных до регламента сопровождения. Ниже — что именно остаётся у вас на руках после сдачи.

  • Конфигурация ленты — описание параметров подгрузки: размер страницы, порог срабатывания дозагрузки, поведение при пустом ответе и при исчерпании данных. Меняется без переписывания экрана.
  • Схема состояний — диаграмма переходов между «загрузка», «данные есть», «пусто», «ошибка», «конец списка» и правил возврата из каждого состояния. По ней видно, где именно лента может «залипнуть».
  • Правила кэширования — что и на какой срок хранится локально, как инвалидируется при обновлении, как ведёт себя приложение без сети и при возврате из фона.
  • Модель данных — структура элемента списка, ключи пагинации, правила дедупликации при повторной подгрузке и слиянии страниц.
  • Документация — текстовое описание логики подгрузки, точек расширения и типовых сценариев изменения: новый фильтр, смена источника, добавление типа карточки.
  • Регламент сопровождения — что проверять при релизе, какие метрики смотреть (доля ошибок дозагрузки, время до первого экрана, глубина прокрутки), как откатывать изменения ленты.
  • Схемы интеграции — как лента связана с аналитикой, поиском и офлайн-режимом, чтобы новые модули не ломали пагинацию.

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

Кейс: как лента перестала дублировать карточки

Заказчик пришёл с классической жалобой: пользователи видят одни и те же карточки в ленте, а при быстрой прокрутке вниз список на секунды замирает. Метрики подтвердили диагноз: доля дублей доходила до 8% показов, отклик на быстрый свайп проседал до 400–600 мс, а память на длинной сессии росла линейно — приложение доходило до 700 МБ и система начинала выгружать процесс в фоне.

Что было под капотом

Лента тянула страницы по номеру offset, а ключом элемента служил индекс в массиве. Стоило данным на сервере переупорядочиться между запросами — и один и тот же товар приезжал дважды под разными индексами, а часть позиций пропадала. Кэш держал все загруженные страницы в памяти без вытеснения, поэтому долгая сессия превращалась в утечку.

Что изменили

Перешли на курсорную пагинацию с непрозрачным токеном вместо номера страницы — сервер отдаёт «следующий после вот этого», и вставки в середину больше не сдвигают окно. Ключом элемента сделали стабильный идентификатор записи, а не позицию: повторный приход той же сущности теперь схлопывается на уровне хранилища. Добавили окно удержания — держим в памяти текущий экран и по паре экранов в каждую сторону, остальное вытесняется, а картинки грузятся по мере приближения к границе.

Что стало

Доля дублей упала до нуля — повторные показы ловятся на входе. Отклик на быструю прокрутку сократился до 80–120 мс, потому что подгрузка идёт заранее, за экран до края, и не блокирует кадр. Память на длинной сессии стабилизировалась в районе 180–220 МБ и перестала расти со временем: вытеснение работает независимо от того, сколько пользователь листает.

Отдельно закрыли откат: если сервер вернёт устаревший токен, лента не рвётся, а тихо перезапрашивает окно с последней валидной позиции. Кейс маркетплейса показал главное — infinite scroll ломается не на анимации, а на модели данных под ней.

Чем курсорная пагинация отличается от страничной

Страничная пагинация опирается на смещение: клиент запрашивает «первые 20», затем «следующие 20» — сервер отсчитывает записи от начала выборки. Пока данные статичны, всё предсказуемо. Но лента живая: между запросами приходит новый пост, и всё, что ниже, сдвигается вниз на одну позицию. Следующая страница начинается уже не с той записи — пользователь видит дубликат или теряет элемент, который так и не показался. Это не баг клиента, а свойство самого смещения: оно не привязано к конкретной записи, только к её порядковому номеру в моменте.

Курсорная пагинация вместо номера страницы передаёт указатель на последнюю полученную запись — её уникальный ключ и значение поля сортировки. Сервер возвращает всё, что идёт строго после этой точки. Вставка новых записей сверху на такую выборку не влияет: курсор указывает на конкретный элемент, а не на позицию. Пользователь листает ленту без дубликатов и пропусков — это и есть консистентность данных при чтении.

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

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

Технические сомнения: утечки, дубли, возврат на экран

Infinite scroll почти всегда ломается не на первом экране, а на возврате: пользователь уходит в карточку товара, возвращается — и лента прыгает в начало. Причина в том, что список пересоздаётся заново, а состояние подгрузки и позиция скролла живут в памяти экрана, который система выгрузила при уходе в фон. Лечится это сохранением состояния ленты отдельно от самого экрана: смещение, загруженные страницы и признак «данные ещё есть» кладём в модель, переживающую пересоздание, и при возврате восстанавливаем позицию до отрисовки, а не после.

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

Почему лента теряет позицию после сворачивания приложения?

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

Как ограничить память при длинной сессии?

Держать в памяти всю прокрученную ленту нельзя — на длинной сессии это приводит к росту потребления и падению. Мы ограничиваем окно: храним последние N экранов плюс запас, остальное выгружаем, а при возврате наверх подтягиваем заново. Пользователь этого не замечает, а потребление памяти перестаёт расти с длиной сессии.

Что делать, если данные на сервере изменились между загрузками?

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

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

Обсудим задачу и вернёмся с оценкой и планом

Чтобы предложить по делу, а не «по прайсу», нам нужно понять вашу текущую ленту: как она устроена сейчас, где уже проседает, какие сценарии для вас критичны. На первом разговоре разбираем не абстрактные пожелания, а конкретику — что развёрнуто, чем меряется, что болит.

После этого возвращаемся с оценкой работ и планом внедрения: что делаем в первую очередь, что можно отложить, где риски. Ниже — что стоит держать под рукой к обсуждению.

  • Как сейчас устроена подгрузка: страницы, курсор, размер порции — и что происходит на медленной сети.
  • Какие сценарии критичны: лента товаров, чат, соцфид, поиск — у каждого свои требования к подгрузке.
  • Что происходит при сбое: пустой экран, дубли карточек, потеря позиции при возврате — какие из этих симптомов уже видели.
  • Есть ли кэш и офлайн-режим, как ведёт себя приложение при обрыве связи.
  • Как меряется качество: скорость первой порции, доля догрузок, отказы на длинной прокрутке.
  • Что уже пробовали и почему не устроило — чтобы не повторять пройденное.
  • Ограничения по срокам и релизному циклу: когда нужен результат и что нельзя ломать в текущей версии.

Опишите задачу в двух словах — какое приложение, какая лента, что беспокоит. Вернёмся с оценкой и планом, без общих слов.

Подход к подгрузке Как работает Где уместен Что даёт клиенту
Страничная пагинация Лента запрашивается порциями с фиксированным номером страницы Каталоги с предсказуемым порядком, разделы с фильтрами Простая логика, понятный возврат на экран
Курсорная пагинация Следующая порция берётся относительно последней видимой записи Бесконечные ленты, новостные потоки, маркетплейсы Нет дублей и пропусков при обновлении данных
Оконный просмотр Приложение держит в памяти только видимую область списка Длинные ленты с тяжёлыми карточками Плавная прокрутка, стабильная работа на слабых устройствах
Гибридный вариант Курсор для основной ленты, страницы для фильтров и поиска Крупные приложения со смешанными сценариями Предсказуемость там, где нужна, и скорость там, где важна
Этап внедрения Что происходит Кто участвует Типовой срок
Аудит текущей ленты Разбор сценариев прокрутки, поиск дублей и обрывов Аналитик, ведущий разработчик 3–5 дней
Проектирование схемы состояний Описание загрузки, ошибки, пустого экрана, возврата Разработчик, проектировщик взаимодействия 4–7 дней
Реализация подгрузки Курсор или окно просмотра, обработка границ списка Команда мобильной разработки 1–3 недели
Тестирование на реальных сценариях Быстрая прокрутка, слабая сеть, возврат на экран Тестировщик, разработчик 4–6 дней
Передача команде Регламенты, схемы, разбор типовых сбоев Ведущий разработчик, поддержка 2–3 дня
Слой решения Что закрывает На что влияет у клиента
Слой данных Запросы порций, курсоры, кэш ответов Лента не дублирует карточки при возврате
Слой состояний Загрузка, ошибка, конец списка, повтор Понятные экраны вместо «пустоты»
Слой интерфейса Подгрузка у границы списка, индикаторы Прокрутка ощущается непрерывной
Слой наблюдаемости Сбои подгрузки, долгие ответы, отказы Проблемы видны до жалоб пользователей

Работы ведём на профессиональном стеке мобильной разработки — клиент получает предсказуемый результат на всём парке устройств.

Наши компетенции:

Последние работы

  • Разработка мобильного приложения для компании FEEDME
    Разработка мобильного приложения для компании FEEDME
    1010
  • Разработка мобильного приложения для компании XOOMER
    Разработка мобильного приложения для компании XOOMER
    903
  • Разработка мобильного приложения для компании RHL
    Разработка мобильного приложения для компании RHL
    1332
  • Разработка мобильного приложения для компании ZIPPY
    Разработка мобильного приложения для компании ZIPPY
    1180
  • Разработка мобильного приложения для компании Affhome
    Разработка мобильного приложения для компании Affhome
    1107
  • Разработка мобильного приложения для компании FLAVORS
    Разработка мобильного приложения для компании FLAVORS
    714