Как определить, что изображение появилось на экране для начала его загрузки
Что проверяют
Знаете ли вы нативный loading="lazy" и IntersectionObserver и понимаете ли, почему старый подход через обработчик scroll плох. Следом обычно спрашивают, как ленивая загрузка влияет на LCP и CLS.
Как отвечать
Сначала назовите самый простой вариант: атрибут loading="lazy" у <img> и <iframe>. Его поддерживают все современные браузеры, и загрузка начинается заранее, до того как картинка доедет до экрана. Для обычных изображений в контенте JS не нужен.
IntersectionObserver берут, когда нужен контроль:
- когда он нужен: фоновые картинки из CSS, свой запас до экрана, ленивая загрузка компонентов, аналитика показов;
- как работает:
new IntersectionObserver(callback, { rootMargin: "200px 0px", threshold: 0 }). В колбэк приходит массивentries, у каждой записи естьisIntersecting. Для видимого элемента подставляемsrcиsrcsetизdata-srcиdata-srcset, затем вызываемobserver.unobserve(entry.target); root: по умолчанию это viewport. Если лента прокручивается внутри контейнера, корнем указывают его;- почему не
scroll: колбэк наблюдателя вызывается асинхронно и не нагружает главный поток на каждом событии прокрутки, аgetBoundingClientRect()в обработчикеscrollзаставляет браузер пересчитывать раскладку; - размеры:
widthиheightилиaspect-ratioу картинки, чтобы место было зарезервировано заранее; - React: наблюдатель создают в
useEffectи отключают черезdisconnect()при размонтировании. В Next.js компонентnext/imageзагружается лениво по умолчанию.
Где спотыкаются
Делают ленивой первую крупную картинку экрана. Она почти всегда оказывается LCP-элементом, и ленивая загрузка его задерживает. Верхним изображениям, наоборот, ставят fetchpriority="high".
Ставят threshold: 1 для картинки выше окна браузера. Она никогда не будет видна целиком, и колбэк не сработает.
Забывают про размеры: картинка загружается, раздвигает контент, и CLS растёт. Или не отключают наблюдатель, и в SPA он продолжает держать ссылки на удалённые узлы.