Table of Contents
前置概念
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);
// 监听targetobserver.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?