利用"交叉观察者"这个小宝贝儿,轻松实现懒加载、吸顶、触底 ❗

可以先看一下MDN中的介绍:

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

直接进入正题,IntersectionObserver 翻译为 "交叉观察者",它的任务就是监听目标元素指定父元素(用户可指定,默认为viewport)是否在发生交叉行为,简单理解就是监听目标元素是否进入或者离开了指定父元素的内部(理解这句就行了,管他交不交叉呢),我好像在开车,但是你们没有证据 ... 😐

image

以下的目标元素简称为目标指定父元素简称为父亲交叉行为简称为交叉viewport简称为视窗 👌

下面会有动图介绍,先忍忍!

用法

1. 构造函数

new IntersectionObserver(callback, options);

2. callback

发生交叉的回调,接受一个entries参数,返回当前已监听并且发生了交叉目标集合(后面会举例说明为什么是"且发生了交叉"):

new IntersectionObserver(entries => {
  entries.forEach(item => console.log(item));
  // ...
});

我们看看item里面包含哪些常用属性:

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

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

3. options

顾名思义,它是一个配置参数,对象类型,非必填,常用属性如下:

属性 说明
root 指定父元素,默认为视窗
rootMargin 触发交叉的偏移值,默认为"0px 0px 0px 0px"(上左下右,正数为向外扩散,负数则向内收缩)
new IntersectionObserver(callback, {
  root: document.querySelector("xx"),
  rootMargin: "0px 0px -100px 0px"
});

如果设置rootMargin为"20px 0px 30px 30px",那么元素未到达视窗时,就已经切换为可见状态了:

image

4. 常用方法

名称 说明 参数
observe 开始监听一个目标元素 节点
unobserve 停止监听一个目标元素 节点
takeRecords 返回所有监听的目标元素集合
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

效果如下:


image

2. 假设页面上有多个class="box"的盒子且父元素为视窗

let box = document.querySelectorAll(".box");

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

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

当所有盒子距离视窗顶部距离一致时,效果如下:

image

当所有盒子距离视窗顶部距离不一致时,效果如下:

image

为什么要举例以上两种情况呢,因为entries是返回当前已监听并且发生了交叉目标集合,第一种情况,大家都一起发生交叉,固每次返回的集合长度都为;第二种情况则是每个目标轮流发生交叉,且当前只触发了一个,所以每次返回的集合长度只有

3. 指定父元素

假设html如下:

<div class="parent">
  <div class="child"></div>
</div>

然后开始监听:

let child = document.querySelector(".child");

let observer = new IntersectionObserver(entries => {
  entries.forEach(item => {
    console.log(item.isIntersecting ? "可见" : "不可见");
  });
}, {
  root: document.querySelector(".parent")
});

observer.observe(child); // 开始监听child

效果如下:


image

实际应用

1. 图片懒加载

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

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));

效果如下:


image

把网速调慢:


image

设置rootMargin偏移值为"0px 0px -100px 0px"(底部向内收缩):

image

该方法还有一个好处,那就是当页面上某个节点存在横向滚动条的时候,一样应对自如:


image

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

2. 触底

我们在列表底部放一个参照元素,然后让交叉观察者去监听;

假设html结构如下:

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

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

然后监听参照元素:

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

效果如下:


image

3. 吸顶

实现元素吸顶的方式有很多种,如css的position: sticky,兼容性较差;如果用交叉观察者实现也很方便,同样也要放一个参照元素

假设html结构如下:

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

<nav>我可以吸顶</nav>

假设scss代码如下:

nav {
  &.fixed {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
  }
}

开始监听:

let nav = document.querySelector('nav');
let reference = document.querySelector(".reference");

new IntersectionObserver(entries => {

  let item = entries[0];
  let top = item.boundingClientRect.top;

  // 当参照元素的的top值小于0,也就是在视窗的顶部的时候,开始吸顶,否则移除吸顶
  if (top < 0) nav.classList.add("fixed");
  else nav.classList.remove("fixed");

}).observe(reference);

效果如下:


image

但是有个问题,当你滚动的慢的时候,会掉进一个死循环:


image

为了方便观察,我们把参考元素加一个高度跟颜色:


image

问题很明显,当给nav增加fixed定位时,nav脱离了文档流,自然参考元素会往下掉,然后往下掉又发生了交叉,从而去除fixed定位,陷入一个死循环;

思考了一会,解决办法是,让参考元素绝对定位至nav的上方:

let nav = document.querySelector('nav');
let reference = document.querySelector(".reference");

reference.style.top = nav.offsetTop + "px";

// 以下代码不变 ...

这样,即使nav脱离的文档流,也不会影响参考元素的位置:

image

4. 动画展示

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

假设html结构如下:

<ul>
  <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));

效果如下:


image

兼容性

IE不兼容,不过有官方的polyfill

最后

暂时就发现这么多用途啦,值得注意的是,必须是子元素跟父元素发生交叉,如果你想检查两个非父子关系的交叉,那是不行的嘻嘻,如果你觉得这篇文章不错,请别忘记点个关注哦~😊

交流

公众号「前端宇宙情报局」,将不定时更新最新、实用的前端技巧/技术性文章,对了偶尔还会有互联网中的趣事趣闻🍻

关注公众号,回复"1"获取微信群聊二维码,一起学习、一起交流、一起摸鱼🌊

image

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容