返回 blog
2020年12月22日
2 分钟阅读

图片懒加载

2021年了,不会还在用scrollTop、getBoundingClientRect这些API来判断图片是否进入可视区域吧?

Intersection Observer API

这是新的武器,用于检测目标元素与祖先元素或视窗的相交情况。 (异步的,不会阻塞UI渲染)

以前我们要判断一个元素是否可见,或者两个元素是否相交,需要用到各种宽度高度之类的api,现在只需要这个 intersection observer 就全搞定了:

  • 图片懒加载
  • 内容无限滚动 (怎么判断内容已到达底部)
  • 检测一些重要视图的曝光情况, 如广告
  • 在用户看见的时候再去执行任务或播放动画(性能优化)

过去,相交检测通常要用到事件监听,并且需要频繁调用Element.getBoundingClientRect() 方法以获取相关元素的边界信息。事件监听和调用 Element.getBoundingClientRect()  都是在主线程上运行,因此频繁触发、调用可能会造成性能问题。这种检测方法极其怪异且不优雅

intersection observer 为什么更好呢?

Intersection Observer API 会注册一个回调函数,每当被监视的元素进入或者退出另外一个元素时(或者 viewport ),或者两个元素的相交部分大小发生变化时,该回调方法会被触发执行。这样,我们网站的主线程不需要再为了监听元素相交而辛苦劳作,浏览器会自行优化元素相交管理

何为相交?

Intersection Observer 使用方法

// 定义相交监视器
var observer = new IntersectionObserver(changes => {
  for (const change of changes) {
    console.log(change.time);               // 发生变化的时间
    console.log(change.rootBounds);         // 根元素的矩形区域的信息
    console.log(change.boundingClientRect); // 目标元素的矩形区域的信息
    console.log(change.intersectionRect);   // 目标元素与视口(或根元素)的交叉区域的信息
    console.log(change.intersectionRatio);  // 目标元素与视口(或根元素)的相交比例
    console.log(change.target);             // 被观察的目标元素
  }
}, {});
// 开始观察某个目标元素
observer.observe(target);
// 停止观察某个目标元素
observer.unobserve(target);
// 关闭监视器
observer.disconnect();

使用方式:

const intersectionObserver = new IntersectionObserver(callback, options)
intersectionObserver.observe(target)  // 给target注册监听

具体示例:

function ob(target) {
	const options = {
    root: document.querySelector('#x'), // 根元素。如果为空时,则默认视窗。
    rootMargin: '0px', // 根元素的外边距
    threshold: 0.25, // number或数组。表示target与root相交的比例,达到比例时则触发回调函数。若指定0,表示只要节点进入可视区域马上触发回调。若指定1,表示节点完全进入可视区域时触发回调(可以用来做无限滚动)
  }
  const intersectionObserver = new IntersectionObserver((entries, observer)=>{
    entries.forEach(entry=>{
      //   entry.boundingClientRect
      //   entry.intersectionRatio
      //   entry.intersectionRect
      //   entry.isIntersecting
      //   entry.rootBounds
      //   entry.target
      //   entry.time
      if(entry.isIntersecting) {
        // 若相交了,执行一些业务逻辑,如懒加载
        entry.target.setAttribute('src', 'xxx')
        ...
        observer.disconnect()
      }
    })
  }, options)
  intersectionObserver.observer(target)
}

window.onload = () => {
	const imgs = [...document.querySelector('img')]
  imgs.forEach(img=>{
  	ob(img)
  })
}

请留意,你注册的回调函数将会在主线程中被执行。所以该函数执行速度要尽可能的快。如果有一些耗时的操作需要执行,建议使用 Window.requestIdleCallback() 方法。

兼容性: image.png IE球球你做个人吧

相关插件: https://github.com/thebuilder/react-intersection-observer

使用IntersectionObserver实现无限滚动

在需要无限滚动的下方加一个空盒子,这个盒子用来判断内容是否滚动到了底部

<template>
  <div class="outter">
    <div class="inner"></div>
    <div ref="inner" class="test"></div>
  </div>
</template>

<script>
export default {
  methods: {
    createOb(target) {
      const options = {
        // root: document.documentElement,
        rootMargin: "0px 0px 0px 0px", // 如果需要一些提前量,可以设置rootMargin
        threshold: [1]
      };

      const io = new IntersectionObserver((entries, observer) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            console.log("滚动到底部了");
          }
        });
      }, options);

      io.observe(target);
    },
  },
  mounted() {
    // console.log(this.$refs.inner);
    this.createOb(this.$refs.inner);
  },
};
</script>

<style lang="less" scoped>
.outter {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  padding: 32px;
  .inner {
    height: 2000px;
    width: 100%;
    background-color: #000;
  }
  .test {
    width: 100%;
    height: 0px;
  }
}
</style>