Перейти к основному содержимому

[Lv1] Оптимизация загрузки изображений: четырёхуровневая ленивая загрузка

Четырёхуровневая стратегия ленивой загрузки изображений, которая значительно сокращает трафик первого экрана и улучшает воспринимаемую скорость загрузки.


Ситуация

Страница галереи может содержать сотни изображений, но пользователи обычно просматривают лишь несколько первых элементов.

Типичные проблемы без оптимизации:

  • Огромный начальный объём запросов изображений
  • Долгое время загрузки первого экрана
  • Подёргивания при прокрутке на слабых устройствах
  • Впустую потраченный трафик на изображения, которые пользователь никогда не увидит

Задача

  1. Загружать только изображения вблизи видимой области
  2. Предзагружать непосредственно перед появлением в поле зрения
  3. Контролировать количество одновременных запросов изображений
  4. Избегать повторных загрузок при быстрой навигации
  5. Удерживать трафик изображений первого экрана в строгих рамках бюджета

Действие: четырёхуровневая стратегия

Уровень 1: Определение видимости с помощью IntersectionObserver

const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
loadImage(entry.target as HTMLImageElement);
observer.unobserve(entry.target);
}
});
},
{ rootMargin: '120px 0px', threshold: 0.01 }
);

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

Уровень 2: Заглушка и прогрессивный опыт

  • Использовать размытую миниатюру / skeleton-заглушку
  • Зарезервировать ширину и высоту для предотвращения сдвигов макета
  • Заменить заглушку после декодирования изображения
<img src="/placeholder.webp" data-src="/real-image.webp" width="320" height="180" alt="cover" />

Уровень 3: Очередь с контролем параллельности

const MAX_CONCURRENT = 6;
const queue: Array<() => Promise<void>> = [];
let active = 0;

async function runQueue() {
if (active >= MAX_CONCURRENT || queue.length === 0) return;
const task = queue.shift();
if (!task) return;

active += 1;
try {
await task();
} finally {
active -= 1;
runQueue();
}
}

Ограничивает нагрузку на сеть и предотвращает пиковые всплески запросов.

Уровень 4: Отмена и дедупликация

  • Отмена устаревших запросов через AbortController
  • Использование карты URL в памяти для дедупликации загрузок
  • Пропуск повторных запросов уже успешно загруженных ресурсов
const inflight = new Map<string, Promise<void>>();

Результат

Примерное влияние после внедрения:

  • Объём изображений первого экрана значительно сокращён
  • Более быстрая первая значимая отрисовка
  • Более плавная прокрутка
  • Снижение показателя отказов в мобильных сетях

Краткое резюме для собеседования

Я комбинирую определение видимой области, отображение заглушек, управление очередью запросов и отмену/дедупликацию. Это позволяет избежать загрузки изображений, которые пользователь никогда не увидит, и поддерживает отзывчивость как сети, так и интерфейса.