点击上方“前端进阶周刊”,选择“置顶或者星标”
期待你的关注!
可以先看一下
MDN
中的介绍:
IntersectionObserver
接口,提供了一种异步观察
目标元素与其祖先元素或顶级文档视窗(
viewport
)
交叉状态
的方法,祖先元素与视窗(
viewport
)被称为根(
root
);
直接进入正题,
IntersectionObserver
翻译为 "
交叉观察者
",它的任务就是监听
目标元素
跟
指定父元素
(用户可指定,默认为
viewport
)是否在发生
交叉行为
,简单理解就是监听
目标元素
是否进入或者离开了
指定父元素
的内部(理解这句就行了,管他交不交叉呢),
我好像在开车,但是你们没有证据 ...
?
![552e79a13643412e1cc7046fab79e28e.png](https://i-blog.csdnimg.cn/blog_migrate/ed027dcd568d1fe564b1efa19197b641.png)
以下的
目标元素
简称为
目标
、
指定父元素
简称为
父亲
、
交叉行为
简称为
交叉
,
viewport
简称为
视窗
?
下面会有动图介绍,先忍忍!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
为
所有已监听的目标集合
✅
3. options
顾名思义,它是一个
配置
参数,对象类型,非必填,
常用
属性如下:
属性 | 说明 |
root | 指定父元素,默认为视窗 |
rootMargin | 触发交叉 的偏移值,默认为"0px 0px 0px 0px"(上左下右,正数为向外扩散,负数则向内收缩) |
如果设置
rootMargin
为"
20px 0px 30px 30px
",那么元素未到达
视窗
时,就已经切换为
可见
状态了:
4. 常用方法
名称 | 说明 | 参数 |
observe | 开始监听一个目标元素 | 节点 |
unobserve | 停止监听一个目标元素 | 节点 |
takeRecords | 返回所有监听的目标元素集合 | |
disconnect | 停止所有监听 | |
0
2简单例子
1. 假设页面上有一个
的盒子且父元素为视窗
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. 假设页面上有多个
的盒子且父元素为视窗
:
let box = document.querySelectorAll(".box");let observer = new IntersectionObserver(entries => console.log(`发生交叉行为,目标元素有${entries.length}个`));box.forEach(item => observer.observe(item)); // 监听多个box
当所有盒子距离视窗顶部距离
一致
时,效果如下:
![f601490deccf0a737df280eab7a6159f.gif](https://i-blog.csdnimg.cn/blog_migrate/d1c44213d0e34a4b95ce6261679c8762.gif)
当所有盒子距离视窗顶部距离
不一致
时,效果如下:
![9f1bfa3684444a28edb95c15d3183c02.gif](https://i-blog.csdnimg.cn/blog_migrate/dee4cffc59a32ef0cfe26ff03c5c0f56.gif)
为什么要
举例
以上两种情况呢,因为
entries
是返回当前
已监听
并且发生了
交叉
的
目标集合
,第一种情况,大家都
一起
发生
交叉
,固每次返回的集合长度都为
三
;第二种情况则是每个目标
轮流
发生
交叉
,且当前只触发了
一个
,所以每次返回的集合长度只有
一
✅
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));
效果如下:
![9d2416f54984cf82c78c3a3900738bac.gif](https://i-blog.csdnimg.cn/blog_migrate/81e66e949cb434a852c6e77149c73be7.gif)
把网速调慢:
![8cd48079cf4d30a816b890baf126364f.gif](https://i-blog.csdnimg.cn/blog_migrate/38057cd7523e03ac9e40f85d12bad784.gif)
设置
rootMargin
偏移值为"
0px 0px -100px 0px
"(底部向内收缩):
![26aaeca0cb0e4da4251dcd84102239e8.gif](https://i-blog.csdnimg.cn/blog_migrate/2184f42684a5308c47887841216de36b.gif)
该方法还有一个好处,那就是当页面上某个节点存在
横向滚动
条的时候,一样应对自如:
![69370f737a393de0dbd5e9079d100e1a.gif](https://i-blog.csdnimg.cn/blog_migrate/f83aee3b7150df3bf001365e616daf88.gif)
传统的懒加载只是监听全局滚动条的滚动,像这种小细节还是无法实现的(传统的实现方法并不是判断目标是否出现在
视窗
,所以横向的图片会一起加载,即使你没有向左滑动),所以这也是
交叉观察者
的一大优点✅
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);
效果如下:
![c220d07b149542204febfec5c9c9edcc.gif](https://i-blog.csdnimg.cn/blog_migrate/5204223dcf967b6bc5f4d86b99e7021d.gif)
但是有个问题,当你滚动的慢的时候,会掉进一个死循环:
![d9ebc301e79346d53f74bb4801db9639.gif](https://i-blog.csdnimg.cn/blog_migrate/639a7f42eaf5deac0f4c6243613dfab2.gif)
为了方便观察,我们给参考元素加一个高度跟颜色:
![b27534997bfa09938bc0f15698a42a7f.gif](https://i-blog.csdnimg.cn/blog_migrate/473e1ba08dcd426f4e6f936a61a1d0aa.gif)
问题很明显,当给
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 总结
暂时就发现这么多用途啦,值得注意的是,必须是
子元素跟父元素发生交叉
,如果你想检查两个
非父子关系的交叉
,那是
不行
的嘻嘻,如果你觉得这篇文章不错,请别忘记在右下角点个在看哦~?
![9b4f4934910b14f5d895b39b4bfc02ca.png](https://i-blog.csdnimg.cn/blog_migrate/a13b552da4a77f6bfc92bcc1fa2c2f18.jpeg)