javascript如何判断一个元素是否在可视区域中
前置概念
client 相关
名称 | 概念 | 备注 |
---|---|---|
clientWidth | 可视区域宽度,包含 padding,但并不包含 border、scrollbar、margin | 单位 px,只读,若元素大小超出可视区域,仅计算可视区域 |
clientHeight | 可视区域高度,包含 padding,但并不包含 border、scrollbar、margin | 单位 px,只读,若元素大小超出可视区域,仅计算可视区域 |
clientTop | 元素上边框宽度,不包含上部的 padding 与 margin | 单位 px,只读,与 borderTopWidth 属性相同 |
clientLeft | 元素左边框宽度,不包含左部的 padding 与 margin | 单位 px,只读,与 borderLeftWidth 属性相同,如果该元素有滚动条且 direction 为 right-to-left,在 windows 平台非 IE 浏览器该属性返回值为左边框宽度+滚动条宽度,IE 返回值为 0,在 macOS 平台均返回左边框宽度 |
offset 相关
名称 | 概念 | 备注 |
---|---|---|
offsetWidth | 可视区域宽度,包含 padding、border、scrollbar,但不包含 margin | 单位 px,只读,若元素大小超出可视区域,仅计算可视区域 |
offsetHeight | 可视区域高度,包含 padding、border、scrollbar,但不包含 margin | 单位 px,只读,若元素大小超出可视区域,仅计算可视区域 |
offsetParent | 返回最近的 position 不为 static 的祖先元素 | 只读,若元素的 display 为’none’,则该属性为 null,该属性是 offsetTop、offsetLeft 的计算依据 |
offsetTop | 返回当前元素顶部相对父元素 offsetParent 顶部的距离,包含当前元素的 margin 及相对父元素的 padding、scrollbar、border | 只读,即当前元素 border 以外(不含 border)相对父元素 offsetParent border 以内(含 border)的距离 |
offsetLeft | 返回当前元素左侧相对父元素 offsetParent 左侧的距离,包含当前元素的 margin 及相对父元素的 padding、scrollbar、border | 只读,即当前元素 border 以外(不含 border)相对父元素 offsetParent border 以内(含 border)的距离 |
scroll 相关
名称 | 概念 | 备注 |
---|---|---|
scrollWidth | 返回元素的全部宽度,包含 padding 但不包含 scrollbar、border、margin | 单位 px,只读,该属性包含了元素超出可视区域的部分 |
scrollHeight | 返回元素的全部高度,包含 padding 但不包含 scrollbar、border、margin | 单位 px,只读,该属性包含了元素超出可视区域的部分 |
scrollTop | 设置或返回元素在垂直方向的滚动像素数 | 如果设置值非法或元素不可滚动,该属性会被设置为 0,如果设置值超过最大值,则设置为最大值 |
scrollLeft | 设置或返回元素在水平方向的滚动像素数 | 如果设置值非法或元素不可滚动,该属性会被设置为 0,如果设置值超过最大值,则设置为最大值 |
getBoundingClientRect()
getBoundingClientRect 是元素上的一个方法,调用后返回元素的大小、相对视窗口的位置等信息。(每次滚动位置改变时,调用该方法的返回值都会变化)
返回的对象有 8 个属性:
名称 | 说明 |
---|---|
x | 元素原点(左上角顶点)的 x 坐标(相对视窗口左边的距离) |
y | 元素原点(左上角顶点)的 y 坐标(相对视窗口顶部的距离) |
width | 元素的宽度,包含 padding、scrollbar、border |
height | 元素的高度,包含 padding、scrollbar、border |
left | 元素左边相对视窗口左边的距离,通常与 x 相同,若 width 是负值,则为 x + width 的值 |
top | 元素顶部相对视窗口顶部的距离,通常与 y 相同,若 height 为负值,则为 y + height 的值 |
right | 元素右边相对视窗口左边的距离,通常与 x + width 相同,若 width 为负值,则为 x 的值 |
bottom | 元素底部相对视窗口顶部的距离,通常与 y + height 相同,若 height 为负值,则为 y 的值 |
Intersection Observer API
Intersection Observer API 提供了一种异步检测目标元素与祖先元素或 viewport(视窗口)相交情况变化的方法。
过去,相交检测通常要用到事件监听,并且需要频繁调用Element.getBoundingClientRect()
方法以获取相关元素边界信息。事件监听和调用Element.getBoundingClientRect()
都是在主线程上运行,因此频繁触发、调用可能会造成性能问题。
假如一个无限滚动页面有很多动画,并使用了两个第三方库,这两个第三方库都有自己的相交检测程序,都运行在主线程中,当用户滚动页面时,这些相交检测程序就会在页面滚动回调函数里不停触发调用,造成性能问题,体验效果让人失望。
Intersection Observer API 会注册一个回调函数,每当被监视的元素进入或者退出另外一个元素时,或者两个元素的相交部分大小发生变化时,该回调方法会被触发执行。这样我们网站的主线程不需要再为了监听元素相交而辛苦劳作,浏览器会自行优化元素相交管理。
注意 Intersection observer API 无法提供重叠的像素个数或者具体哪个像素重叠,它的更常见的使用方式是——当两个元素相交比例在 N%左右时,触发回调,以执行某些逻辑。
使用示例
Intersection Observer 允许配置一个回调函数,当以下情况发生时调用
每当目标(target)元素与设备视窗或其他指定元素发生交集的时候执行。设备视窗或其他元素我们称为根元素。
Observer 第一次监听目标元素时执行。
目标元素(target)与根元素(root)之间的交叉程度叫交叉比(intersection ratio)。这是目标(target)元素相对于根(root)的交集百分比表示,它的取值在 0.0 到 1.0 之间。
// 传递给IntersectionObserver()构造函数的配置对象,控制观察者的回调函数的被调用时环境
const options = {
// 指定根元素,用于检查目标元素可见性。必需是目标元素的祖先元素。如果未指定或为null,默认为浏览器视窗(viewport)
root: document.querySelector("#container"),
// 根元素外边距,用于确定root与target发生交集时计算交集的区域范围,使用该属性可控制root元素每一条边的收缩或扩张。如果指定了root,rootMargin也可以使用百分比来取值。
rootMargin: "0px",
// 触发的交叉比阈值,可以是单一的数字也可以是数字的数组,表示当target与root相交程度达到该值时回调会被执行。
// 若要探测target在root中可见性超过50%时触发回调,则指定该属性为0.5;
// 若要探测target在root中可见程度每多25%就触发一次回调,则指定该属性为[0, 0.25, 0.5, 0.75, 1];
// 默认值为0,表示target有一个像素出现在root中,就会触发回调执行;
// 设置为1,表示target完全出现在root中的时候回调才会执行;
threshold: 1.0,
};
// 回调函数
const callback = (entries, observer) => {
entries.forEach((entry) => {
// 每个entry都表示一个被观察的target元素的相交变化
// entry.boundingClientRect target元素的位置、大小信息
// entry.intersectionRatio target元素与root元素交叉比,实际计算的是entry.intersectionRect 面积 / boundingClinetRect 面积,元素非矩形时,也按矩形计算
// entry.intersectionRect 相交区域的位置、大小信息
// entry.isIntersecting target元素是否与root元素相交
// entry.rootBounds root元素的位置、大小信息
// entry.target target元素本身
// entry.time 从observer的时间原点到交叉触发的时间的时间戳
});
};
// 新建一个相交观察实例
const observer = new IntersectionObserver(callback, options);
// 监听target
observer.observe(document.querySelector("#element"));
判断元素在可视区域中
方法一 使用 scrollTop 与 offsetTop
/**
* 通过元素及其可滚动的父元素,判断当前元素是否在父元素可视区域内(仅判断了垂直滚动)
* @param elementNode 当前元素
* @param containerNode 带滚动条的包裹父元素
*/
function isElementViewableInContainer(elementNode: HTMLDivElement, containerNode: HTMLDivElement) {
// 子元素相对父元素顶部的距离
const elementTop = elementNode.offsetTop;
// 子元素底部相对父元素顶部的距离,使用offsetHeight将边框以内都算入子元素高度
const elementBottom = elementNode.offsetTop + elementNode.offsetHeight;
// 父元素滚动距离
const containerTop = containerNode.scrollTop;
// 父元素当前可视底部距离,使用clientHeight,不包含父元素滚动条及边框高度
const containerBottom = containerNode.scrollTop + container.clientHeight;
// 若子元素顶在父元素滚动后顶部之下 且 子元素底部在父元素滚动后底部之上,则判定为在滚动父元素的可视区域内
if (elementTop >= containerTop && elementBottom <= containerBottom) return true;
// 否则在可视区域外
else return false;
}
方法二 使用 getBoundingClientRect
/**
* 通过元素及其可滚动的父元素,判断当前元素是否在父元素可视区域内(仅判断了垂直滚动)
* @param elementNode 当前元素
* @param containerNode 带滚动条的包裹父元素
*/
function isElementViewableInContainer(elementNode: HTMLDivElement, containerNode: HTMLDivElement) {
// 子元素rect
const elementRect = elementNode.getBoundingClientRect();
// 子元素顶部相对视窗口顶部的距离
const elementTop = elementRect.top;
// 子元素底部相对视窗口顶部的距离
const elementBottom = elementRect.bottom;
// 父元素rect
const containerRect = containerNode.getBoundingClientRect();
// 父元素顶部相对视窗口顶部的距离
const containerTop = containerRect.top;
// 父元素底部相对视窗口顶部的距离
const containerBottom = containerRect.bottom;
// 若子元素顶在父元素滚动后顶部之下 且 子元素底部在父元素滚动后底部之上,则判定为在滚动父元素的可视区域内
if (elementTop >= containerTop && elementBottom <= containerBottom) return true;
// 否则在可视区域外
else return false;
}
方法三 使用 Intersection Observer
// 根元素
const root = document.getElementById("container");
// 目标元素
const target = document.getElementById("element");
// observer配置
const options = {
root: root,
rootMargin: "0px",
threshold: 1,
};
// 是否可见
let isViewable = false;
// 回调函数
const callback = (entries) => {
entries.forEach((entry) => {
if (entry.target.id === root.id) {
// 完全可见时相交比率为1
isViewable = entry.intersectionRatio === 1;
}
});
};
// 新建观察者
const observer = new IntersectionObserver(callback, options);
// 监听目标元素
observer.observe(element);
参考资料
What is offsetHeight, clientHeight, scrollHeight?