判断元素是否在视窗以内

做为一名前端工程师咱们常常须要判断目标元素是否在视窗以内或者和视窗的距离小于一个值(例如 100 px),从而实现一些经常使用的功能,例如:前端

  • 图片的懒加载
  • 列表的无限滚动
  • 计算广告元素的曝光状况
  • 可点击连接的预加载

说明:可点击连接的预加载 这个功能目前使用的网站还比较少,其实就是“预先获取页面可视区域内的连接,加快后续加载速度”,能极大提高用户在站内跳转时的体验,由 Google 在 2018 年年末经过 quicklink 项目 进行开源。git

目前流行的方式是经过 Element.getBoundingClientRect() 拿到元素的相关位置信息后进行手动的判断,可是这种方法因为运行在 JavaScript 的主进程上,因此当须要监听的元素较多时,可能会形成性能问题。github

那么仔细想想,其实在浏览器渲染的时候,它就知道了元素是否在视窗以内,自身面积有多少在视窗以内。因此最高效的方式就是预先告诉浏览器当目标元素和视窗重叠的时候,咱们要搞事情,而后等着浏览器执行回调函数便可。出于这种考虑,W3C 提出了 Intersection Observer API浏览器

我作了一个小实验,建立了一个十万个节点的长列表,当节点滚入到视窗中时,背景就会从红色变为黄色。前端工程师

下图是使用 Element.getBoundingClientRect() 进行计算实现的效果,能够看到有很是明显的卡顿,主要是由于须要对每个元素都进行计算,判断它们是否在视窗以内。具体的代码能够点击查看 Code Pen函数

Element.getBoundingClientRect() 实现

下图是使用 Intersection Observer API 进行注册回调实现的效果,能够看出来十分流畅。具体的代码能够点击查看 Code Pen性能

Intersection Observer API

本文接下来就分别介绍这两种方法。网站

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

MDN - getBoundingClientRect()

若是一个元素在视窗以内的话,那么它必定知足下面四个条件:

  • top 大于等于 0
  • left 大于登陆 0
  • bottom 小于等于视窗高度
  • right 小于等于视窗宽度

考虑到不一样浏览器的兼容性,能够写出来以下的函数用于判断元素是否在视窗以内:

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 进行兼容。

相关文章
相关标签/搜索