Бесконечная прокрутка: реализация на HTML, CSS и JavaScript

Кратко
Бесконечная прокрутка подгружает новый контент по мере прокрутки страницы, улучшая плавность просмотра на мобильных устройствах. Эта статья показывает простой шаг‑за‑шаговый подход на чистом HTML, CSS и JavaScript с советами по производительности и доступности.
Введение
Бесконечная прокрутка (infinite scroll) позволяет подгружать элементы автоматически по мере прокрутки вниз, в отличие от классической пагинации, где пользователь нажимает кнопку или ссылку. Это удобно для лент контента, галерей и каталогов — особенно на мобильных устройствах — но требует аккуратного управления запросами и состояния загрузки.
Настройка фронтенда
Ниже — минимальная HTML-структура для демонстрации. Пример сохраняет подключение CSS и JavaScript.
Страница с бесконечной прокруткой
Этот файл содержит контейнер с плейсхолдерными изображениями и ссылки на style.css и script.js.

Стили CSS для прокручиваемого контента
Чтобы расположить изображения в сетке, добавьте следующий CSS в файл style.css:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html { font-size: 62.5%; }
body {
font-family: Cambria, Times, "Times New Roman", serif;
}
h1 {
text-align: center;
font-size: 5rem;
padding: 2rem;
}
img {
width: 100%;
display: block;
}
.products__list {
display: flex;
flex-wrap: wrap;
gap: 2rem;
justify-content: center;
}
.products__list > * {
width: calc(33% - 2rem);
}
.loading-indicator {
display: none;
position: absolute;
bottom: 30px;
left: 50%;
background: #333;
padding: 1rem 2rem;
color: #fff;
border-radius: 10px;
transform: translateX(-50%);
}После добавления вышеуказанных стилей страница должна выглядеть примерно так:
Базовая реализация на JavaScript
В script.js нужно отслеживать прокрутку и определять, когда пользователь приблизился к концу документа, чтобы подгрузить дополнительные данные.
"use strict";
window.addEventListener("scroll", () => {
if (
window.scrollY + window.innerHeight >=
document.documentElement.scrollHeight - 100
) {
// Пользователь близок к концу страницы — подгружаем контент
fetchMoreContent();
}
});Затем реализуем функцию, которая будет запрашивать плейсхолдеры с API и логировать ответ для отладки.
async function fetchMoreContent() {
try {
let response = await fetch("https://fakestoreapi.com/products?limit=3");
if (!response.ok) {
throw new Error("Сетевой ответ не в порядке");
}
let data = await response.json();
console.log(data);
} catch (error) {
console.error("Проблема при получении нового контента:", error);
} finally {
console.log("Функция fetch была вызвана");
}
}Для этой демо‑страницы мы используем API с fakestoreapi.com.
При прокрутке вы увидите вызовы в консоли:

Предотвращаем множественные параллельные запросы
Если не ограничивать количество одновременных запросов, при быстрой прокрутке браузер отправит много запросов и устройство может замедлиться. Введите флаг состояния загрузки:
let isFetching = false;И обновите функцию fetchMoreContent, чтобы она игнорировала вызов, если предыдущий всё ещё выполняется:
async function fetchMoreContent() {
if (isFetching) return; // Выход, если уже происходит загрузка
isFetching = true; // Устанавливаем флаг
try {
let response = await fetch("https://fakestoreapi.com/products?limit=3");
if (!response.ok) {
throw new Error("Сетевой ответ не в порядке");
}
let data = await response.json();
} catch (error) {
console.error("Проблема при получении нового контента:", error);
} finally {
console.log("Функция fetch была вызвана");
isFetching = false; // Сбрасываем флаг
}
}Отображение нового контента
Чтобы добавить полученные элементы в DOM, сначала выберите контейнер:
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; // Придерживайтесь осмысленных alt для доступности
productsList.appendChild(imgElement);
});
}И обновите fetchMoreContent, чтобы передавать полученные данные в displayNewContent:
async function fetchMoreContent() {
if (isFetching) return;
isFetching = true;
try {
let response = await fetch("https://fakestoreapi.com/products?limit=3");
if (!response.ok) {
throw new Error("Сетевой ответ не в порядке");
}
let data = await response.json();
displayNewContent(data);
} catch (error) {
console.error("Проблема при получении нового контента:", error);
} finally {
console.log("Функция fetch была вызвана");
isFetching = false;
}
}После этих изменений бесконечная прокрутка будет подгружать и отображать новые элементы.

Улучшения: индикатор загрузки
Покажите пользователю, что идёт загрузка. Добавьте HTML‑элемент для индикатора:
Загрузка...
Выберите элемент в JS:
const loadingIndicator = document.querySelector(".loading-indicator");Реализуйте две функции для показа/скрытия индикатора:
function showLoadingIndicator() {
loadingIndicator.style.display = "block";
console.log("Загрузка...");
}
function hideLoadingIndicator() {
loadingIndicator.style.display = "none";
console.log("Загрузка завершена.");
}И вызывайте их в fetchMoreContent:
async function fetchMoreContent() {
if (isFetching) return;
isFetching = true;
showLoadingIndicator();
try {
let response = await fetch("https://fakestoreapi.com/products?limit=3");
if (!response.ok) {
throw new Error("Сетевой ответ не в порядке");
}
let data = await response.json();
displayNewContent(data);
} catch (error) {
console.error("Проблема при получении нового контента:", error);
} finally {
console.log("Функция fetch была вызвана");
hideLoadingIndicator();
isFetching = false;
}
}Хорошие практики
- Не запрашивайте слишком много элементов за один вызов — это может перегрузить память и сеть.
- Используйте debounce/терминг (задержку) при обработке события scroll, чтобы уменьшить частоту срабатываний.
- Предоставьте альтернативу: кнопка «Показать ещё» или классическая пагинация для пользователей, которые так предпочитают.
- Если данных больше нет, явно сообщите об этом пользователю и перестаньте отправлять запросы.
- Подумайте о lazy loading для изображений (
) и о кэшировании ответов на клиенте.
Доступность и SEO
- У каждого изображения должен быть осмысленный alt‑тег.
- Бесконечная прокрутка может затруднить индексацию и навигацию для пользователей клавиатуры и скринридеров. Для критичного контента предоставьте семантическую пагинацию или aria‑метки, указывающие на состояние загрузки.
Когда бесконечная прокрутка не подходит
- Если пользователю важно добраться до футера с информацией (контакты, права, ссылки), бесконечная прокрутка может мешать.
- Для поискового и каталожного контента, где пользователи привыкли к страницам и URL, традиционная пагинация будет удобнее.
Альтернативные подходы
- «Показать ещё» — сочетает контроль пользователя и ленивую загрузку.
- Пагинация — улучшает предсказуемость навигации и SEO.
- Виртуализация списка (windowing) — для больших коллекций экономит память (пример: библиотеки react-window, virtual-scroller).
Быстрый чек‑лист для внедрения
- Определить порог срабатывания (например, 100px от низа).
- Ввести флаг isFetching и throttling/debounce на scroll.
- Добавить индикатор загрузки и сообщения об ошибке.
- Проверить доступность (alt, aria-live для статуса загрузки).
- Ограничить размер пакета данных и поддержать lazy loading изображений.
Факт‑бокс
- Подходит для: ленты новостей, галерей, социальных фидов.
- Не подходит для: каталогов с важной пагинацией, страниц с критичным SEO.
- Ключевые приёмы: debounce, флаг загрузки, индикатор состояния, явный конец списка.
Примеры отказа и тесты приёмки
- Отказ: при очень медленном соединении множественные запросы могут накапливаться — тест: симулировать медленную сеть и убедиться в единственном активном запросе.
- Критерии приёмки:
- Новые элементы корректно подгружаются при достижении порога.
- Индикатор загрузки отображается при запросе и скрывается по завершении.
- Нет наложения одинаковых запросов при быстрой прокрутке.
- Скринридер получает уведомление об обновлении контента (aria‑live).
Часто задаваемые вопросы
Что лучше: бесконечная прокрутка или пагинация?
Это зависит от сценария. Для непрерывного потребления контента (ленты, социальные сети) — бесконечная прокрутка удобна. Для каталогов и SEO‑важных страниц лучше пагинация.
Как избежать перегрузки запросами?
Используйте флаг isFetching, throttle/debounce при обработке scroll и ограничьте количество элементов в одном запросе.
Как обеспечить доступность?
Добавьте осмысленные alt‑теги для изображений, aria‑live регион для уведомлений о новой порции контента и альтернативную навигацию (кнопка «Показать ещё»).
Итог
Бесконечная прокрутка — мощный инструмент для улучшения UX при просмотре длинных списков, особенно на мобильных устройствах. Главное — контролировать частоту запросов, показывать пользователю статус загрузки и думать о доступности и альтернативной навигации.
Похожие материалы
Как разделить меш в Blender
Как увеличить изображение без потери качества
Как создать влог на iPhone — полное руководство
Как отразить экран на телевизор — все способы
Бесконечная прокрутка в Vue 3 — useInfiniteScroll