轻轻松松实现高难度懒加载、吸顶、触底
关注 高级前端进阶 ,回复“ 加群 ”
加入我们一起学习,天天进步
可以先看一下 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
如下:
然后开始监听:
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
>
然后监听 参照元素 :
new IntersectionObserver (entries => {
let item = entries[0]; // 拿第一个就行,反正只有一个
if (item.isIntersecting) console.log( “滚动到了底部,开始请求数据” );
}).
observe
(document.querySelector(
“.reference”
)); // 监听参照元素
效果如下:
3. 吸顶
实现元素吸顶的方式有很多种,如 css 的 position: sticky
,兼容性较差;如果用 交叉观察者
实现也很方便,同样也要放一个 参照元素
;
假设 html
结构如下:
假设 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
交流
如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:
-
点个「 在看 」,让更多的人也能看到这篇内容(喜欢不点在看,都是耍流氓 -_-)
-
关注我的官网 https:// m uyiy.cn ,让我们成为长期关系
-
关注公众号「 高级前端进阶 」,每周重点攻克一个前端面试重难点,公众号后台回复「 资料 」免费送给你精心准备的前端进阶资料。