Загрузка контента

Ленивая загрузка (lazy loading) — техника, при которой ресурсы подгружаются только в момент, когда они становятся необходимыми. В библиотеке Waypoints эта стратегия реализуется через отслеживание положения элемента относительно области просмотра и выполнение заданного обработчика при достижении определённой точки.

Ключевая идея: контент не загружается заранее, а инициируется событием прокрутки.

Принцип работы Waypoints при загрузке контента

Waypoints отслеживает положение DOM-элементов и вызывает функции при пересечении заданной точки (offset). Для ленивой загрузки:

  • создаётся элемент-заглушка
  • назначается waypoint
  • при попадании в зону видимости выполняется загрузка данных

Простейшая схема:

new Waypoint({
  element: document.getElementById('target'),
  handler: function(direction) {
    // загрузка контента
  },
  offset: '75%'
});

offset определяет момент срабатывания:

  • '75%' — когда элемент находится на 75% высоты экрана
  • 'bottom-in-view' — когда элемент полностью появляется

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

Разметка

<img data-src="image.jpg" class="lazy-image" alt="...">

Изображение не содержит src, чтобы не загружаться сразу.

Скрипт

document.querySelectorAll('.lazy-image').forEach(function(img) {
  new Waypoint({
    element: img,
    handler: function() {
      img.src = img.dataset.src;
      this.destroy();
    },
    offset: '100%'
  });
});

Особенности

  • используется data-src для хранения URL
  • waypoint уничтожается после загрузки (destroy()), чтобы избежать повторных вызовов
  • offset '100%' означает загрузку прямо перед появлением

Подгрузка HTML-контента

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

Пример структуры

<div id="content-container"></div>
<div id="load-trigger"></div>

Логика загрузки

new Waypoint({
  element: document.getElementById('load-trigger'),
  handler: function() {
    fetch('/api/content')
      .then(response => response.text())
      .then(html => {
        document.getElementById('content-container')
          .insertAdjacentHTML('beforeend', html);
      });

    this.destroy();
  },
  offset: '80%'
});

Важные моменты

  • используется fetch для получения данных
  • контент вставляется через insertAdjacentHTML
  • waypoint удаляется после первого срабатывания

Бесконечная прокрутка (Infinite Scroll)

Один из наиболее распространённых сценариев — реализация бесконечной ленты.

Базовая архитектура

  1. список элементов
  2. элемент-триггер внизу
  3. waypoint, который загружает следующую порцию

Пример

let page = 1;

function loadMore() {
  fetch(`/api/items?page=${page}`)
    .then(res => res.json())
    .then(data => {
      const container = document.getElementById('list');

      data.forEach(item => {
        const div = document.createElement('div');
        div.textContent = item.title;
        container.appendChild(div);
      });

      page++;
    });
}

const waypoint = new Waypoint({
  element: document.getElementById('load-more'),
  handler: function() {
    loadMore();
  },
  offset: 'bottom-in-view'
});

Потенциальные проблемы

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

Защита от повторных запросов

let isLoading = false;

function loadMore() {
  if (isLoading) return;

  isLoading = true;

  fetch(`/api/items?page=${page}`)
    .then(res => res.json())
    .then(data => {
      // добавление элементов
      page++;
      isLoading = false;
    });
}

Работа с несколькими точками загрузки

Waypoints позволяет управлять множеством триггеров на странице.

Группировка

Waypoint.refreshAll();

Используется после динамического добавления элементов.

Массовая инициализация

document.querySelectorAll('.lazy-block').forEach(block => {
  new Waypoint({
    element: block,
    handler: function() {
      block.classList.add('loaded');
      this.destroy();
    },
    offset: '70%'
  });
});

Предзагрузка контента

Для повышения UX контент можно загружать заранее — до появления.

Использование offset

offset: '150%'

Контент начнёт загружаться ещё до попадания в область видимости.

Пример

new Waypoint({
  element: section,
  handler: function() {
    loadSectionData();
    this.destroy();
  },
  offset: '120%'
});

Оптимизация производительности

Уничтожение Waypoints

Каждый waypoint должен удаляться после использования:

this.destroy();

Ограничение количества наблюдаемых элементов

  • избегать создания waypoint для каждого мелкого элемента
  • использовать контейнеры вместо отдельных узлов

Дебаунс/троттлинг

Waypoints уже оптимизирован, но при сложной логике стоит ограничивать вызовы:

function debounce(fn, delay) {
  let timeout;
  return function() {
    clearTimeout(timeout);
    timeout = setTimeout(fn, delay);
  };
}

Комбинирование с CSS и анимациями

Загрузка контента часто сопровождается визуальными эффектами.

Пример

new Waypoint({
  element: block,
  handler: function() {
    block.classList.add('fade-in');
    this.destroy();
  },
  offset: '80%'
});
.fade-in {
  opacity: 1;
  transition: opacity 0.5s ease;
}

Обработка ошибок загрузки

Важно учитывать сбои сети.

fetch('/api/data')
  .then(res => {
    if (!res.ok) throw new Error('Ошибка загрузки');
    return res.json();
  })
  .catch(err => {
    console.error(err);
  });

Рекомендации

  • отображение fallback-контента
  • повторные попытки загрузки
  • логирование ошибок

Интеграция с другими библиотеками

Waypoints может использоваться вместе с:

  • AJAX-библиотеками
  • UI-фреймворками
  • системами шаблонов

Пример с jQuery

$('#trigger').waypoint(function() {
  $('#container').load('/content');
}, {
  offset: '75%'
});

Практические паттерны

Плейсхолдеры

Перед загрузкой отображаются временные блоки:

<div class="placeholder"></div>

После загрузки заменяются реальным контентом.

Skeleton UI

Создаются стилизованные заглушки, имитирующие структуру:

.skeleton {
  background: linear-gradient(90deg, #eee, #ddd, #eee);
}

Lazy + Infinite Scroll

Комбинация:

  • изображения внутри списка загружаются лениво
  • сам список расширяется по мере прокрутки

Типичные ошибки

  • забытый destroy() → утечки памяти
  • слишком маленький offset → контент появляется с задержкой
  • отсутствие блокировки запросов → дублирование данных
  • чрезмерное количество waypoint-объектов → падение производительности

Структурирование кода

Для крупных проектов логика загрузки выносится в отдельные модули:

class LazyLoader {
  constructor(selector) {
    this.elements = document.querySelectorAll(selector);
    this.init();
  }

  init() {
    this.elements.forEach(el => {
      new Waypoint({
        element: el,
        handler: () => this.load(el),
        offset: '80%'
      });
    });
  }

  load(el) {
    el.src = el.dataset.src;
  }
}

Вывод ключевых принципов

  • событие прокрутки используется как триггер
  • контент загружается строго по необходимости
  • waypoint должен уничтожаться после выполнения
  • offset регулирует UX и производительность
  • контроль состояния загрузки обязателен