溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

IntersectionObserver怎么使用

發(fā)布時間:2023-04-20 15:47:40 來源:億速云 閱讀:83 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要講解了“IntersectionObserver怎么使用”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“IntersectionObserver怎么使用”吧!

背景介紹

作為一款產(chǎn)品,往往希望能得到用戶的反饋,從而通過對用戶行為的分析進行功能、交互等方面的改進。然而直接的一對一的用戶交流是低效且困難的,因此最普遍的做法便是通過數(shù)據(jù)埋點來反推用戶的行為。那么數(shù)據(jù)埋點中很重要的一環(huán)便是:曝光。

所謂曝光,便是頁面被展示的時候進行打點。舉個簡單的例子:用戶進入分類頁面,商品以行為單位從上而下進行排列。當(dāng)用戶滾動頁面時,之前不在視窗范圍內(nèi)的商品就會出現(xiàn),此時,這部分商品就算曝光了,需要進行一次記錄。
那么為了實現(xiàn)上面功能,最普遍的做法有兩個。其一:監(jiān)聽滾動事件,然后計算某個商品與視窗的相對位置,從而判斷是否可見。其二:設(shè)置一個定時器,然后以固定的時間為間隔計算某個商品與視窗的相對位置。

上面兩種做法在某種程度上能夠?qū)崿F(xiàn)我們的目的,但是會有一些問題,比如最明顯的:慢。因為計算相對位置時會調(diào)用getBoundingClientRect(),這個api會導(dǎo)致瀏覽器進行全頁面的重新布局,影響性能,特別是在頻繁進行時。因此IntersectionObserver API進入了我們的視野。

IntersectionObserver API介紹

關(guān)于IntersectionObserver API的官方文檔見此。兼容性如下圖所示:

IntersectionObserver怎么使用

簡單的說IntersectionObserver讓你知道什么時候observe的元素進入或者存在在root區(qū)域里了。下面我們來看下這個API的具體內(nèi)容:

// 用構(gòu)造函數(shù)生成觀察者實例,回調(diào)函數(shù)是必須的,后面的配置對象是可選的 
const observer = new IntersectionObserver(changes => {   
    for (const change of changes) {     
        console.log(change.time);               // 相交發(fā)生時經(jīng)過的時間
        console.log(change.rootBounds);         // 表示發(fā)生相交時根元素可見區(qū)域的矩形信息,是一個對象值
        console.log(change.boundingClientRect); // target.boundingClientRect()發(fā)生相交時目標(biāo)元素的矩形信息,也是個對象值
        console.log(change.intersectionRect);   // 根元素與目標(biāo)元素相交時的矩形信息     
        console.log(change.intersectionRatio);  // 表示相交區(qū)域占目標(biāo)區(qū)域的百分比,是一個0到1的值  
        console.log(change.target);             // 相交發(fā)生時的目標(biāo)元素   
       }
  }, { 	
  root: null,   	
  threshold: [0, 0.5, 1],   	
  rootMargin: "50px" 
 });  
 // 實例屬性 
 observer.root  
 observer.rootMargin  
 observer.thresholds  
 // 實例方法 
 observer.observe(target); // 觀察針對某個特定元素的相交事件  
 observer.unobserve(target); // 停止對某個特定元素的相交事件的觀察  
 observer.disconnect(); // 停止對所有目標(biāo)元素的閾值事件的觀察,簡單的說就是停用整個IntersectionObserver 
 // 除了上面三個實例方法,還有一個takeRecords()的方法,之后會詳細(xì)介紹

IntersectionObserver API允許開發(fā)人員了解目標(biāo)dom元素相對于intersection root的可見性。這個root可以通過實例屬性獲取。默認(rèn)情況下它是null,此時它不是真正意義上的元素,它指視窗范圍,因此只要視窗范圍內(nèi)的目標(biāo)元素滾入視窗時,就會觸發(fā)回調(diào)函數(shù)(如果root元素不存在了,則執(zhí)行其任何的observe都會出錯)。

我們可以在配置對象中將root改為具體的元素,此時當(dāng)目標(biāo)元素出現(xiàn)在root元素中時會觸發(fā)回調(diào),注意,在這種情況下相交可能發(fā)生在視窗下面。具體代碼在下,感興趣的同學(xué)可以試一下:

<!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>

在上面的例子中,回調(diào)函數(shù)打印出來的對象中有一個intersectionRatio值,這個值其實涉及到了整個API的核心功能:當(dāng)目標(biāo)元素和根元素相交的面積占目標(biāo)元素面積的百分比到達或跨過某些指定的臨界值時就會觸發(fā)回調(diào)函數(shù)。因此相對的在配置對象里有一個threshold來對這個百分比進行配置,默認(rèn)情況下這個值是[0],注意里面的值不能在0-1之外,否則會報錯。我們舉個例子如下:

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

在上面這個例子中,我們設(shè)定了0,0.5,1.0這三個值,因此當(dāng)交叉區(qū)域跨越0,0.5,1.0時都會觸發(fā)回調(diào)函數(shù)。注意我這邊的用詞是跨越,而不是到達。因為會存在以下兩種情況導(dǎo)致回調(diào)打印出來的intersectionRatio不為0,0.5和1.0。

一、瀏覽器對相交的檢測是有時間間隔的。瀏覽器的渲染工作都是以幀為單位的,而IntersectionObserver是發(fā)生在幀里面的。因此假如你設(shè)定了[0,0.1,0.2,0.3,0.4,0.5]這個threshold,但是你的滾動過程特別快,導(dǎo)致所有的繪制在一幀里面結(jié)束了,此時回調(diào)只會挑最近的臨界值觸發(fā)一次。

二、 IntersectionObserver是異步的。在瀏覽器內(nèi)部,當(dāng)一個觀察者實例觀察到眾多的相交行為時,它不會立即執(zhí)行。關(guān)于IntersectionObserver的草案里面寫明了其實現(xiàn)是基于requestIdleCallback()來異步的執(zhí)行我們的回調(diào)函數(shù)的,并且規(guī)定了最大的延遲時間是100ms。關(guān)于這部分涉及到前面第一段代碼里的一個實例方法takeRecords()。如果你很迫切的希望馬上知道是否有相交,你不希望等待可能的100ms,此時你就能調(diào)用takeRecords(),此后你能馬上獲得包含IntersectionObserverEntry 對象的數(shù)組,里面有相交信息,如果沒有任何相交行為發(fā)生,則返回一個空數(shù)組。但這個方法與正常的異步回調(diào)是互斥的,如果它先執(zhí)行了則正?;卣{(diào)里面就沒信息了,反之亦然。

除開上面的問題,如果目標(biāo)元素的面積為0會產(chǎn)生什么情況呢?因為與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>

我們會看到,雖然我們設(shè)定了0.5這個閾值,但實際回調(diào)只會在0與1.0時觸發(fā)。這是一種特殊的處理方式。

這里需要強調(diào)一點的是,我們的目標(biāo)元素在Observe的時候可以不存在的(注意這里的不存在是指沒有插入dom結(jié)構(gòu),但是元素本身是需要存在的),只需要在相交發(fā)生時存在就行了,我們來舉個栗子:

<!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>

同理,如果目標(biāo)元素與根元素處于相交狀態(tài),但是在一段時間后目標(biāo)元素不存在了(比如remove,或者display:none)了,那么此時依然會觸發(fā)一次回調(diào)。但是如果本身就不處于相交狀態(tài),然后消失掉了,因為0->0沒有變化,所以不會觸發(fā)回調(diào),具體如下面的例子所示:

<!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

互聯(lián)網(wǎng)上的很多小廣告都是通過iframe嵌入的,然而現(xiàn)有的情況下很難獲取iframe在頂層視窗內(nèi)的曝光,但是使用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在視窗內(nèi)出現(xiàn)時觸發(fā)回調(diào),而且threshold值同樣能夠起作用。這樣一來,大大簡化了此類情況下獲取曝光的難度。

延遲加載與無限滾動

上面我們關(guān)于配置參數(shù)已經(jīng)提到了root和threshold,實際上還有一個值:rootMargin。這個值實際就是給根元素添加了一個假想的margin值。使用場景最普遍的是用于延遲加載。因為如果真的等目標(biāo)元素與根元素相交的時候再進行加載圖片等功能就已經(jīng)晚了,所以有一個rootMargin值,這樣等于根元素延伸開去了,目標(biāo)元素只要與延伸部分相交就會觸發(fā)回調(diào),下面我們來繼續(xù)舉個例子:

<!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>

在上面的例子中,目標(biāo)元素并沒有出現(xiàn)在根元素的視窗里的時候就已經(jīng)觸發(fā)回調(diào)了。

整個API可以用來實現(xiàn)無限滾動和延遲加載,下面就分別舉出兩個簡單的例子來啟發(fā)思路。
延遲加載的例子:

<!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已經(jīng)問世很多年了,大部分瀏覽器都可以兼容,低版本瀏覽器可以通過Polyfill解決,規(guī)范制訂者在github上發(fā)布了Polyfill。

利弊介紹

  • 優(yōu)點

    • 性能比直接的監(jiān)聽scroll事件或者設(shè)置timer都好

    • 使用簡單

    • 利用它的功能組合可以實現(xiàn)很多其他效果,比如無限滾動等

    • 對iframe的支持好

  • 缺點

    • 它不是完美像素與無延遲的,畢竟根本上是異步的。因此不適合做滾動動畫

感謝各位的閱讀,以上就是“IntersectionObserver怎么使用”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對IntersectionObserver怎么使用這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI