轻轻松松实现高难度懒加载、吸顶、触底

关注  高级前端进阶 ,回复“ 加群

加入我们一起学习,天天进步

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

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

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

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

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

0 1

基本用法

1. 构造函数

new IntersectionObserver (callback, options);

2. callback

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

new IntersectionObserver (entries => {

entries. forEach (item => console. log (item));

// …

});

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

属性

说明

boundingClientRect

空间信息

intersectionRatio

元素可见区域的占比

isIntersecting

字面理解为是否正在
交叉
,可用做判断元素是否可见

target

目标节点,就跟
event.target
一样

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

3. options

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

属性

说明

root

指定父元素,默认为
视窗

rootMargin

触发
交叉
的偏移值,默认为” 0px 0px 0px 0px “(上左下右,正数为向外扩散,负数则向内收缩)

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

4. 常用方法

名称

说明

参数

observe

开始监听一个目标元素

节点

unobserve

停止监听一个目标元素

节点

takeRecords

返回所有监听的目标元素集合

disconnect

停止所有监听

0 2

简单例子

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

效果如下:

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

let box = document.querySelectorAll( “.box” );

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

box.
forEach
(item => observer.
observe

(item)); // 监听多个box

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

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

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

3. 指定父元素

假设 html 如下:

“parent” >

“child” >

然后开始监听:

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

效果如下:

0 3

实际应用

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

效果如下:

把网速调慢:

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

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

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

2. 触底

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

假设 html 结构如下:

  • index
  • // 多个li

“reference”

>

然后监听 参照元素

new IntersectionObserver (entries => {

let item = entries[0]; // 拿第一个就行,反正只有一个

if (item.isIntersecting) console.log( “滚动到了底部,开始请求数据” );

}).
observe
(document.querySelector(
“.reference”

)); // 监听参照元素

效果如下:

3. 吸顶

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

假设 html 结构如下:

“reference” >

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

效果如下:

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

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

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

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

let nav = document.querySelector( nav );

let reference = document.querySelector( “.reference” );

reference.style.top = nav.offsetTop + “px” ;

// 以下代码不变 …

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

4. 动画展示

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

假设 html 结构如下:

  • // 多个li

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

效果如下:

0 4

浏览器兼容性

IE 不兼容,不过有官方的 polyfill, 链接地址为: https://github.com/w3c/IntersectionObserver/tree/master/polyfill

0 5

总结      

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

0 6

交流     

如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:

  1. 点个「 在看 」,让更多的人也能看到这篇内容(喜欢不点在看,都是耍流氓 -_-)

  2. 关注我的官网  https:// m uyiy.cn ,让我们成为长期关系

  3. 关注公众号「 高级前端进阶 」,每周重点攻克一个前端面试重难点,公众号后台回复「 资料 」免费送给你精心准备的前端进阶资料。