Как определить, что элемент находится в viewport в JavaScript
Что проверяют
Знаете ли вы современный способ, а не только расчёт координат, и понимаете ли, во что обходится проверка на каждый скролл. Следом обычно спрашивают про ленивую загрузку картинок или про подсчёт показов блока.
Как отвечать
Основной ответ IntersectionObserver. Браузер сам считает пересечение и вызывает колбэк асинхронно, без обработчика scroll и без принудительного пересчёта раскладки:
- создание:
new IntersectionObserver(callback, { root: null, rootMargin: '200px', threshold: [0, 0.5, 1] }), затемobserver.observe(el); - колбэк: получает массив
entries, у каждой записи естьisIntersecting,intersectionRatioиboundingClientRect; threshold: доля видимой площади, при пересечении которой вызывается колбэк.0значит «показался хотя бы край»,1значит «виден целиком»;rootMargin: расширяет или сужает область наблюдения, удобно начинать загрузку заранее;- очистка:
unobserve(el)после первого показа при ленивой загрузке иdisconnect()при размонтировании компонента.
Ручной вариант через el.getBoundingClientRect() тоже надо знать. Частичная видимость это rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0. Он годится для разовой проверки, а в обработчике скролла его стоит троттлить через requestAnimationFrame: чтение геометрии после изменений DOM форсирует layout.
Для картинок и iframe сначала назовите loading="lazy": браузер решает задачу сам, без JavaScript.
Где спотыкаются
Проверяют полную видимость: top >= 0 && bottom <= innerHeight. Элемент выше экрана по этой формуле не бывает видимым никогда, даже когда занимает весь экран.
Не учитывают, что колбэк срабатывает сразу после observe() с текущим состоянием. Код, который считает каждый вызов показом, засчитывает лишние.
Считают isIntersecting доказательством, что пользователь видит элемент. Наблюдатель учитывает обрезку родителями с overflow, но не знает про opacity: 0 и про модальное окно поверх блока.