能够先看一下MDN
中的介绍:css
IntersectionObserver
接口,提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态
的方法,祖先元素与视窗(viewport
)被称为根(root
);html
直接进入正题,IntersectionObserver
翻译为 "交叉观察者
",它的任务就是监听目标元素
跟指定父元素
(用户可指定,默认为viewport
)是否在发生交叉行为
,简单理解就是监听目标元素
是否进入或者离开了指定父元素
的内部(理解这句就好了,管他交不交叉呢),我好像在开车,可是大家没有证据 ... 😐前端
如下的目标元素
简称为目标
、指定父元素
简称为父亲
、交叉行为
简称为交叉
,viewport
简称为视窗
👌git
下面会有动图介绍,先忍忍!github
new IntersectionObserver(callback, options);
发生交叉
的回调,接受一个entries
参数,返回当前已监听
而且发生了交叉
的目标
集合(后面会举例说明为何是"且发生了交叉
"):浏览器
new IntersectionObserver(entries => { entries.forEach(item => console.log(item)); // ... });
咱们看看item
里面包含哪些经常使用
属性:微信
属性 | 说明 | |
---|---|---|
boundingClientRect | 空间信息 | |
intersectionRatio | 元素可见区域的占比 | |
isIntersecting | 字面理解为是否正在交叉 ,可用作判断元素是否可见 |
|
target | 目标节点,就跟event.target 同样 |
注意:页面初始化的时候会触发一次callback
,entries
为全部已监听的目标集合
✅异步
顾名思义,它是一个配置
参数,对象类型,非必填,经常使用
属性以下:函数
属性 | 说明 | |
---|---|---|
root | 指定父元素,默认为视窗 |
|
rootMargin | 触发交叉 的偏移值,默认为"0px 0px 0px 0px"(上左下右,正数为向外扩散,负数则向内收缩) |
new IntersectionObserver(callback, { root: document.querySelector("xx"), rootMargin: "0px 0px -100px 0px" });
若是设置rootMargin
为"20px 0px 30px 30px
",那么元素未到达视窗
时,就已经切换为可见
状态了:学习
名称 | 说明 | 参数 |
---|---|---|
observe | 开始监听一个目标元素 | 节点 |
unobserve | 中止监听一个目标元素 | 节点 |
takeRecords | 返回全部监听的目标元素集合 | |
disconnect | 中止全部监听 |
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
效果以下:
class="box"
的盒子且父元素为视窗
:let box = document.querySelectorAll(".box"); let observer = new IntersectionObserver(entries => console.log(`发生交叉行为,目标元素有${entries.length}个`)); box.forEach(item => observer.observe(item)); // 监听多个box
当全部盒子距离视窗顶部距离一致
时,效果以下:
当全部盒子距离视窗顶部距离不一致
时,效果以下:
为何要举例
以上两种状况呢,由于entries
是返回当前已监听
而且发生了交叉
的目标集合
,第一种状况,你们都一块儿
发生交叉
,固每次返回的集合长度都为三
;第二种状况则是每一个目标轮流
发生交叉
,且当前只触发了一个
,因此每次返回的集合长度只有一
✅
假设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
效果以下:
之前都是监听浏览器滚动,而后遍历拿到每一个图片的空间信息,而后判断一些位置信息从而进行图片加载;而如今只须要交给交叉观察者
去作:
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
"(底部向内收缩):
该方法还有一个好处,那就是当页面上某个节点存在横向滚动条的时候,同样应对自如:
传统的懒加载只是监听全局滚动条的滚动,像这种小细节仍是没法实现的(传统的实现方法并非判断目标是否出如今视窗
,因此横向的图片会一块儿加载,即便你没有向左滑动),因此这也是交叉观察者
的一大优势✅
咱们在列表底部放一个参照元素
,而后让交叉观察者
去监听;
假设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")); // 监听参照元素
效果以下:
实现元素吸顶的方式有不少种,如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);
效果以下:
可是有个问题,当你滚动的慢的时候,会掉进一个死循环:
为了方便观察,咱们把参考元素加一个高度跟颜色:
问题很明显,当给nav
增长fixed
定位时,nav
脱离了文档流,天然参考元素
会往下掉,而后往下掉又发生了交叉
,从而去除fixed
定位,陷入一个死循环;
思考了一会,解决办法是,让参考元素
绝对定位至nav
的上方:
let nav = document.querySelector('nav'); let reference = document.querySelector(".reference"); reference.style.top = nav.offsetTop + "px"; // 如下代码不变 ...
这样,即便nav
脱离的文档流,也不会影响参考元素
的位置:
相信不少人都须要过这种需求,当某个元素出现的时候就给该元素加个动画,好比渐变、偏移等;
假设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));
效果以下:
IE不兼容,不过有官方的polyfill✅
暂时就发现这么多用途啦,值得注意的是,必须是子元素跟父元素发生交叉
,若是你想检查两个非父子关系的交叉
,那是不行
的嘻嘻,若是你以为这篇文章不错,请别忘记点个赞
跟关注
哦~😊
公众号「前端宇宙情报局」
,将不定时更新最新、实用的前端技巧/技术性文章,对了偶尔还会有互联网中的趣事趣闻🍻
关注公众号,回复"1
"获取微信群聊二维码,一块儿学习、一块儿交流、一块儿摸鱼🌊