Реализация бесконечной прокрутки

Тренажер по работе с HTML DOM на JavaScript для пользователей с начальным и средним уровнем подготовки.

Тренажер JavaScript: Покоряем HTML DOM

Бесконечная прокрутка (Infinite Scroll) — это техника, которая загружает новый контент автоматически при прокрутке страницы до конца, позволяя пользователю просматривать информацию без необходимости переключения между страницами. Этот подход широко используется в социальных сетях, блогах и других контент-платформах для улучшения пользовательского опыта. В этом тренажере вы научитесь реализовывать бесконечную прокрутку с использованием JavaScript — от простой детекции прокрутки до оптимизированной асинхронной загрузки данных с сервера. Вы будете работать с событиями прокрутки, Intersection Observer API, обработкой состояния загрузки и другими техниками, необходимыми для создания плавной и эффективной бесконечной прокрутки.

Список тем

Определение достижения конца страницы

В этом задании вам нужно реализовать функцию, которая определяет, когда пользователь прокрутил страницу до конца. Когда пользователь достигает конца страницы, в консоль должно выводиться сообщение 'Достигнут конец страницы'.

CSS
body {height: 200px;} 
 .content {
  font-family: Arial, sans-serif;
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.placeholder {
  background: linear-gradient(#f0f0f0, #d0d0d0);
}
HTML
Восстановить HTML
<div class="content">
  <h1>Определение конца страницы</h1>
  <p>Прокрутите страницу вниз, чтобы увидеть, как работает скрипт.</p>
  <div class="placeholder" style="height: 1500px;"></div>
  <p>Конец страницы</p>
</div>
JavaScript
function checkScrollEnd() {
  const scrollTop = input__1.scrollTop || document.body.scrollTop;
  const scrollHeight = input__2.scrollHeight || document.body.scrollHeight;
  const clientHeight = input__3.clientHeight || document.body.clientHeight;
  
  if (scrollTop + clientHeight >= scrollHeight - 5) {
    console.log('Достигнут конец страницы');
  }
}

input__4.addEventListener('input__5', checkScrollEnd);
Для определения достижения конца страницы используйте событие scroll и сравните сумму scrollTop и clientHeight с scrollHeight. Используйте window.addEventListener для прослушивания события прокрутки и document.documentElement для получения нужных значений.
Заполнить ответами все поля
Результат
Лог
Выполнить
Отметить решенным
Показать подсказку

Добавление новых элементов при прокрутке

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

CSS
body {height: 200px;} 
 .container {
  font-family: Arial, sans-serif;
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
}

#content p {
  padding: 15px;
  background-color: #f5f5f5;
  border-radius: 5px;
  margin: 10px 0;
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
HTML
Восстановить HTML
<div class="container">
  <h1>Динамическая загрузка контента</h1>
  <p>Прокрутите вниз, чтобы загрузить больше элементов.</p>
  <div id="content">
    <p>Элемент 1</p>
    <p>Элемент 2</p>
    <p>Элемент 3</p>
    <p>Элемент 4</p>
    <p>Элемент 5</p>
  </div>
</div>
JavaScript
let itemCount = 5;

function addItems() {
  const contentDiv = document.getElementById('input__1');
  
  for (let i = 0; i < 5; i++) {
    const newItem = input__2.createElement('input__3');
    itemCount++;
    newItem.input__4 = `Элемент ${itemCount}`;
    input__5.appendChild(newItem);
  }
}

function checkScrollEnd() {
  const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
  const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
  const clientHeight = document.documentElement.clientHeight || document.body.clientHeight;
  
  if (scrollTop + clientHeight >= scrollHeight - 10) {
    addItems();
  }
}

window.addEventListener('scroll', checkScrollEnd);
Используйте функцию для проверки достижения конца страницы и добавляйте новые элементы с помощью метода appendChild. Создавайте новые элементы с помощью document.createElement и устанавливайте их содержимое через textContent.
Заполнить ответами все поля
Результат
Лог
Выполнить
Отметить решенным
Показать подсказку

Индикатор загрузки при прокрутке

В этом задании нужно реализовать индикатор загрузки, который отображается при достижении конца страницы. После имитации загрузки (с помощью setTimeout) должны добавляться новые элементы, а индикатор должен скрываться.

CSS
body {height: 200px;} 
 .container {
  font-family: Arial, sans-serif;
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
}

.item {
  padding: 20px;
  background-color: #f0f0f0;
  margin: 10px 0;
  border-radius: 5px;
}

.loading-indicator {
  text-align: center;
  padding: 15px;
  background-color: #ffeb3b;
  margin: 10px 0;
  border-radius: 5px;
  display: none;
}
HTML
Восстановить HTML
<div class="container">
  <h1>Загрузка с индикатором</h1>
  <div id="content">
    <div class="item">Элемент 1</div>
    <div class="item">Элемент 2</div>
    <div class="item">Элемент 3</div>
    <div class="item">Элемент 4</div>
    <div class="item">Элемент 5</div>
  </div>
  <div id="loading" class="loading-indicator">Загрузка...</div>
</div>
JavaScript
let itemCount = 5;
let isLoading = false;

function addItems() {
  const contentDiv = document.getElementById('content');
  const loadingIndicator = document.getElementById('input__1');
  
  if (isLoading) return;
  
  isLoading = true;
  loadingIndicator.style.input__2 = 'input__3';
  
  // Имитация загрузки данных
  input__4(function() {
    for (let i = 0; i < 5; i++) {
      const newItem = document.createElement('div');
      newItem.className = 'item';
      itemCount++;
      newItem.textContent = `Элемент ${itemCount}`;
      contentDiv.appendChild(newItem);
    }
    
    isLoading = false;
    loadingIndicator.style.display = 'input__5';
  }, 1500);
}

function checkScrollEnd() {
  const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
  const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
  const clientHeight = document.documentElement.clientHeight || document.body.clientHeight;
  
  if (scrollTop + clientHeight >= scrollHeight - 10 && !isLoading) {
    addItems();
  }
}

window.addEventListener('scroll', checkScrollEnd);
Используйте свойство display для показа и скрытия индикатора загрузки. Примените setTimeout для имитации задержки загрузки данных и используйте функцию для добавления новых элементов после этой задержки.
Заполнить ответами все поля
Результат
Лог
Выполнить
Отметить решенным
Показать подсказку

Загрузка данных из массива

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

CSS
body {height: 200px;} 
 .container {
  font-family: Arial, sans-serif;
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
}

#content div {
  padding: 15px;
  margin: 10px 0;
  background-color: #f0f0f0;
  border-radius: 5px;
}

.loading, .end-message {
  text-align: center;
  padding: 15px;
  margin: 10px 0;
  border-radius: 5px;
  display: none;
}

.loading {
  background-color: #ffeb3b;
}

.end-message {
  background-color: #e0e0e0;
}
HTML
Восстановить HTML
<div class="container">
  <h1>Загрузка данных из массива</h1>
  <div id="content"></div>
  <div id="loading" class="loading">Загрузка...</div>
  <div id="end-message" class="end-message">Больше нет данных</div>
</div>
JavaScript
// Данные для загрузки
const items = [
  "Первый элемент", "Второй элемент", "Третий элемент", "Четвертый элемент", "Пятый элемент",
  "Шестой элемент", "Седьмой элемент", "Восьмой элемент", "Девятый элемент", "Десятый элемент",
  "Одиннадцатый элемент", "Двенадцатый элемент", "Тринадцатый элемент", "Четырнадцатый элемент", "Пятнадцатый элемент"
];

let currentIndex = 0;
let isLoading = false;

function loadMoreItems() {
  if (isLoading) return;
  
  const contentDiv = document.getElementById('input__1');
  const loadingIndicator = document.getElementById('loading');
  const endMessage = document.getElementById('input__2');
  
  if (currentIndex >= items.input__3) {
    endMessage.style.display = 'block';
    return;
  }
  
  isLoading = true;
  loadingIndicator.style.display = 'block';
  
  setTimeout(function() {
    // Количество элементов для загрузки за один раз
    const itemsToLoad = Math.min(5, items.length - currentIndex);
    
    for (let i = 0; i < itemsToLoad; i++) {
      const newItem = document.createElement('div');
      newItem.textContent = items[input__4];
      contentDiv.input__5(newItem);
      currentIndex++;
    }
    
    isLoading = false;
    loadingIndicator.style.display = 'none';
    
    if (currentIndex >= items.length) {
      endMessage.style.display = 'block';
    }
  }, 1000);
}

function checkScrollEnd() {
  const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
  const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
  const clientHeight = document.documentElement.clientHeight || document.body.clientHeight;
  
  if (scrollTop + clientHeight >= scrollHeight - 10 && !isLoading) {
    loadMoreItems();
  }
}

// Загружаем начальные элементы
loadMoreItems();

window.addEventListener('scroll', checkScrollEnd);
Используйте переменную для отслеживания текущей позиции в массиве. При каждой загрузке проверяйте, остались ли еще данные в массиве, и добавляйте соответствующие элементы. Если данные закончились, отобразите сообщение.
Заполнить ответами все поля
Результат
Лог
Выполнить
Отметить решенным
Показать подсказку

Асинхронная загрузка данных с сервера

Эта задача только для примера. В этом задании нужно реализовать асинхронную загрузку данных с использованием Fetch API. При прокрутке страницы до конца должен выполняться запрос к API, и полученные данные должны добавляться на страницу.

CSS
body {height: 200px;} 
 .container {
  font-family: Arial, sans-serif;
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.post {
  padding: 15px;
  margin: 15px 0;
  background-color: #f9f9f9;
  border-radius: 5px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}

.post h2 {
  margin-top: 0;
  color: #333;
}

.post p {
  color: #666;
}

.loading-indicator, .error-message {
  text-align: center;
  padding: 15px;
  margin: 15px 0;
  border-radius: 5px;
  display: none;
}

.loading-indicator {
  background-color: #e3f2fd;
}

.error-message {
  background-color: #ffebee;
  color: #c62828;
}
HTML
Восстановить HTML
<div class="container">
  <h1>Асинхронная загрузка данных</h1>
  <div id="posts"></div>
  <div id="loading" class="loading-indicator">Загрузка данных...</div>
  <div id="error" class="error-message">Ошибка загрузки данных</div>
</div>
JavaScript
let page = 1;
let isLoading = false;

async function loadPosts() {
  if (isLoading) return;
  
  const postsContainer = document.getElementById('posts');
  const loadingIndicator = document.getElementById('loading');
  const errorMessage = document.getElementById('error');
  
  isLoading = true;
  loadingIndicator.style.display = 'block';
  errorMessage.style.display = 'none';
  
  try {
    const response = await input__1('https://naytikurs.ru/posts?_page=' + page + '&_limit=5');
    
    if (!response.input__2) {
      throw new Error('Ошибка загрузки данных');
    }
    
    const posts = await response.input__3();
    
    if (posts.length === 0) {
      // Больше нет данных
      loadingIndicator.textContent = 'Больше нет данных';
      return;
    }
    
    posts.forEach(post => {
      const postElement = document.createElement('div');
      postElement.className = 'input__4';
      
      const title = document.createElement('h2');
      title.textContent = post.title;
      
      const body = document.createElement('p');
      body.textContent = post.body;
      
      postElement.appendChild(title);
      postElement.appendChild(body);
      postsContainer.input__5(postElement);
    });
    
    page++;
  } catch (error) {
    errorMessage.style.display = 'block';
    console.error('Ошибка загрузки:', error);
  } finally {
    isLoading = false;
    loadingIndicator.style.display = 'none';
  }
}

function checkScrollEnd() {
  const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
  const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
  const clientHeight = document.documentElement.clientHeight || document.body.clientHeight;
  
  if (scrollTop + clientHeight >= scrollHeight - 10 && !isLoading) {
    loadPosts();
  }
}

// Загружаем начальные посты
loadPosts();

window.addEventListener('scroll', checkScrollEnd);
Используйте fetch для выполнения асинхронных запросов. Обработайте ответ с помощью методов json() и then(). Добавьте полученные данные на страницу, создавая новые элементы DOM.
Заполнить ответами все поля
Результат
Лог
Выполнить
Отметить решенным
Показать подсказку

Использование Intersection Observer для бесконечной прокрутки

В этом задании нужно реализовать бесконечную прокрутку с использованием Intersection Observer API вместо события прокрутки. Нужно создать наблюдатель, который будет отслеживать видимость специального элемента-сенсора в конце страницы.

CSS
body {height: 200px;} 
 .container {
  font-family: Arial, sans-serif;
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
}

.item {
  padding: 20px;
  margin: 10px 0;
  background-color: #f0f0f0;
  border-radius: 5px;
}

.loading-indicator {
  text-align: center;
  padding: 15px;
  margin: 10px 0;
  background-color: #e3f2fd;
  border-radius: 5px;
  display: none;
}

.sentinel {
  height: 20px;
  margin-bottom: 20px;
}
HTML
Восстановить HTML
<div class="container">
  <h1>Бесконечная прокрутка с Intersection Observer</h1>
  <div id="content">
    <div class="item">Элемент 1</div>
    <div class="item">Элемент 2</div>
    <div class="item">Элемент 3</div>
    <div class="item">Элемент 4</div>
    <div class="item">Элемент 5</div>
  </div>
  <div id="loading" class="loading-indicator">Загрузка...</div>
  <div id="sentinel" class="sentinel"></div>
</div>
JavaScript
let itemCount = 5;
let isLoading = false;

function addItems() {
  if (isLoading) return;
  
  const contentDiv = document.getElementById('content');
  const loadingIndicator = document.getElementById('loading');
  
  isLoading = true;
  loadingIndicator.style.display = 'block';
  
  setTimeout(function() {
    for (let i = 0; i < 5; i++) {
      const newItem = document.createElement('div');
      newItem.className = 'item';
      itemCount++;
      newItem.textContent = `Элемент ${itemCount}`;
      contentDiv.appendChild(newItem);
    }
    
    isLoading = false;
    loadingIndicator.style.display = 'none';
  }, 1000);
}

// Создаем Intersection Observer
const options = {
  root: input__1,
  rootMargin: 'input__2',
  threshold: input__3
};

const observer = new input__4(function(entries) {
  entries.forEach(entry => {
    if (entry.input__5 && !isLoading) {
      addItems();
    }
  });
}, options);

// Начинаем наблюдение за сенсором
const sentinel = document.getElementById('sentinel');
observer.observe(sentinel);
Используйте IntersectionObserver для создания наблюдателя. Настройте его для отслеживания видимости элемента-сенсора. В колбэке наблюдателя проверяйте, видим ли сенсор, и если да, загружайте новые данные.
Заполнить ответами все поля
Результат
Лог
Выполнить
Отметить решенным
Показать подсказку

Кнопка 'Загрузить еще' с бесконечной прокруткой

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

CSS
body {height: 200px;} 
 .container {
  font-family: Arial, sans-serif;
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
}

.item {
  padding: 20px;
  margin: 10px 0;
  background-color: #f0f0f0;
  border-radius: 5px;
}

.loading-indicator {
  text-align: center;
  padding: 15px;
  margin: 10px 0;
  background-color: #e3f2fd;
  border-radius: 5px;
  display: none;
}

.load-more-button {
  display: block;
  width: 100%;
  padding: 15px;
  background-color: #2196f3;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  font-size: 16px;
  margin: 20px 0;
  transition: background-color 0.3s;
}

.load-more-button:hover {
  background-color: #0b7dda;
}
HTML
Восстановить HTML
<div class="container">
  <h1>Комбинированная загрузка</h1>
  <div id="content">
    <div class="item">Элемент 1</div>
    <div class="item">Элемент 2</div>
    <div class="item">Элемент 3</div>
    <div class="item">Элемент 4</div>
    <div class="item">Элемент 5</div>
  </div>
  <div id="loading" class="loading-indicator">Загрузка...</div>
  <button id="load-more" class="load-more-button">Загрузить еще</button>
</div>
JavaScript
let itemCount = 5;
let isLoading = false;

function loadMoreItems() {
  if (isLoading) return;
  
  const contentDiv = document.getElementById('content');
  const loadingIndicator = document.getElementById('loading');
  const loadMoreButton = document.getElementById('load-more');
  
  isLoading = true;
  loadingIndicator.style.display = 'block';
  loadMoreButton.disabled = true;
  
  setTimeout(function() {
    for (let i = 0; i < 5; i++) {
      const newItem = document.createElement('div');
      newItem.className = 'item';
      itemCount++;
      newItem.textContent = `Элемент ${itemCount}`;
      contentDiv.appendChild(newItem);
    }
    
    isLoading = false;
    loadingIndicator.style.display = 'none';
    loadMoreButton.disabled = false;
  }, 1000);
}

function checkScrollEnd() {
  const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
  const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
  const clientHeight = document.documentElement.clientHeight || document.body.clientHeight;
  
  if (scrollTop + clientHeight >= scrollHeight - 10 && !isLoading) {
    loadMoreItems();
  }
}

// Добавляем обработчик события для кнопки
const loadMoreButton = document.getElementById('input__1');
loadMoreButton.addEventListener('input__2', input__3);

// Добавляем обработчик события для прокрутки
input__4.addEventListener('input__5', checkScrollEnd);
Используйте одну и ту же функцию для загрузки элементов как при прокрутке, так и при нажатии на кнопку. Добавьте обработчик события click для кнопки и обработчик события scroll для окна.
Заполнить ответами все поля
Результат
Лог
Выполнить
Отметить решенным
Показать подсказку

Загрузка изображений с ленивой загрузкой

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

CSS
body {height: 200px;} 
 .container {
  font-family: Arial, sans-serif;
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

#gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 15px;
}

.image-container {
  border-radius: 5px;
  overflow: hidden;
  box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}

.lazy-image {
  width: 100%;
  height: 200px;
  object-fit: cover;
  transition: opacity 0.3s;
  opacity: 0;
}

.lazy-image.loaded {
  opacity: 1;
}

.loading-indicator {
  text-align: center;
  padding: 15px;
  margin: 20px 0;
  background-color: #e3f2fd;
  border-radius: 5px;
  display: none;
}

.sentinel {
  height: 20px;
  margin-bottom: 20px;
}
HTML
Восстановить HTML
<div class="container">
  <h1>Галерея с ленивой загрузкой</h1>
  <div id="gallery"></div>
  <div id="loading" class="loading-indicator">Загрузка изображений...</div>
  <div id="sentinel" class="sentinel"></div>
</div>
JavaScript
// Массив URL изображений
const imageUrls = [
  "https://naytikurs.ru/assets/uploads/2025/03/3d-modelirovanie-i-vizualizatsiya-test-dlya-spetsialistov-v-trehmernoj-grafike2-300x300.png",
  "https://naytikurs.ru/assets/uploads/2025/03/3d-modelirovanie-i-vizualizatsiya-test-dlya-spetsialistov-v-trehmernoj-grafike-300x300.png",
  "https://naytikurs.ru/assets/uploads/2025/03/3d-modelirovanie-i-vizualizatsiya-test-dlya-spetsialistov-v-trehmernoj-grafike3-300x300.png",
  "https://naytikurs.ru/assets/uploads/2025/03/3d-modelirovanie-i-vizualizatsiya-test-dlya-spetsialistov-v-trehmernoj-grafike4-300x300.png",
  "https://naytikurs.ru/assets/uploads/2025/03/naskolko-vas-legko-obmanut-v-internete-300x300.png",
  "https://naytikurs.ru/assets/uploads/2025/03/test-na-znanie-osnov-kiberbezopasnosti-300x300.png"
];

let currentIndex = 0;
let isLoading = false;

// Функция для создания элемента изображения
function createImageElement(url) {
  const container = document.createElement('div');
  container.className = 'image-container';
  
  const img = document.createElement('img');
  img.className = 'lazy-image';
  img.input__1 = 'data-src'; // Используем data-атрибут для хранения URL
  img.setAttribute('input__2', url);
  
  container.appendChild(img);
  return container;
}

// Функция для загрузки изображений
function loadMoreImages() {
  if (isLoading) return;
  
  const gallery = document.getElementById('gallery');
  const loadingIndicator = document.getElementById('loading');
  
  isLoading = true;
  loadingIndicator.style.display = 'block';
  
  setTimeout(function() {
    for (let i = 0; i < 6; i++) {
      // Используем циклический доступ к массиву изображений
      const imageIndex = currentIndex % imageUrls.length;
      const imageUrl = imageUrls[imageIndex];
      
      const imageElement = createImageElement(imageUrl);
      gallery.appendChild(imageElement);
      
      currentIndex++;
    }
    
    isLoading = false;
    loadingIndicator.style.display = 'none';
    
    // Обновляем наблюдатель для новых изображений
    setupLazyLoading();
  }, 1000);
}

// Настройка ленивой загрузки
function setupLazyLoading() {
  const lazyImages = document.querySelectorAll('img.lazy-image:not(.loaded)');
  
  const imageObserver = new IntersectionObserver(function(entries, observer) {
    entries.forEach(entry => {
      if (entry.input__3) {
        const img = entry.target;
        const src = img.getAttribute('data-src');
        
        img.input__4 = src;
        img.classList.add('input__5');
        
        // Прекращаем наблюдение после загрузки
        observer.unobserve(img);
      }
    });
  }, { rootMargin: '0px 0px 100px 0px' });
  
  lazyImages.forEach(img => {
    imageObserver.observe(img);
  });
}

// Настройка бесконечной прокрутки
const scrollObserver = new IntersectionObserver(function(entries) {
  entries.forEach(entry => {
    if (entry.isIntersecting && !isLoading) {
      loadMoreImages();
    }
  });
}, { rootMargin: '0px 0px 200px 0px' });

// Начинаем наблюдение за сенсором прокрутки
const sentinel = document.getElementById('sentinel');
scrollObserver.observe(sentinel);

// Загружаем начальные изображения
loadMoreImages();
Используйте IntersectionObserver для отслеживания видимости элементов. Для каждого изображения храните URL в атрибуте data-src и загружайте изображение, устанавливая src только когда оно становится видимым.
Заполнить ответами все поля
Результат
Лог
Выполнить
Отметить решенным
Показать подсказку

Бесконечная прокрутка с управлением состоянием загрузки

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

CSS
body {height: 200px;} 
 .container {
  font-family: Arial, sans-serif;
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.item {
  padding: 20px;
  margin: 10px 0;
  background-color: #f9f9f9;
  border-radius: 5px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}

.status-indicator {
  padding: 15px;
  margin: 15px 0;
  border-radius: 5px;
  text-align: center;
  display: none;
}

.loading {
  background-color: #e3f2fd;
}

.error {
  background-color: #ffebee;
  color: #c62828;
}

.no-more {
  background-color: #f5f5f5;
  color: #616161;
}

#retry {
  margin-left: 10px;
  padding: 5px 10px;
  background-color: #f44336;
  color: white;
  border: none;
  border-radius: 3px;
  cursor: pointer;
}
HTML
Восстановить HTML
<div class="container">
  <h1>Бесконечная прокрутка с управлением состоянием</h1>
  <div id="content"></div>
  <div id="loading" class="status-indicator loading">Загрузка данных...</div>
  <div id="error" class="status-indicator error">Ошибка загрузки данных. <button id="retry">Повторить</button></div>
  <div id="no-more" class="status-indicator no-more">Больше нет данных</div>
</div>
JavaScript
let page = 1;
let isLoading = false;
let hasError = false;
let hasMore = true;

async function loadData() {
  if (isLoading || !hasMore) return;
  
  const contentDiv = document.getElementById('content');
  const loadingIndicator = document.getElementById('loading');
  const errorIndicator = document.getElementById('error');
  const noMoreIndicator = document.getElementById('no-more');
  
  // Обновляем состояние и UI
  isLoading = true;
  hasError = false;
  updateStatusIndicators();
  
  try {
    // Имитируем загрузку данных с сервера
    const data = await fetchData(page);
    
    if (data.length === 0) {
      // Больше нет данных
      hasMore = input__1;
      updateStatusIndicators();
      return;
    }
    
    // Добавляем элементы на страницу
    data.forEach(item => {
      const itemElement = document.createElement('div');
      itemElement.className = 'item';
      itemElement.textContent = item.text;
      contentDiv.appendChild(itemElement);
    });
    
    // Увеличиваем номер страницы
    input__2++;
  } catch (error) {
    // Обрабатываем ошибку
    hasError = input__3;
    console.error('Ошибка загрузки данных:', error);
  } finally {
    // Обновляем состояние и UI
    isLoading = false;
    updateStatusIndicators();
  }
}

// Функция для обновления индикаторов состояния
function updateStatusIndicators() {
  const loadingIndicator = document.getElementById('loading');
  const errorIndicator = document.getElementById('error');
  const noMoreIndicator = document.getElementById('no-more');
  
  loadingIndicator.style.display = isLoading ? 'input__4' : 'none';
  errorIndicator.style.display = hasError ? 'block' : 'none';
  noMoreIndicator.style.display = !hasMore && !isLoading && !hasError ? 'block' : 'none';
}

// Функция для имитации загрузки данных с сервера
async function fetchData(page) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // Имитируем ошибку для страницы 3
      if (page === 3 && Math.random() < 0.5) {
        reject(new Error('Ошибка сети'));
        return;
      }
      
      // Имитируем отсутствие данных после страницы 5
      if (page > 5) {
        resolve([]);
        return;
      }
      
      // Генерируем тестовые данные
      const items = [];
      for (let i = 1; i <= 5; i++) {
        items.push({
          id: (page - 1) * 5 + i,
          text: `Элемент ${(page - 1) * 5 + i} (страница ${page})`
        });
      }
      resolve(items);
    }, 1000);
  });
}

// Обработчик события прокрутки
function checkScrollEnd() {
  const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
  const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
  const clientHeight = document.documentElement.clientHeight || document.body.clientHeight;
  
  if (scrollTop + clientHeight >= scrollHeight - 10 && !isLoading && hasMore && !hasError) {
    input__5();
  }
}

// Добавляем обработчик для кнопки повторной попытки
document.getElementById('retry').addEventListener('click', function() {
  if (hasError) {
    hasError = false;
    updateStatusIndicators();
    loadData();
  }
});

// Добавляем обработчик события для прокрутки
window.addEventListener('scroll', checkScrollEnd);

// Загружаем начальные данные
loadData();
Используйте переменные состояния для отслеживания различных состояний: isLoading, hasError, hasMore. Обновляйте эти состояния в зависимости от результатов загрузки данных. Отображайте соответствующие индикаторы для каждого состояния.
Заполнить ответами все поля
Результат
Лог
Выполнить
Отметить решенным
Показать подсказку
НайтиКурс.Ру