35

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

 5 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.
neoserver,ios ssh client

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

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

可以先看一下 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


Recommend

  • 64

    NEXT关站公告 NEXT关站公告 由于种种原因,NEXT即将关站,感谢大家5年来对NEXT的关注与热爱。 在没有N...

  • 36

    程序员 - @GoPHP - 上家公司老板无耻,创业失败不发工资,公司所有人都被逼走了,他又搞了一帮人来,结果离职的工资还不发,少则欠了半个月多则 2 个月!发现用 go 写个脚本调网站接口特别爽,轻轻松松开 1000 个

  • 71
    • 掘金 juejin.im 6 years ago
    • Cache

    Vue开发——实现吸顶效果

    因为项目需求,最近开始转到微信公众号开发,接触到了Vue框架,这个效果的实现虽说是基于Vue框架下实现的,但是同样也可以借鉴到其他地方,原理都是一样的。 进入正题,先看下效果图: 其实js做这个效果还是挺简单的,因为在css中我们可以设置一个元素的posit

  • 42

    前言 原谅我,前半句是真的,后半句是噱头,但是真的很简化了。 MLSQL已经有一个相对来比较完善的Python Runtime,细节可以参看这篇文章,所以玩深度学习是很容易的,不过需要你提供一段tensorflow代码或者项目。 但...

  • 93

    前言 我入职第二家公司接到的第一个需求就是修复之前外包做的滚动吸顶效果。我当时很纳闷为何一个滚动吸顶会有 bug,后来我查看代码才发现直接用的是 offsetTop 这个属性,而且并没有做兼容性处理。 offsetTop 用于获得当前元素到定位父级( elem

  • 47

    前言 近日,在做活动页的过程中遇到两层吸顶的需求,并且要兼容 IE9 及以上的浏览器。乍一看不就是个吸顶嘛,应该不难吧,事实证明还是踩了很多坑才出来。兼容性问题多到吐血,我太难了。废话不多说,先看一下两层吸顶的最终实...

  • 26

    基于 Vue 的两层吸顶踩坑总结 2019-11-17 发布于 ...

  • 8

    在业务开发中我们经常会有滚动吸顶的效果,目前Flutter也有很多种实现方式,这里介绍一下本人在开发中使用到的基于NestedScrollView实现的滚动吸顶组件;以及中间涉及的各种定位的布局操作;NestedScrollView首先了解一下NestedScrollView滚动...

  • 6

    V2EX  ›  路由器 两室一厅标户,吸顶 ap 效果如何?  

  • 5

    iPhone兼容性修复:吸顶效果的Tabs标签页组件的完美自定义 作者:王晶 2023-10-11 08:14:43 在这篇文章中,我将与大家分享我的实际开发经验,重点关注如何自定义实现吸顶效果的Tabs标签页组件。通过本文,您将了解...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK