做为一名前端工程师咱们常常须要判断目标元素是否在视窗以内或者和视窗的距离小于一个值(例如 100 px),从而实现一些经常使用的功能,例如:前端
说明:可点击连接的预加载 这个功能目前使用的网站还比较少,其实就是“预先获取页面可视区域内的连接,加快后续加载速度”,能极大提高用户在站内跳转时的体验,由 Google 在 2018 年年末经过 quicklink 项目 进行开源。git
目前流行的方式是经过 Element.getBoundingClientRect()
拿到元素的相关位置信息后进行手动的判断,可是这种方法因为运行在 JavaScript 的主进程上,因此当须要监听的元素较多时,可能会形成性能问题。github
那么仔细想想,其实在浏览器渲染的时候,它就知道了元素是否在视窗以内,自身面积有多少在视窗以内。因此最高效的方式就是预先告诉浏览器当目标元素和视窗重叠的时候,咱们要搞事情,而后等着浏览器执行回调函数便可。出于这种考虑,W3C 提出了 Intersection Observer API
。浏览器
我作了一个小实验,建立了一个十万个节点的长列表,当节点滚入到视窗中时,背景就会从红色变为黄色。前端工程师
下图是使用 Element.getBoundingClientRect()
进行计算实现的效果,能够看到有很是明显的卡顿,主要是由于须要对每个元素都进行计算,判断它们是否在视窗以内。具体的代码能够点击查看 Code Pen。函数
下图是使用 Intersection Observer API
进行注册回调实现的效果,能够看出来十分流畅。具体的代码能够点击查看 Code Pen。性能
本文接下来就分别介绍这两种方法。网站
Element.getBoundingClientRect()
- 手动计算经过 Element.getBoundingClientRect()
,咱们能够拿到元素在视窗内的位置,包括其距离视窗的上下左右的距离和它自身的宽高。ui
const target = document.querySelector('.target');
const clientRect = target.getBoundingClientRect();
// log data
console.log(clientRect);
// {
// bottom: 556.21875,
// height: 393.59375,
// left: 333,
// right: 1017,
// top: 162.625,
// width: 684
// }
复制代码
能够经过来自 MDN 的一张图进行说明:spa
若是一个元素在视窗以内的话,那么它必定知足下面四个条件:
考虑到不一样浏览器的兼容性,能够写出来以下的函数用于判断元素是否在视窗以内:
function isInViewPort(element) {
const viewWidth = window.innerWidth || document.documentElement.clientWidth;
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
const {
top,
right,
bottom,
left,
} = element.getBoundingClientRect();
return (
top >= 0 &&
left >= 0 &&
right <= viewWidth &&
bottom <= viewHeight
);
}
// usage
console.log(isInViewPort(document.querySelector('.target'))); // true or false
复制代码
Intersection Observer API
注册回调Intersection Observer
即重叠观察者,从这个命名就能够看出它用于判断两个元素是否重叠。
这个 API 使用十分简单,只需两步:建立观察者 和 传入被观察者。
const options = {
// 表示重叠面积占被观察者的比例,从 0 - 1 取值,
// 1 表示彻底被包含
threshold: 1.0,
};
const callback = (entries, observer) => { ....}
const observer = new IntersectionObserver(callback, options);
复制代码
经过上面的几行代码就建立了观察者 observer
,传入的参数 callback
在重叠比例超过 threshold
时会被执行。
说明:
options
支持传入更多的参数来指定根元素,未传入时使用视窗元素。
const target = document.querySelector('.target');
observer.observe(target);
// 上段代码中被省略的 callback
const callback = function(entries, observer) {
entries.forEach(entry => {
entry.time; // 触发的时间
entry.rootBounds; // 根元素的位置矩形,这种状况下为视窗位置
entry.boundingClientRect; // 被观察者的位置举行
entry.intersectionRect; // 重叠区域的位置矩形
entry.intersectionRatio; // 重叠区域占被观察者面积的比例(被观察者不是矩形时也按照矩形计算)
entry.target; // 被观察者
});
};
复制代码
经过 observer.observe(target)
这一行代码便可简单的注册被观察者
注意:目前在浏览器的原生支持还不是很好,可使用 w3c - IntersectionObserver Polifill 进行兼容。