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

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


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


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


3. options


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

属性
说明
root
指定父元素,默认为 视窗
rootMargin
触发 交叉 的偏移值,默认为"0px 0px 0px 0px"(上左下右,正数为向外扩散,负数则向内收缩)

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

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


效果如下:
640?wx_fmt=gif
2. 假设页面上有多个class="box"的盒子且父元素为视窗


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

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

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


当所有盒子距离视窗顶部距离 一致 时,效果如下:
640?wx_fmt=gif
当所有盒子距离视窗顶部距离 不一致 时,效果如下:
640?wx_fmt=gif
为什么要 举例 以上两种情况呢,因为 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


效果如下:
640?wx_fmt=gif


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


效果如下:
640?wx_fmt=gif
把网速调慢:
640?wx_fmt=gif
设置 rootMargin 偏移值为" 0px 0px -100px 0px "(底部向内收缩):
640?wx_fmt=gif
该方法还有一个好处,那就是当页面上某个节点存在 横向滚动 条的时候,一样应对自如:
640?wx_fmt=gif
传统的懒加载只是监听全局滚动条的滚动,像这种小细节还是无法实现的(传统的实现方法并不是判断目标是否出现在 视窗 ,所以横向的图片会一起加载,即使你没有向左滑动),所以这也是 交叉观察者 的一大优点✅


2. 触底

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


假设 html 结构如下:


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

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


然后监听参照元素:


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


效果如下:
640?wx_fmt=gif
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);


效果如下:
640?wx_fmt=gif
但是有个问题,当你滚动的慢的时候,会掉进一个死循环:
640?wx_fmt=gif
为了方便观察,我们给参考元素加一个高度跟颜色:
640?wx_fmt=gif
问题很明显,当给 nav 增加 fixed 定位时, nav 脱离了文档流,自然 参考元素 会往下掉,然后往下掉又发生了 交叉 ,从而去除 fixed 定位,陷入一个死循环;


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


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

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

// 以下代码不变 ...


这样,即使 nav 脱离的文档流,也不会影响 参考元素 的位置:
640?wx_fmt=gif
4. 动画展示


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


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


效果如下:
640?wx_fmt=gif


0 4
浏览器兼容性




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




0 5
      总结      




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

扫一扫 关注我的公众号【前端名狮】,更多精彩内容陪伴你!



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值