Гид по технологиям

Бесконечная прокрутка на HTML/CSS/JS

4 min read Frontend Обновлено 30 Dec 2025
Бесконечная прокрутка на HTML/CSS/JS
Бесконечная прокрутка на HTML/CSS/JS

Иллюстрация человека, удобно сидящего в современном кресле и увлечённо использующего смартфон

Бесконечная прокрутка (infinite scroll) — это подход, при котором контент подгружается автоматически по мере приближения пользователя к концу страницы. В отличие от классической нумерованной пагинации, здесь нет явных страниц и кнопок «Дальше». Такой подход делает навигацию плавной, но требует аккуратной реализации, чтобы не ухудшать производительность и UX.

Настройка фронтенда

Ниже — минимальная структура HTML для демонстрации бесконечной прокрутки. Страница содержит контейнер для карточек/изображений и подключает CSS и JS-файлы.




  
  
  
  Infinite Scroll Page


  

Infinite Scroll Page

Куртка — пример изображения товара Куртка — пример изображения товара Куртка — пример изображения товара Куртка — пример изображения товара Куртка — пример изображения товара Куртка — пример изображения товара

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

Исходная страница после добавления HTML и CSS

Стилизация — style.css

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

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html { font-size: 62.5%; }

body {
  font-family: Cambria, Times, "Times New Roman", serif;
  background: #fafafa;
  color: #222;
  padding-bottom: 6rem; /* пространство для индикатора */
}

h1 {
  text-align: center;
  font-size: 3.2rem;
  padding: 2rem;
}

img {
  width: 100%;
  display: block;
  border-radius: 6px;
}

.products__list {
  display: flex;
  flex-wrap: wrap;
  gap: 2rem;
  justify-content: center;
  max-width: 1200px;
  margin: 0 auto;
  padding: 1rem;
}

.products__list > * {
  width: calc(33% - 2rem);
  min-width: 220px;
}

.loading-indicator {
  display: none;
  position: fixed;
  bottom: 30px;
  left: 50%;
  background: rgba(0,0,0,0.8);
  padding: 0.8rem 1.2rem;
  color: #fff;
  border-radius: 10px;
  transform: translateX(-50%);
  font-size: 1.4rem;
}

@media (max-width: 768px) {
  .products__list > * { width: calc(50% - 2rem); }
}

@media (max-width: 480px) {
  .products__list > * { width: 100%; }
}

Основная реализация на JavaScript

Идея простая: отслеживать положение скролла или использовать IntersectionObserver; при приближении к низу страницы — запрашивать новые данные и добавлять их в DOM.

Простой пример с обработчиком scroll (подойдёт для демонстрации):

"use strict";

let isFetching = false; // флаг, чтобы избежать параллельных запросов

window.addEventListener("scroll", () => {
  if (
    window.scrollY + window.innerHeight >=
      document.documentElement.scrollHeight - 100
  ) {
    // Пользователь близок к низу — загружаем ещё
    fetchMoreContent();
  }
});

async function fetchMoreContent() {
  if (isFetching) return;
  isFetching = true;

  try {
    const response = await fetch("https://fakestoreapi.com/products?limit=3");
    if (!response.ok) throw new Error("Network response was not ok");
    const data = await response.json();
    displayNewContent(data);
  } catch (error) {
    console.error("Ошибка при загрузке контента:", error);
  } finally {
    isFetching = false;
    console.log("Fetch function finished");
  }
}

const productsList = document.querySelector(".products__list");

function displayNewContent(data) {
  data.forEach((item) => {
    const imgElement = document.createElement("img");
    imgElement.src = item.image;
    imgElement.alt = item.title || "Изображение товара";
    productsList.appendChild(imgElement);
  });
}

Выше — минимальная рабочая логика: fetch → render → сброс флага. На практике стоит добавить индикатор загрузки, обработку пустого результата и отложенный запуск (debounce/throttle).

Подтверждение вызова fetch при прокрутке

Индикатор загрузки и улучшения UX

Добавьте в HTML элемент индикатора:

Загрузка...

Затем в JS выберите элемент и управляйте его видимостью:

const loadingIndicator = document.querySelector(".loading-indicator");

function showLoadingIndicator() {
  loadingIndicator.style.display = "block";
}

function hideLoadingIndicator() {
  loadingIndicator.style.display = "none";
}

async function fetchMoreContent() {
  if (isFetching) return;
  isFetching = true;
  showLoadingIndicator();

  try {
    const response = await fetch("https://fakestoreapi.com/products?limit=3");
    if (!response.ok) throw new Error("Network response was not ok");
    const data = await response.json();
    displayNewContent(data);
  } catch (error) {
    console.error("Ошибка при загрузке контента:", error);
  } finally {
    hideLoadingIndicator();
    isFetching = false;
  }
}

Альтернатива: IntersectionObserver (рекомендуется)

IntersectionObserver отслеживает пересечение целевого элемента с viewport и обычно более эффективен, чем слушатель scroll. Используйте «сентинел» — пустой див в конце списка — и наблюдайте за ним.

// Создаём элемент-сентинел в конце списка
const sentinel = document.createElement('div');
sentinel.className = 'sentinel';
productsList.after(sentinel);

const observer = new IntersectionObserver(entries => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      fetchMoreContent();
    }
  });
}, { rootMargin: '200px' });

observer.observe(sentinel);

IntersectionObserver снижает нагрузку на поток событий и упрощает логику debounce.

Лучшие практики

  • Не загружайте слишком много элементов за один запрос — используйте разумный лимит (например, 5–20 в зависимости от размера).
  • Ограничивайте одновременные запросы флагом isFetching и/или AbortController для отмены старых запросов.
  • Используйте debounce/throttle или IntersectionObserver, чтобы избежать сотен вызовов fetch при быстром скролле.
  • Показывайте индикатор загрузки и понятные сообщения при отсутствии контента.
  • Предоставьте альтернативу: кнопка “Загрузить ещё” или классическая пагинация для пользователей, которые её предпочитают.
  • Учитывайте доступность: клавиатурная навигация, скринридеры и уведомления для пользователей с ограничениями.

Когда бесконечный скролл не подходит

  • Если пользователи часто возвращаются к конкретным позициям в списке — пагинация даёт стабильные URL и якоря.
  • Если важен SEO для содержимого глубокой вложенности — пагинация или server-side rendering с каноническими ссылками предпочтительнее.
  • Для сложных фильтров и сортировки бесконечный поток может усложнить UX; рассмотрите смешанный подход.

Практические шаблоны и сниппеты (cheat sheet)

  • Debounce-функция (100–300 мс): задержка перед вызовом fetch
function debounce(fn, wait = 200) {
  let t;
  return (...args) => {
    clearTimeout(t);
    t = setTimeout(() => fn.apply(this, args), wait);
  };
}
  • Пример использования AbortController для отмены предыдущего запроса
let controller;
async function fetchWithAbort(url) {
  if (controller) controller.abort();
  controller = new AbortController();
  const signal = controller.signal;
  const res = await fetch(url, { signal });
  return res.json();
}

Роли и чеклист перед релизом

  • Frontend-разработчик: проверить debounce/обработку ошибок, memory leaks, доступность фокуса.
  • Backend-разработчик: поддержать пагинацию на API, корректные лимиты и заголовки кеширования.
  • UX/UI дизайнер: предусмотреть кнопку «Загрузить ещё», индикацию загрузки и состояние «конец контента».

Короткий чеклист:

  • Флаг isFetching или AbortController внедрён
  • Показаны состояния: загрузка, ошибка, конец списка
  • Тесты на медленном соединении
  • Доступность: aria-атрибуты и фокус

Критерии приёмки

  • При прокрутке загружаются новые элементы без дублирования.
  • Наблюдаемый элемент (или scroll-лист) не генерирует больше одного параллельного запроса.
  • Индикатор загрузки отображается во время запроса и исчезает при завершении.
  • При достижении конца контента отображается сообщение «Больше нет элементов».

Короткая сводка

Бесконечная прокрутка повышает вовлечённость, но требует заботы о производительности и доступности. Выберите IntersectionObserver для производительности, используйте флаги/AbortController, показывайте индикатор загрузки и всегда предлагайте альтернативный способ навигации для тех, кто предпочитает пагинацию.

Демонстрация работающей бесконечной прокрутки

План действий для внедрения: 1) реализовать базовый fetch+render, 2) добавить isFetching/AbortController, 3) заменить scroll на IntersectionObserver, 4) покрыть UX/доступность и протестировать на реальных устройствах.

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

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Голосовой PIN на Amazon Echo — как настроить
Безопасность

Голосовой PIN на Amazon Echo — как настроить

Как получить Apple TV+ бесплатно на 3 месяца
Streaming

Как получить Apple TV+ бесплатно на 3 месяца

Запуск Windows‑игр на Apple Silicon с CrossOver
Технологии

Запуск Windows‑игр на Apple Silicon с CrossOver

Извлечь текст из изображений и PDF через Google Drive
Инструменты

Извлечь текст из изображений и PDF через Google Drive

Как создать аватар в Facebook
Социальные сети

Как создать аватар в Facebook

Автоматический беззвучный режим на Android
Android.

Автоматический беззвучный режим на Android