> Как определить, что элемент находится в viewport в JavaScript (JavaScript)
Уровень: senior · Роль: frontend · Категория: Технические вопросы
Компании: Домклик
Стек: JavaScript
> Пример ответа
Короткий ответ
Основной способ - использовать getBoundingClientRect() и сравнить координаты элемента с размерами viewport. Альтернатива для современных браузеров - IntersectionObserver, который работает асинхронно и эффективнее при отслеживании множества элементов. Выбор зависит от задачи: для разовой проверки подходит getBoundingClientRect(), для постоянного мониторинга (lazy loading, анимации) - IntersectionObserver.
Подробное объяснение
getBoundingClientRect() возвращает объект с координатами элемента относительно viewport: top, bottom, left, right. Элемент видим, если его границы пересекаются с областью просмотра:
JAVASCRIPTconst rect = el.getBoundingClientRect();const isVisible = rect.top < window.innerHeight && rect.bottom > 0 &&rect.left < window.innerWidth && rect.right > 0;
Важно учитывать, что getBoundingClientRect() вызывает reflow - при массовых проверках это может быть дорого. Также он не учитывает opacity: 0, visibility: hidden или display: none - элемент с такими стилями всё равно вернёт координаты.
IntersectionObserver решает эти проблемы: он работает асинхронно, не блокирует main thread, и позволяет задать порог видимости (threshold) и корневой элемент (root). При этом он не поддерживается в старых браузерах (IE11), поэтому для legacy-проектов нужен полифилл или fallback на getBoundingClientRect().
Дополнительные нюансы: при наличии горизонтального скролла нужно учитывать window.innerWidth, а не document.documentElement.clientWidth - последний не включает scrollbar. Для iframe viewport - это окно iframe, а не родительской страницы.
На практике
Для lazy loading изображений предпочтителен IntersectionObserver - он позволяет отменить наблюдение после срабатывания и не требует ручной обработки scroll-событий. Для разовой проверки при клике или в обработчике - getBoundingClientRect() проще и не требует создания observer.
При использовании IntersectionObserver стоит помнить: callback срабатывает асинхронно, поэтому нельзя полагаться на него в момент, когда нужно синхронно получить результат. Также observer не сработает, если элемент уже в viewport на момент создания - нужно вызвать observe() и дождаться первого callback.
Для производительности при scroll-событиях с getBoundingClientRect() используйте requestAnimationFrame или throttle, чтобы не вызывать reflow чаще, чем раз в кадр.
Пример кода
JAVASCRIPT// Проверка через getBoundingClientRectfunction isElementInViewport(el, partiallyVisible = true) {const rect = el.getBoundingClientRect();const vw = window.innerWidth || document.documentElement.clientWidth;const vh = window.innerHeight || document.documentElement.clientHeight;if (partiallyVisible) {return rect.top < vh && rect.bottom > 0 && rect.left < vw && rect.right > 0;}return rect.top >= 0 && rect.left >= 0 &&rect.bottom <= vh && rect.right <= vw;}// Использование IntersectionObserverconst observer = new IntersectionObserver((entries) => {entries.forEach(entry => {if (entry.isIntersecting) {// элемент появился в viewportentry.target.classList.add('visible');observer.unobserve(entry.target); // отписываемся после срабатывания}});}, {root: null, // viewportthreshold: 0.1 // 10% элемента должно быть видно});observer.observe(document.querySelector('.lazy-image'));
Как отвечать на собеседовании
Начните с getBoundingClientRect() как базового решения, объясните логику сравнения координат. Затем упомяните IntersectionObserver как более современный и производительный подход. Подчеркните trade-off: первый - синхронный и простой, второй - асинхронный и эффективный для множества элементов. Упомяните про reflow и необходимость throttle для scroll-обработчиков. Если спросят про edge cases - расскажите про iframe, scrollbar, display: none и пороги видимости.
Что проверяет интервьюер
Интервьюер оценивает понимание работы браузерного рендеринга, знание API и умение выбирать инструмент под задачу. Важно показать осознание производительности: почему getBoundingClientRect() в scroll-обработчике - плохая идея, и как IntersectionObserver решает эту проблему. Также проверяется знание ограничений обоих подходов и умение аргументировать выбор.
Типичные ошибки
- Использование
document.documentElement.clientHeightвместоwindow.innerHeight- при наличии горизонтального scrollbar значения различаются. - Забывают про горизонтальную прокрутку и проверяют только
topиbottom. - Вызов
getBoundingClientRect()в scroll-обработчике без throttle - вызывает reflow на каждое событие. - Не учитывают, что
IntersectionObserverне срабатывает мгновенно - результат приходит асинхронно. - Путают
isIntersectingсо значениемintersectionRatio- первое булево, второе число от 0 до 1. - Проверяют только
display: none, но неvisibility: hiddenилиopacity: 0- эти элементы всё равно считаются видимыми по координатам.
> Похожие задачи по frontend
В чем разница между стрелочной и обычной функцией в контексте this в JavaScript?
Какие события жизненного цикла HTML-страницы существуют
В чем разница XML и JSON
Попадем ли в catch при ошибке в асинхронном вызове внутри try в JavaScript
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью