Окей, друзья. Вот казалось бы, обычный список. Ну просто список. Примитивный. Просто отобразить элементы. Казалось бы — да что может пойти не так? А оно, как назло, лагает. Прокрутка дёрганая, всё тормозит, будто рендерим не текст, а 3D-анимацию с RTX на картошке. Знакомо?
А ведь задача тривиальна: показать 100–1000+ элементов и не сорваться в ад. И вот тут начинается пляска: кто-то лезет в оптимизацию рендера, кто-то в виртуализацию, а кто-то просто матерится и пишет "ну, тут тупо фреймворк тормозит, ничего не поделаешь". Но мы-то с тобой не такие. Мы копнём, чтобы выжать свои честные 60 fps. Не потому что надо, а потому что приятно, когда всё летает.
1. Понимание проблемы: FPS — это не магия, а физика
Если у тебя при прокрутке списка FPS падает ниже 60 — это значит, что главный поток (UI thread) не успевает за кадр (а у тебя на каждый кадр всего-то ~16.6 мс). Проскочил мимо — список подёрнулся, кадр пропущен, ты злишься.
Всё, что ты добавляешь в список — текст, иконки, изображения, анимации, даже отступы — это дополнительная нагрузка. И если ты всё рендеришь разом и на лету — готовься к просадкам.
2. Virtualization рулит. Остальное — понты
Если ты ещё не используешь виртуализацию — ну, прости, но ты живёшь как будто в 2010. Реальный список, у которого 1000 элементов и всё это одновременно в DOM/в дереве/на экране — это просто самоубийство.
Virtual scroll (виртуализация) — показывает только то, что реально видно пользователю. Остальное в спячке. Не рендерится. Не отрисовывается. Не грузит память. Классика жанра.
Есть встроенные решения:
- React — react-window, react-virtualized
- Vue — vue-virtual-scroller
- Своё самописное решение на канвасе, если ты true задрот и не боишься крови.
Ставишь — и уже дышится легче.
3. Реюзай, не пересоздавай
Частая ошибка — при скролле заново пересоздавать каждый элемент. Типа "ну а чо, быстро же". Не быстро. Особенно если ты, допустим, на каждом элементе перезапрашиваешь данные, пересоздаёшь обработчики событий, пересчитываешь стили.
Решение простое, как хруст в шее: реюз компонентов. Не пересоздавай DOM, не пересоздавай объекты. Один раз отрисовал — дальше просто меняй данные.
Пример: если у тебя в списке карточки товаров — не надо каждый раз пересоздавать весь JSX/DOM, просто передай в реюзнутый компонент новый props.
4. Минимизируй пересчёты и перерасчёты
Тут уже чуть сложнее. Часто тормоза — это не сами элементы, а то, что они запускают кучу каскадных перерасчётов. Поменял высоту одного блока — и весь список заново вычисляет layout. Печально.
Решение:
- Задай фиксированную высоту элементов, если можешь.
- Используй CSS transform/translate вместо top/left — они не запускают layout-расчёты.
- Минимизируй reflow (да, старое слово, но актуальное).
- И, блин, не ставь box-shadow в каждый элемент — это же убийство GPU!
5. Изображения: самые коварные тормоза
Ты думаешь, картинка — это просто? А она — как мина. Поставил одну неаккуратно — и всё, у тебя уже вместо 60 fps — PowerPoint.
- Не загружай full-res картинки на скролле. Используй placeholder'ы, lazy load, замены типа blur-up.
- Используй сжатые форматы — WebP, AVIF, или хотя бы уменьшай jpg.
- Отрисовывай картинки на фоне, если их не надо интерактивно крутить.
- Кэшируй, мать его. Если ты каждую прокрутку грузишь аватарку из сети — ты не оптимизатор, ты диверсант.
6. Batch update или как не убить фрейм
Когда пользователь скроллит, не надо пушить в интерфейс каждое изменение по отдельности. Ты создаёшь очередь обновлений, которую UI-поток просто не успевает проглотить.
Вместо этого:
- Группируй обновления.
- Используй requestAnimationFrame.
- В React — useTransition и useDeferredValue (если ты на последней версии).
- В Vue — nextTick и батчинг реактивности.
Не надо кормить интерфейс по чайной ложке, накорми разом — и пусть работает.
7. Профилируй как бог
Если ты не включал профайлер, ты не знаешь, где у тебя узкое горлышко. Даже не начинай оптимизировать вслепую. Ты же не лечишь болезнь по интуиции, правда?
- Chrome DevTools → Performance
- React Profiler
- Flipper для React Native
- Firefox Profiler — кстати, тоже мощный
Посмотри, где залипает main thread. Что дольше всего жрёт кадры. Какие компоненты чаще всего пересоздаются. И только потом — оптимизируй.
8. Не забывай про feel
Ну и вишенка: 60 fps — это не только про технику, но и про впечатление. Иногда у тебя может быть 60 кадров, но ощущение всё равно "медленно". Потому что input-лаг, потому что "неприятная" анимация, потому что интерфейс ведёт себя неестественно.
Добавь лёгкий инерционный скролл, убери рывки, сгладь переходы. Пусть всё не просто летает, а порхает, как бабочка, а не шарахается, как трамвай.
Итог такой: 60 fps для списка — это не роскошь, а минимальный стандарт UX. Не надо делать ракеты из простых списков, но и забивать на производительность тоже нельзя. Всё, что ты рендеришь — имеет цену. И если ты хочешь, чтобы твой интерфейс воспринимался "легким" — придется попотеть. Но оно того стоит.
А теперь вопрос тебе, дорогой друг: как ты решаешь проблему лагов в списках — латаешь последствия или копаешься в корне? Какой твой любимый приём оптимизации? Делись в комментах, не будь одиночкой в этом лаговом аду.