# 交叉观察者 - Intersection Observer

Intersection Observer (opens new window) 交叉观察者
懒加载、吸顶、触底效果,原来可以如此的容易。

# 交叉观察者是个啥?

MDN的介绍:
IntersectionObserver接口,提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法,祖先元素与视窗(viewport)被称为根(root)。

简单来说,IntersectionObserver翻译为交叉观察者,它的任务就是监听目标元素指定的父元素(用户不指定,默认为viewport)是否发生了交叉行为

交叉行为就是指监听的目标元素是否进入或者离开了指定父元素的内部。(经典场景: 图片懒加载)

# 基本用法

  1. 构造函数
new IntersectionObserver(callback, options);
  1. callback
    发生交叉行为的回调,接受一个entries参数,返回当前已监听并且发生了交叉行为目标元素的集合。
new IntersectionObserver(entries => {
    entries.forEach(item => console.log(item));
    // ...
});

看看item里面包含的常用属性。

属性 说明
boundingClientRect 空间信息
intersectionRatio 元素可见区域的占比
isIntersecting 是否正在交叉行为,可用做判断元素是否可见
target 目标节点,跟event.target一样

注意

页面初始化的时候会触发一次callbackentries所有已监听的目标集合

  1. options
    顾名思义,它是一个配置参数,对象类型,选填,常用属性如下:
属性 说明
root 指定父元素,默认为视窗
rootMargin 触发交叉的偏移值,默认为"0px 0px 0px 0px"(上左下右,正数向外扩散,负数向内收缩)
  1. 常用方法
名称 说明 参数
observe 开始监听一个目标元素 节点
unobserve 停止监听一个目标元素 节点
taskRecords 返回所有监听的目标元素集合
disconnect 停止所有监听

# 简单例子

  1. 假设页面上有一个class="box"的盒子且父元素为视窗
let box = document.querySelector(".box");

let observer = new IntersectionObserver(entries => {
    entries.forEach(item => {
        let tips = item.isIntersecting ? "进入了父元素的内部" : "离开了父元素的内部";
        console.log(tips);
    });
});

observer.observe(box); // 监听一个box
  1. 假设页面上有多个class="box"的盒子且父元素为视窗
let box = document.querySelectorAll(".box");

let observer = new IntersectionObserver(entries => console.log(`发生交叉行为,目标元素有${entries.length}`));

box.forEach(item => observer.observe(item)); // 监听多个box

# 实际应用

# 图片懒加载

以前都是监听浏览器滚动,然后遍历拿到每个图片的空间信息,然后判断一些位置信息从而进行图片加载,而现在只需要交给交叉观察者做:

let images = document.querySelectorAll("img.lazyload");

let observer = new IntersectionObserver(entries => {
    entries.forEach(item => {
        if (item.isIntersecting) {
            item.target.src = item.target.dataset.origin; // 开始加载图片
            observer.unobserve(item.target); // 停止监听已开始加载的图片
        }
    });
});

images.forEach(item => observer.observe(item));

提示

使用该方法还有一个好处,就是图片横向滚动(轮播图)进来的时候也是有效的。

传统的懒加载只是监听全局滚动条的滚动,像这种小细节还是无法实现的(传统的实现方法并不是判断目标是否出现在视窗,所以横向的图片会一起加载,即使你没有向左滑动),所以这也是交叉观察者的一大优点。

# 触底

我们在列表底部放一个参照元素,然后让交叉观察者去监听:
假设html结构如下:

<!-- 数据列表 -->
<ul>
    <!-- 多个li -->
    <li>index</li> 
</ul>

<!-- 参照元素 -->
<div class="reference"></div>

然后监听参照元素:

new IntersectionObserver(entries => {
    let item = entries[0]; // 拿第一个就行,反正只有一个
    if (item.isIntersecting) {
        console.log("滚动到了底部,开始请求数据");
    }
}).observe(document.querySelector(".reference")); // 监听参照元素

# 动画展示

相信很多人都需要过这种需求,当某个元素出现的时候就给该元素加个动画,比如渐变、偏移等。
假设html结构如下:

<ul>
    <!-- 多个li -->
    <li></li>
</ul>

假设scss代码如下:

ul {
 li {
   &.show {
    // 默认从左边进来
    animation: left 1s ease;
    
    // 偶数从右边进来
    &:nth-child(2n) {
      animation: right 1s ease;
    }
   }
 }
}

@keyframes left {
  from {
    opacity: 0;
    transform: translate(-20px, 20px); // right动画改成20px, 20px即可
  }

  to {
    opacity: 1;
  }
}

然后开始监听:

let list = document.querySelectorAll("ul li");

let observer = new IntersectionObserver(entries => {
    entries.forEach(item => {
        if (item.isIntersecting) {
            item.target.classList.add("show"); // 增加show类名
            observer.unobserve(item.target); // 移除监听
        }
    });
});

list.forEach(item => observer.observe(item));

# 总结

使用交叉观察者的前提必须是子元素跟父元素发生交叉。两个非父子元素的交叉那是不行的。

最近更新时间: 2020/5/26 19:03:58