如何判断元素是否在可视区域中
判断元素是否在可视区域(视口)内是一个非常常见的需求(比如懒加载、埋点统计、滚动动画触发等)
实现的方案
shell
共有三种方案实现,分别是:IntersectionObserver API(现代方案,推荐)、getBoundingClientRect 传统方案,兼容性好)、offsetTop + 滚动距离(冷门方案,不推荐)IntersectionObserver API 方案 - 视口观察器
shell
# IntersectionObserver API 是什么
是浏览器原生提供的异步监听元素可见性的 API,性能最优(不会在滚动时频繁触发计算),也是目前最推荐的方案。
IntersectionObserver 是构造函数,第一个参数为回调函数,第二个参数为配置对象
# 优点
IntersectionObserver 是异步的,不会阻塞主线程,滚动时性能远优于传统的滚动监听 + 计算方案;
# 原理
IntersectionObserver 构造函数的第一个回调函数中,可获取到元素进入和离开可视区域的时机,第二个参数配置对象:可指定监听的容器、距可视区距离、元素可见性等
词法专门为观察 元素是否进入可视区域 而生
# 代码示例
# 1. 创建观察器实例
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
# entry.isIntersecting 为 true 时,表示元素进入可视区域
if (entry.isIntersecting) {
console.log('元素进入可视区域', entry.target);
# 可在这里执行业务逻辑:比如加载图片、触发动画、统计曝光等
# 如果只需要监听一次,可取消观察
# observer.unobserve(entry.target);
} else {
console.log('元素离开可视区域', entry.target);
}
});
}, {
# 可选配置项(按需调整)
root: null, # 监听的根容器,默认是视口(null)
rootMargin: '0px', # 可视区域的扩展/收缩(比如 '100px' 表示元素距离视口100px时就触发)
threshold: 0 # 触发回调的可见比例(0=只要有1像素可见就触发;1=元素完全可见才触发)
});
# 2. 监听目标元素
const targetElement = document.querySelector('#your-target-element');
if (targetElement) {
observer.observe(targetElement);
}
# 3. 销毁观察器(比如组件卸载时)
# observer.disconnect();getBoundingClientRect 方案 - 获取边界客户端矩形
shell
# getBoundingClientRect
此方案是通过计算元素的位置与视口的关系来判断,兼容性覆盖所有浏览器,但需要结合 scroll 事件监听,滚动时频繁触发会有性能风险(建议加节流)。
getBoundingClientRect 是 dom 元素的一个属性,获取元素的边界位置,返回元素的 top/left/bottom/right/width/height,都是相对于视口左上角的坐标(最后一句很关键)
# 优点
兼容旧版本浏览器
# 原理
getBoundingClientRect 可获取元素的边界位置相对左上角的坐标信息 可以此判断元素是否进入可视区
还可以获取 视口的宽高 结合 getBoundingClientRect 信息判断元素是否整体进入可视区,还是部分进入可视区
# 代码示例
function isElementInViewport(el) {
if (!el) return false;
# 获取元素的top/left/bottom/right/width/height,都是相对于视口左上角的坐标
const rect = el.getBoundingClientRect();
# 视口的宽高(兼容移动端)
const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
# 判断逻辑:元素的四个边都在视口内(可根据需求调整,比如只要部分可见)
return (
rect.top >= 0 && # 元素顶部不超出视口上沿
rect.left >= 0 && # 元素左侧不超出视口左沿
rect.bottom <= viewportHeight && # 元素底部不超出视口下沿
rect.right <= viewportWidth # 元素右侧不超出视口右沿
);
}
# 2. 滚动监听(加节流优化性能)
let timer = null;
window.addEventListener('scroll', () => {
clearTimeout(timer);
timer = setTimeout(() => {
const target = document.querySelector('#your-target-element');
const isInViewport = isElementInViewport(target);
console.log('元素是否在可视区域:', isInViewport);
}, 100); # 100ms 节流,减少计算次数
});offsetTop + 滚动距离(冷门方案,不推荐)方案
shell
# 原理
滚动距离、视口高度/宽度、 元素相对于文档的偏移量 以此判断元素是否出现在可视区域
这是早期的手动计算方案,需要逐层计算元素的偏移量,容易出错(比如有定位父元素时),仅作为兼容性兜底
function isInViewportByOffset(el) {
if (!el) return false;
# 滚动距离
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
# 视口高度/宽度
const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
# 元素相对于文档的偏移量(需逐层计算,这里简化为直接取offsetTop,仅适用于无定位父元素)
const elTop = el.offsetTop;
const elLeft = el.offsetLeft;
const elHeight = el.offsetHeight;
const elWidth = el.offsetWidth;
# 判断逻辑
return (
elTop + elHeight > scrollTop && # 元素底部 > 滚动顶部
elTop < scrollTop + viewportHeight && # 元素顶部 < 滚动顶部+视口高度
elLeft + elWidth > scrollLeft && # 元素右侧 > 滚动左侧
elLeft < scrollLeft + viewportWidth # 元素左侧 < 滚动左侧+视口宽度
);
}