34

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

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzU3NjczNDk2MA%3D%3D&%3Bmid=2247484755&%3Bidx=1&%3Bsn=cc288e4938da105ee5540b4c4d9ce12b
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

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

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

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

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

以下的 目标元素 简称为 目标 指定父元素 简称为 父亲 交叉行为 简称为 交叉 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 ",那么元素未到达 视窗 时,就已经切换为 可见 状态了:

VZvUNr7.png!web

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

效果如下:

Zb6by2N.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

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

mqQvaiZ.gif

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

2QZzEfI.gif

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

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

效果如下:

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

效果如下:

mAFZni3.gif

把网速调慢:

3Eriam2.gif

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

3eaUVbV.gif

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

uI3yEni.gif

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

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"

)); // 监听参照元素

效果如下:

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

效果如下:

I3a2Ubi.gif

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

RzyeqmU.gif

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

7ZVBjaf.gif

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

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

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

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

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

// 以下代码不变 ...

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

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

效果如下:

ymmuIrB.gif

0 4

浏览器兼容性

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

0 5

总结      

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

0 6

交流     

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

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

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

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

AJ3mAna.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK