iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >一文详解前端进阶之IntersectionObserver
  • 209
分享到

一文详解前端进阶之IntersectionObserver

前端IntersectionObserver前端进阶 2023-05-16 20:05:04 209人浏览 安东尼
摘要

目录背景介绍IntersectionObserver api介绍IntersectionObserver API与iframe延迟加载与无限滚动利弊介绍背景介绍

背景介绍

作为一款产品,往往希望能得到用户的反馈,从而通过对用户行为的分析进行功能、交互等方面的改进。然而直接的一对一的用户交流是低效且困难的,因此最普遍的做法便是通过数据埋点来反推用户的行为。那么数据埋点中很重要的一环便是:曝光。

所谓曝光,便是页面被展示的时候进行打点。举个简单的例子:用户进入分类页面,商品以行为单位从上而下进行排列。当用户滚动页面时,之前不在视窗范围内的商品就会出现,此时,这部分商品就算曝光了,需要进行一次记录。
那么为了实现上面功能,最普遍的做法有两个。其一:监听滚动事件,然后计算某个商品与视窗的相对位置,从而判断是否可见。其二:设置一个定时器,然后以固定的时间为间隔计算某个商品与视窗的相对位置。

上面两种做法在某种程度上能够实现我们的目的,但是会有一些问题,比如最明显的:慢。因为计算相对位置时会调用getBoundinGClientRect(),这个api会导致浏览器进行全页面的重新布局,影响性能,特别是在频繁进行时。因此IntersectionObserver API进入了我们的视野。

IntersectionObserver API介绍

关于IntersectionObserver API的官方文档见此。兼容性如下图所示:

简单的说IntersectionObserver让你知道什么时候observe的元素进入或者存在在root区域里了。下面我们来看下这个API的具体内容:

// 用构造函数生成观察者实例,回调函数是必须的,后面的配置对象是可选的 
const observer = new IntersectionObserver(changes => {   
    for (const change of changes) {     
        console.log(change.time);               // 相交发生时经过的时间
        console.log(change.rootBounds);         // 表示发生相交时根元素可见区域的矩形信息,是一个对象值
        console.log(change.boundingClientRect); // target.boundingClientRect()发生相交时目标元素的矩形信息,也是个对象值
        console.log(change.intersectionRect);   // 根元素与目标元素相交时的矩形信息     
        console.log(change.intersectionRatio);  // 表示相交区域占目标区域的百分比,是一个0到1的值  
        console.log(change.target);             // 相交发生时的目标元素   
       }
  }, { 	
  root: null,   	
  threshold: [0, 0.5, 1],   	
  rootMargin: "50px" 
 });  
 // 实例属性 
 observer.root  
 observer.rootMargin  
 observer.thresholds  
 // 实例方法 
 observer.observe(target); // 观察针对某个特定元素的相交事件  
 observer.unobserve(target); // 停止对某个特定元素的相交事件的观察  
 observer.disconnect(); // 停止对所有目标元素的阈值事件的观察,简单的说就是停用整个IntersectionObserver 
 // 除了上面三个实例方法,还有一个takeRecords()的方法,之后会详细介绍 

IntersectionObserver API允许开发人员了解目标dom元素相对于intersection root的可见性。这个root可以通过实例属性获取。默认情况下它是null,此时它不是真正意义上的元素,它指视窗范围,因此只要视窗范围内的目标元素滚入视窗时,就会触发回调函数(如果root元素不存在了,则执行其任何的observe都会出错)。

我们可以在配置对象中将root改为具体的元素,此时当目标元素出现在root元素中时会触发回调,注意,在这种情况下相交可能发生在视窗下面。具体代码在下,感兴趣的同学可以试一下:

<!DOCTYPE html> 
<html lang="en"> 	
    <head> 		
        <meta charset="utf-8" /> 		
        <title>intersectionObserve</title> 		
        <style type="text/CSS"> 			
            #root { 				
                position: relative; 				
                width: 400px; 				
                height: calc(100vh + 200px); 				
                background: lightblue; 				
                overflow: scroll; 			
            } 			
            #target { 			   
                position: absolute; 			   
                top: calc(100vh + 800px); 			   
                width: 100px; 			   
                height: 100px; 			   
                background: red; 			
            }  		
        </style> 	
    </head> 	
    <body> 		
        <div id="root"> 			
            <div id="target"></div> 		
        </div> 		
        <script type="text/javascript"> 			
            let ele = new IntersectionObserver( 				
                (entries) => { 			  		
                    console.log(entries); 			  
                }, { 			  	
                    root: root 			  
                } 			
            );  			
            ele.observe(target); 		
        </script> 	
    </body> 
</html> 

在上面的例子中,回调函数打印出来的对象中有一个intersectionRatio值,这个值其实涉及到了整个API的核心功能:当目标元素和根元素相交的面积占目标元素面积的百分比到达或跨过某些指定的临界值时就会触发回调函数。因此相对的在配置对象里有一个threshold来对这个百分比进行配置,默认情况下这个值是[0],注意里面的值不能在0-1之外,否则会报错。我们举个例子如下:

let ele = new IntersectionObserver( 	
    (entries) => {   		
        console.log(entries);   
    }, {   	
        threshold: [0, 0.5, 1.0]   
    } 
);  
ele.observe(target); 

在上面这个例子中,我们设定了0,0.5,1.0这三个值,因此当交叉区域跨越0,0.5,1.0时都会触发回调函数。注意我这边的用词是跨越,而不是到达。因为会存在以下两种情况导致回调打印出来的intersectionRatio不为0,0.5和1.0。

一、浏览器对相交的检测是有时间间隔的。浏览器的渲染工作都是以帧为单位的,而IntersectionObserver是发生在帧里面的。因此假如你设定了[0,0.1,0.2,0.3,0.4,0.5]这个threshold,但是你的滚动过程特别快,导致所有的绘制在一帧里面结束了,此时回调只会挑最近的临界值触发一次。

二、 IntersectionObserver是异步的。在浏览器内部,当一个观察者实例观察到众多的相交行为时,它不会立即执行。关于IntersectionObserver的草案里面写明了其实现是基于requestIdleCallback()来异步的执行我们的回调函数的,并且规定了最大的延迟时间是100ms。关于这部分涉及到前面第一段代码里的一个实例方法takeRecords()。如果你很迫切的希望马上知道是否有相交,你不希望等待可能的100ms,此时你就能调用takeRecords(),此后你能马上获得包含IntersectionObserverEntry 对象的数组,里面有相交信息,如果没有任何相交行为发生,则返回一个空数组。但这个方法与正常的异步回调是互斥的,如果它先执行了则正常回调里面就没信息了,反之亦然。

除开上面的问题,如果目标元素的面积为0会产生什么情况呢?因为与0计算相交率是没有意义的,实际我们举个例子:

<!DOCTYPE html> 
<html lang="en"> 	
    <head> 		
        <meta charset="utf-8" /> 		
        <title>intersectionObserve</title> 		
        <style type="text/css"> 			
            #target { 			   
                position: relative; 			   
                top: calc(100vh + 500px); 			   
                width: 100px; 			   
                height: 100px; 			   
                background: red; 			
            } 		
        </style> 	
    </head> 	
    <body> 		
        <div id="target"></div> 		
        <div id="img"></div> 		
        <script type="text/javascript"> 			
            let ele = new IntersectionObserver( 				
                (entries) => { 			  		
                    console.log(entries); 			  
                }, { 			  	
                    threshold: [0, 0.5, 1.0] 			  
                } 			
            );  			
            ele.observe(img); 		
        </script> 	
    </body> 
</html> 

我们会看到,虽然我们设定了0.5这个阈值,但实际回调只会在0与1.0时触发。这是一种特殊的处理方式。

这里需要强调一点的是,我们的目标元素在Observe的时候可以不存在的(注意这里的不存在是指没有插入dom结构,但是元素本身是需要存在的),只需要在相交发生时存在就行了,我们来举个栗子:

<!DOCTYPE html> 
<html lang="en"> 	
    <head> 		
        <meta charset="utf-8" /> 		
        <title>intersectionObserve</title> 		
        <style type="text/css"> 			
            #target { 			   
                position: relative; 			   
                top: calc(100vh + 500px); 			   
                width: 100px; 			   
                height: 100px; 			   
                background: red; 			
            } 		
        </style> 	
    </head> 	
    <body> 		
        <div id="target"></div> 		
        <script type="text/javascript"> 			
            let ele = new IntersectionObserver( 				
                (entries) => { 			  		
                    console.log(entries); 			  
                   }, { 			  	
                        threshold: [0, 0.5, 1.0] 			  
                    } 			
                );  			
            let img = document.createElement('div'); 			
            ele.observe(img); 			
            setTimeout(() => { 				
                document.body.appendChild(img); 			
            }, 5000); 		
        </script> 	
    </body> 
</html> 

同理,如果目标元素与根元素处于相交状态,但是在一段时间后目标元素不存在了(比如remove,或者display:none)了,那么此时依然会触发一次回调。但是如果本身就不处于相交状态,然后消失掉了,因为0->0没有变化,所以不会触发回调,具体如下面的例子所示:

<!DOCTYPE html> 
<html lang="en"> 	
    <head> 		
        <meta charset="utf-8" /> 		
        <title>intersectionObserve</title> 		
        <style type="text/css"> 			
            #target { 			   
                position: relative; 			   
                top: calc(100vh + 500px); 			   
                width: 100px; 			   
                height: 100px; 			   
                background: red; 			
            } 		
        </style> 	
    </head> 	
    <body> 		
        <div id="target"></div> 		
        <script type="text/javascript"> 			
        let ele = new IntersectionObserver( 				
            (entries) => { 			  		
                console.log(entries); 			  
            } 			
        );  			
        ele.observe(target); 			
        setTimeout(() => { 				
            document.body.removeChild(target); 			
        }, 5000); 		
        </script> 	
    </body> 
</html> 

IntersectionObserver API与iframe

互联网上的很多小广告都是通过iframe嵌入的,然而现有的情况下很难获取iframe在顶层视窗内的曝光,但是使用IntersectionObserver API我们却可以做到这点。下面举个例子:

<!DOCTYPE html> 
<html lang="en"> 	
    <head> 		
        <meta charset="utf-8" /> 		
        <title>intersectionObserve</title> 		
        <style type="text/css"> 			
            #root { 			   
                position: relative; 			   
                top: calc(100vh + 800px); 			   
                width: 100px; 			   
                height: 100px; 			
            } 			
            #iframe { 				
                width: 600px; 				
                height: 600px; 				
                margin-bottom: 300px; 			
            } 		
        </style> 	
    </head> 	
    <body> 		
        <div id="root">   			
            <iframe id="iframe"></iframe> 		
        </div> 		
        <script> 		  
            let iframeTemplate = ` 		    
                <div id="target"><p>i am iframe</p></div> 		    
                <style> 		      
                    #target { 		        
                    width: 500px; 		        
                    height: 500px; 		        
                    background: red; 		      
                    } 		      
                    #target p { 		      	
                        font-size: 90px; 		      
                    } 		    
                </style> 		    
                <script> 		      
                    let observer = new IntersectionObserver((entries) => { 		        
                        console.log(entries) 		      
                        }, { 		      	
                        threshold: [0,0.5,1.0] 		      
                    }) 		      
                    observer.observe(target) 		    
                </script>`  		  
            iframe.src = URL.createObjectURL(new Blob([iframeTemplate], {"type": "text/html"})) 		
        </script> 	
    </body> 
</html> 

从上面的例子可以看出,使用此API不仅能够使iframe在视窗内出现时触发回调,而且threshold值同样能够起作用。这样一来,大大简化了此类情况下获取曝光的难度。

延迟加载与无限滚动

上面我们关于配置参数已经提到了root和threshold,实际上还有一个值:rootMargin。这个值实际就是给根元素添加了一个假想的margin值。使用场景最普遍的是用于延迟加载。因为如果真的等目标元素与根元素相交的时候再进行加载图片等功能就已经晚了,所以有一个rootMargin值,这样等于根元素延伸开去了,目标元素只要与延伸部分相交就会触发回调,下面我们来继续举个例子:

<!DOCTYPE html> 
<html lang="en"> 	
    <head> 		
        <meta charset="utf-8" /> 		
        <title>intersectionObserve</title> 		
        <style type="text/css"> 			
            #root { 				
                width: 500px; 				
                height: 800px; 				
                overflow: scroll; 				
                background-color: pink; 			
            } 			
            #target { 			   
                position: relative; 			   
                top: calc(100vh + 500px); 			   
                width: 100px; 			   
                height: 100px; 			   
                background: red; 			
            } 		
        </style> 	
    </head> 	
    <body> 		
        <div id="root"> 			
            <div id="target"></div> 		
        </div> 		
        <script type="text/javascript"> 			
            let ele = new IntersectionObserver( 				
                (entries) => { 			  		
                    console.log(entries); 			  
                }, { 			  	
                    rootMargin: '100px', 			  	
                    root: root 			  
                } 			
            );  			
            ele.observe(target); 		
        </script> 	
    </body> 
</html> 

在上面的例子中,目标元素并没有出现在根元素的视窗里的时候就已经触发回调了。

整个API可以用来实现无限滚动和延迟加载,下面就分别举出两个简单的例子来启发思路。
延迟加载的例子:

<!DOCTYPE html> <html lang="en"> 	
    <head> 		
        <meta charset="utf-8" /> 		
        <title>intersectionObserve</title> 		
        <style type="text/css"> 			
            .img { 				
                height: 1000px; 				
                overflow-y: hidden; 			
            } 		
        </style> 	
    </head> 	
    <body> 		
        <ul> 			
            <li class="img"> 				
                <img src="" class="img-item" data-src="Http://okzzg7ifm.bkt.clouddn.com/cat.png"/> 		
            </li> 			
            <li class="img"> 				
                <img src="" class="img-item" data-src="http://okzzg7ifm.bkt.clouddn.com/01.png"/> 			
            </li> 			
            <li class="img"> 				
                <img src="" class="img-item" data-src="http://okzzg7ifm.bkt.clouddn.com/virtualdom.png"/> 			
            </li> 			
            <li class="img"> 				
                <img src="" class="img-item" data-src="http://okzzg7ifm.bkt.clouddn.com/Reactlife.png"/> 			
            </li> 		
        </ul> 		
        <script type="text/javascript"> 			
            let ele = new IntersectionObserver( 				
                (entries) => { 			  		
                    entries.forEach((entry) => { 			  			
                    if (entry.intersectionRatio > 0) { 			  				
                        entry.target.src = entry.target.dataset.src; 			  			
                    } 			  		
                    }) 			  
                }, { 			  	
                rootMargin: '100px', 			  	
                threshold: [0.000001] 			  
                } 			
            ); 			
            let eleArray = Array.from(document.getElementsByClassName('img-item')); 			
            eleArray.forEach((item) => { 				
                ele.observe(item); 			
            }) 		
        </script> 	
    </body> 
</html> 

无限滚动的例子:

<!DOCTYPE html> 
<html lang="en"> 	
    <head> 		
        <meta charset="utf-8" /> 		
        <title>intersectionObserve</title> 		
        <style type="text/css"> 			
            .img { 				
                height: 1200px; 				
                overflow: hidden; 			
            } 			
            #flag { 				
                height: 20px; 				
                background-color: pink; 			
            } 		
        </style> 	
    </head> 	
    <body> 		
        <ul id="imgContainer"> 			
            <li class="img"> 				
                <img src="http://okzzg7ifm.bkt.clouddn.com/cat.png"/> 			
            </li> 			
            <li class="img"> 				
                <img src="http://okzzg7ifm.bkt.clouddn.com/01.png"/> 			
            </li> 			
            <li class="img"> 				
                <img src="http://okzzg7ifm.bkt.clouddn.com/virtualdom.png"/> 			
            </li> 			
            <li class="img"> 				
                <img src="http://okzzg7ifm.bkt.clouddn.com/reactlife.png"/> 			
            </li> 		
        </ul> 		
        <div id="flag"></div> 		
        <script type="text/javascript"> 			
            let imgList = [ 				
                'http://okzzg7ifm.bkt.clouddn.com/immutable-coperation.png', 'http://okzzg7ifm.bkt.clouddn.com/flexdirection.png', 				'http://okzzg7ifm.bkt.clouddn.com/immutable-exampleLayout.png' 			
            ] 			
            let ele = new IntersectionObserver( 				
                (entries) => { 					
                    if (entries[0].intersectionRatio > 0) { 						
                        if (imgList.length) { 							
                            let newImgli = document.createElement('li'); 							
                            newImgli.setAttribute("class", "img"); 							
                            let newImg = document.createElement('img'); 							
                            newImg.setAttribute("src", imgList[0]); 							
                            newImgli.appendChild(newImg); 							
                            document.getElementById('imgContainer').appendChild(newImgli); 							
                            imgList.shift(); 						
                        } 					
                    }  			  
                }, { 			  	
                    rootMargin: '100px', 			  	
                    threshold: [0.000001] 			  
                } 			
            ); 			
            ele.observe(flag); 		
        </script> 	
    </body> 
</html> 

通篇看下来大家是不是感觉这个API还是很好玩的,api已经问世很多年了,大部分浏览器都可以兼容,低版本浏览器可以通过Polyfill解决,规范制订者在GitHub上发布了Polyfill。

利弊介绍

  • 优点

    • 性能比直接的监听scroll事件或者设置timer都好
    • 使用简单
    • 利用它的功能组合可以实现很多其他效果,比如无限滚动等
    • 对iframe的支持好
  • 缺点

    • 它不是完美像素与无延迟的,毕竟根本上是异步的。因此不适合做滚动动画

以上就是一文详解前端进阶之IntersectionObserver的详细内容,更多关于前端IntersectionObserver的资料请关注编程网其它相关文章!

--结束END--

本文标题: 一文详解前端进阶之IntersectionObserver

本文链接: https://www.lsjlt.com/news/210545.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 一文详解前端进阶之IntersectionObserver
    目录背景介绍IntersectionObserver API介绍IntersectionObserver API与iframe延迟加载与无限滚动利弊介绍背景介绍 ...
    99+
    2023-05-16
    前端IntersectionObserver 前端进阶
  • JavaScript进阶之前端文件上传和下载示例详解
    目录文件下载1.通过a标签点击直接下载2.open或location.href3.Blob和Base64文件上传文件上传思路File文件上传单个文件-客户端上传文件-服务端多文件上传...
    99+
    2024-04-02
  • 常用前端手写功能进阶示例详解
    目录1、Promise.all2、Promise.race3、Promise.any4、冒泡排序5、选择排序6、快速排序7、call8、apply9、bind10、instanceo...
    99+
    2024-04-02
  • C语言进阶之文件操作详解
    目录0. Intro1. 文件名2. 文件的打开和关闭2.1 文件指针2.2 打开和关闭文件3. 文件顺序读写3.1 利用以上函数实现拷贝文件操作3.2 二进制的读写3.3 格式化输...
    99+
    2024-04-02
  • Python进阶之协程详解
    目录协程协程的应用场景抢占式调度的缺点用户态协同调度的优势协程的运行原理Python 中的协程总结协程 协程(co-routine,又称微线程)是一种多方协同的工作方式。当前执行者在...
    99+
    2024-04-02
  • javascript之函数进阶详解
    目录函数定义方式函数的调用(6种)this指向问题严格模式高阶函数闭包递归:函数里面调用自己,需要有结束条件函数定义方式 function fn(){}//命名函数 var ...
    99+
    2024-04-02
  • 前端进阶之教你利用javascript存储函数
    目录前言背景介绍实现方案思考js存储函数方案设计最后总结前言 任何一家Saas企业都需要有自己的低代码平台.在可视化低代码的前端研发过程中, 发现了很多有意思的技术需求, 在解决这些...
    99+
    2024-04-02
  • Web前端之iframe详解
    iframe是HTML中的一个标签,用于在页面中嵌入另一个网页或者其他类型的文档。它可以在一个页面中显示另一个页面的内容,类似于在一...
    99+
    2023-09-23
    Web前端
  • Java进阶之SPI机制详解
    目录一、前言二、SPI规范三、SPI应用案例3.1 数据库驱动3.2 Slf4j四、SPI示例4.1 spi-operate-service模块4.2 spi-operat...
    99+
    2024-04-02
  • 前端自动化测试之Jest 进阶教程示例
    目录Jest Mockmock 异步方法Mock TimersMock 类Snapshot 快照测试DOM 测试Jest Mock mock 异步方法 export const r...
    99+
    2023-02-14
    Jest 前端自动化测试 Jest 自动化测试进阶
  • python进阶之魔术方法详解
    目录一、三个内置函数二、双下划线开头和结尾的方法,叫魔术方法。总结一、三个内置函数 1、@classmethod–类名.属性名 2、@staticmethod&ndash...
    99+
    2024-04-02
  • 一文详解如何在前端使用JS进行分类汇总
    目录前言提出问题原数据(按部门再按业务)的轻维表呈现按业务再按部门分组的轻维表呈现按业务按年统计的轻维表呈现展平多级数据晋级:如果想用递归该怎么处理?分类及分类汇总按业务再按部门分组...
    99+
    2023-05-14
    js 分类 javascript分类 js分类汇总
  • Vue之前端体系与前后端分离详解
    目录概述前端知识体系前端三要素表现层(CSS)行为层(JavaScript)JavaScript 框架 UI框架JavaScript 构建工具三端统一混合开发(Hybrid...
    99+
    2024-04-02
  • pytest进阶教程之fixture函数详解
    fixture函数存在意义   与python自带的unitest测试框架中的setup、teardown类似,pytest提供了fixture函数用以在测试执行前和执行后进行必要...
    99+
    2024-04-02
  • 前端小白进阶之路-技巧篇(垂直水平居中)
    在前端布局中居中方式可以说是家常便饭,几乎所有地方都需要用到他,我们常见的就是水平居中和垂直居中。今天小编带大家就看看常用到的这些居中方式都有哪些实现方式。水平居中:水平居中就是为了让子元素在父元素中排列在水平中心位置,实现方式很多,我们看...
    99+
    2023-06-03
  • Python基础之面向对象进阶详解
    目录面向对象三大特征介绍继承语法格式查看类的继承层次结构object根类dir()查看对象属性str()方法的重写多重继承MRO()super()获得父类定义多态特殊方法和重载运算符...
    99+
    2024-04-02
  • Docker 进阶之镜像分层方案详解
    目录导读入门图解创建测试镜像查看镜像使用docker inspect使用docker history镜像分层图镜像分层的好处Docker镜像加载原理rootfsUnion ...
    99+
    2024-04-02
  • Java进阶之高并发核心Selector详解
    目录一、Selector设计二、获取Selector三、EPollSelector如何进行select3.1 Epoll fd的创建3.2 Epoll wait等待内核IO...
    99+
    2024-04-02
  • Python进阶语法之三元表达式详解
    Python进阶语法之三元表达式详解 Python的三元表达式(Ternary Expressions)是一种简洁高效的编写条件逻辑的方式。与许多其他编程语言一样,Python也提供了三元表达式,可以...
    99+
    2023-10-08
    python 开发语言
  • C++学习进阶之Makefile基础用法详解
    目录1. Makefile基本语法与执行2. Makefile简化过程3. Makefile生成并使用库3.1 动态库的建立与使用3.2 动态加载库的建立与使用总结1. Makefi...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作