判断元素在可视区域的方法有三种:
offsetTop
,scrollTop
getBoundingClientRect
IntersectionObserver
js
`getViewHeight`:获取视图窗口高度
const getViewHeight = window.innerHeight || // (IE9+)
document.documentElement.clientHeight || //(W3C 标准盒模型下)
document.body.clientHeight // (老IE)
一、offsetTop + scrollTop (传统方式)
offsetTop
: 当前元素相对于其 offsetParent
元素的顶部内边距的距离 scrollTop
: 元素的内容顶部到它的视口可见内容的顶部的距离
js
const getOffsetTop = (el) => el.offsetTop
const getScrollTop = () => document.documentElement.scrollTop
在视图区域内: getViewHeight <= (getOffsetTop() - getScrollTop())
二、getBoundingClientRect ### (兼容性更好)
Element.getBoundingClientRect()
获取一个元素的 位置和尺寸信息,相对于浏览器视口(ViewPort)的位置。
js
const rect = document.getElementById('myElement').getBoundingClientRect();
console.log(rect);
{
top: 100, // 元素上边到视口顶部的距离
right: 300, // 元素右边到视口右边的距离
bottom: 200, // 元素下边到视口底部的距离
left: 50, // 元素左边到视口左边的距离
width: 250, // 元素宽度(只读,现代浏览器支持)
height: 100, // 元素高度(只读,现代浏览器支持)
x: 50, // left 的别名
y: 100 // top 的别名
}
在视图区域内: getViewHeight() >= rect.top
三、IntersectionObserver (性能更好)
IntersectionObserver
是现代 Web 开发中用于监听元素与视口(或祖先容器)交叉状态 的 API。异步执行的,不会频繁触发重排。
常用于实现:
- 懒加载图片/内容
- 无限滚动
- 广告可见性统计
- 动态加载组件等
js
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) { // 元素进入视口
console.log('元素已进入视口:', entry.target);
observer.unobserve(entry.target); // 可选:只观察一次后取消监听
}
});
}, {
root: null, // 监听相对于哪个容器,默认为浏览器视口
rootMargin: '0px 0px 200px 0px' // 视口边缘偏移,距离底部200px就开始加载
threshold: [0.1, 0.2] // 分别触发回调,根据不同的 `intersectionRatio` 执行不同的逻辑
});
// 要监听的目标元素
const target = document.querySelector('#myElement');
observer.observe(target);
其他补充
1. content-visibility 和 contain-intrinsic-size
content-visibility
是 CSS 的一个新特性,属于 CSS Containment 模块的一部分。它允许浏览器跳过对某些元素的渲染工作(包括布局和绘制),直到它们进入视口或被主动触发渲染。
这个属性对于提升页面性能、实现"懒加载"效果,适合内容量大的页面(如长列表、文章流、卡片墙等)。
效果说明:
- 在视口内,渲染完整内容
- 不在视口内,跳过渲染,仅保留固定高度(由
contain-intrinsic-size
指定)
css
.lazy-render {
content-visibility: auto;
contain-intrinsic-size: auto 100px; /* 告诉浏览器预留多少高度 */
}
2. 用 IntersectionObserver 实现图片懒加载
html
<img data-src="image.jpg" alt="Lazy Image" class="lazy-img">
js
const images = document.querySelectorAll('.lazy-img');
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.add('loaded');
observer.unobserve(img); // 加载完成后停止监听
}
});
}, {
rootMargin: '0px',
threshold: 0.01
});
images.forEach(img => imageObserver.observe(img));