[Lv1] Оптимизация загрузки изображений: четырёхуровневая ленивая загрузка
Четырёхуровневая стратегия ленивой загрузки изображений, которая значительно сокращает трафик первого экрана и улучшает воспринимаемую скорость загрузки.
Ситуация
Страница галереи может содержать сотни изображений, но пользователи обычно просматривают лишь несколько первых элементов.
Типичные проблемы без оптимизации:
- Огромный начальный объём запросов изображений
- Долгое время загрузки первого экрана
- Подёргивания при прокрутке на слабых устройствах
- Впустую потраченный трафик на изображения, которые пользователь никогда не увидит
Задача
- Загружать только изображения вблизи видимой области
- Предзагружать непосредственно перед появлением в поле зрения
- Контролировать количество одновременных запросов изображений
- Избегать повторных загрузок при быстрой навигации
- Удерживать трафик изображений первого экрана в строгих рамках бюджета
Действие: четырёхуровневая стратегия
Уровень 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>>();
Результат
Примерное влияние после внедрения:
- Объём изображений первого экрана значительно сокращён
- Более быстрая первая значимая отрисовка
- Более плавная прокрутка
- Снижение показателя отказов в мобильных сетях
Краткое резюме для собеседования
Я комбинирую определение видимой области, отображение заглушек, управление очередью запросов и отмену/дедупликацию. Это позволяет избежать загрузки изображений, которые пользователь никогда не увидит, и поддерживает отзывчивость как сети, так и интерфейса.