IntersectionObserver API


背景

在網頁開發的過程中,我們常常需要判斷某個元素是否進入了"視口"(viewport),即用戶能不能看到它。

一般采用這樣的方法實現,兼容scroll事件,然后調用方法獲取目標元素的坐標,判斷是否在視口之內。代碼不僅繁瑣,而且由於scroll事件密集發生,計算量很大一不小心沒有函數去抖就又可能導致嚴重的性能問題。

IntersectionObserver

現在我們有了更好的選擇—— IntersectionObserver API ,IntersectionObserver 允許你配置一個回調函數,每當 target ,元素和設備視口或者其他指定元素發生交集的時候該回調函數將會被執行。這個 API 的設計是異步的,而且保證你的回調執行次數是非常有限的,而且回調是會在主線程空閑時才執行,在性能方面表現更優,使用起來也更簡單。那么IntersectionObserver是如何實現的呢?

這里我們翻譯一下Intersection就大概能明白其實現原理了。intersection 是交集的意思

 

接口的原理就是通過觀察兩個元素之間是否是否有交集,然后對其進行監控操作。現在用一個簡單的例子展示

<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="apple-mobile-web-app-capable" content="yes" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
        <title></title>
         <style>
          #content {
            width: 100vw;
            height: 100vh;
          }

          #info{
            position: fixed;
            top: 0;
          }

          #target {
            width: 100px;
            height: 100px;
            background: red;
          }
        </style>
        
    </head>
    <body>
        <div id="info">我藏在頁面底部,請向下滾動</div>
        <div id="content"></div>
        <div id="target"></div>      
    </body>
    <!--  -->
<script type="text/javascript">       
    let observer = new IntersectionObserver(function(entries){
        entries.forEach( function(element, index) {
            console.log(observer.takeRecords());
            if (element.isIntersecting ) {
              info.textContent = "我出來了";
            } else {
              info.textContent = "我藏在頁面底部,請向下滾動"
            }
        });
    
    }, {
    root: null,
    threshold:[0, 1] 
    })

    observer.observe(document.querySelectorAll('#target')[0])
</script>

</html>

API的兼容情況

以下是api對於現代瀏覽器的支持情況

 

低版本瀏覽器可以通過 polyfill 兼容(https://github.com/w3c/IntersectionObserver/tree/master/polyfill)

API

構造函數

new IntersectionObserver(callback, options)

callback 是個必選參數,當有相交發生時,瀏覽器便會調用它,后面會詳細介紹;options 整個參數對象以及它的三個屬性都是可選的:

構造函數的返回值是一個觀察器實例,提供了以下方法

io = new IntersectionObserver(callback, options);
io.observe(document.getElementById('example')); //開始觀察
io.unobserve(element);  //停止觀察
io.disconnect();   //關閉觀察器
io.takeRecords(); //為所有監聽目標返回一個IntersectionObserverEntry對象數組並且停止監聽這些目標。

Callback

回調函數共有兩個參數,第二個參數就是觀察者實例本身,一般沒用,因為實例通常我們已經賦值給一個變量了,而且回調函數里的 this 也是那個實例。第一個參數是個包含有若干個 IntersectionObserverEntry 對象的數組,每個 IntersectionObserverEntry 對象都代表一次相交,它的屬性們就包含了那次相交的各種信息。

{
    time: 3893.92,
    rootBounds: ClientRect {
    bottom: 920,
    height: 1024,
    left: 0,
    right: 1024,
    top: 0,
    width: 920
    },
    boundingClientRect: ClientRect {
     // ...
    },
    intersectionRect: ClientRect {
    // ...
    },
    intersectionRatio: 0.54,
    target: element,
    isIntersecting: true
}

IntersectionObserverEntry


Options
屬性 

root

所監聽對象的具體祖先元素。如果未傳入任何值或值為null,則默認使用viewport。

<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="apple-mobile-web-app-capable" content="yes" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
        <title></title>
         <style>
          #root {
            position: relative;
            width: 200px;
            height: 100vh;
            margin: 0 auto;
            overflow: scroll;
            border: 1px solid #ccc;
          }
          
          #info {
            position: fixed;
          }
          
          #target {
            position: absolute;
            top: calc(100vh + 1px);
            width: 100px;
            height: 100px;
            background: red;
          }
        </style>
        
    </head>
    <body>
        <div id="root">
          <div id="info">向下滾動就能看到我</div>
          <div id="target"></div>
        </div>                  
    </body>
    <!--  -->
<script type="text/javascript">       
    let observer = new IntersectionObserver((entries) => {
    console.log(entries[0])
    if (entries[0].isIntersecting) {
      info.textContent = "我出來了"
    } else {
      info.textContent = "向下滾動就能看到我"
    }
  }, {
    root: root
  })

  observer.observe(target)
</script>

</html>

rootMargin

計算交叉時添加到根(root)邊界盒的矩形偏移量, 可以有效的縮小或擴大根的判定范圍從而滿足計算需要。此屬性返回的值可能與調用構造函數時指定的值不同,因此可能需要更改該值,以匹配內部要求。所有的偏移量均可用像素(pixel)(px)或百分比(percentage)(%)來表達, 默認值為"0px 0px 0px 0px",表達的位置與css margin屬性一直, 上邊距 右邊距 下邊距 左邊距 ,為正值時表示區域擴大,負值時表示區域縮小

<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="apple-mobile-web-app-capable" content="yes" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
        <title></title>
         <style>
          #content {
            width: 100vw;
            height: 100vh;
          }

          #info{
            position: fixed;
            top: 0;
          }

          #target {
            width: 100px;
            height: 100px;
            background: red;
          }
        </style>
        
    </head>
    <body>
        <div id="info">我藏在頁面底部,請向下滾動</div>
        <div id="content"></div>
        <div id="target"></div>      
    </body>
    <!--  -->
<script type="text/javascript">       
    let observer = new IntersectionObserver(function(entries){
        entries.forEach( function(element, index) {
            console.log(observer.takeRecords());
            if (element.isIntersecting ) {
              info.textContent = "我出來了";
            } else {
              info.textContent = "我藏在頁面底部,請向下滾動"
            }
        });
    
    }, {
    root: null,
    rootMargin : '0px 0px -20px 0px',
    threshold:[0, 1] 
    })

    observer.observe(document.querySelectorAll('#target')[0])
</script>
</html>

thresholds

一個包含閾值的list, 升序排列, list中的每個閾值都是監聽對象的交叉區域與邊界區域的比率。當監聽對象的任何閾值被越過時,都會生成一個通知(Notification)。如果構造器未傳入值, 則默認值為0.不僅當目標元素從視口外移動到視口內時會觸發回調,從視口內移動到視口外也會。

<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="apple-mobile-web-app-capable" content="yes" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
        <title></title>
         <style>
          #content {
            width: 100vw;
            height: 100vh;
          }

          #info{
            position: fixed;
            top: 0;
          }

          #target {
            width: 100px;
            height: 100px;
            background: red;
          }
        </style>
        
    </head>
    <body>
        <div id="info"></div>
        <div id="content"></div>
        <div id="target"></div>      
    </body>
    <!--  -->
<script type="text/javascript">       
    let observer = new IntersectionObserver(function(entries){
        entries.forEach( function(element, index) {
            info.textContent = '頁面相交:' + element.intersectionRatio;
       
        });
    
    }, {
    root: null,
    threshold:[0, 0.5,0.7,1] 
    })

    observer.observe(document.querySelectorAll('#target')[0])
</script>
</html>

實例,圖片懶加載(lazyload)

下面我們通過這個接口來實現內容的懶加載

<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="apple-mobile-web-app-capable" content="yes" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
        <title>使用IntersectionObserver實現懶加載</title>
    
        <style type="text/css">
            * {
                margin: 0;
                padding: 0;
            }
            ul,li {
                list-style: none;
            }
            .list {
                width: 800px;
                margin: 0 auto;
            }
            .list ul {
                width: 100%;
                overflow: hidden;
            }
            .list ul li {
                float: left;
                width: 185px;
                height: 400px;
                margin-bottom: 10px;
                margin-left: 10px;
                background-color: #ccc;
                overflow: hidden;
                text-align: center;
                line-height: 400px;
                color: red;
                font-size: 24px;
            }
        </style>
    </head>
    <body>

        <div class="list">
            <ul>
                <li class="lazy-loaded">
                    <template>
                        <img src="images/1.jpg" width="100%" height="100%" border="0"/>
                    </template>
                    <span class="loading">正在加載...</span>
                </li>
                <li class="lazy-loaded">
                    <template>
                        <img src="images/1.jpg" width="100%" height="100%" border="0"/>
                    </template>
                    <span class="loading">正在加載...</span>
                </li>
                <li class="lazy-loaded">
                    <template>
                        <img src="images/1.jpg" width="100%" height="100%" border="0"/>
                    </template>
                    <span class="loading">正在加載...</span>
                </li>
                <li class="lazy-loaded">
                    <template>
                        <img src="images/1.jpg" width="100%" height="100%" border="0"/>
                    </template>
                    <span class="loading">正在加載...</span>
                </li>
                <li class="lazy-loaded">
                    <template>
                        <img src="images/1.jpg" width="100%" height="100%" border="0"/>
                    </template>
                    <span class="loading">正在加載...</span>
                </li>
                <li class="lazy-loaded">
                    <template>
                        <img src="images/1.jpg" width="100%" height="100%" border="0"/>
                    </template>
                    <span class="loading">正在加載...</span>
                </li>
                <li class="lazy-loaded">
                    <template>
                        <img src="images/1.jpg" width="100%" height="100%" border="0"/>
                    </template>
                    <span class="loading">正在加載...</span>
                </li>
                <li class="lazy-loaded">
                    <template>
                        <img src="images/1.jpg" width="100%" height="100%" border="0"/>
                    </template>
                    <span class="loading">正在加載...</span>
                </li>
                <li class="lazy-loaded">
                    <template>
                        <img src="images/1.jpg" width="100%" height="100%" border="0"/>
                    </template>
                    <span class="loading">正在加載...</span>
                </li>
                <li class="lazy-loaded">
                    <template>
                        <img src="images/1.jpg" width="100%" height="100%" border="0"/>
                    </template>
                    <span class="loading">正在加載...</span>
                </li>
                <li class="lazy-loaded">
                    <template>
                        <img src="images/1.jpg" width="100%" height="100%" border="0"/>
                    </template>
                    <span class="loading">正在加載...</span>
                </li>
                <li class="lazy-loaded">
                    <template>
                        <img src="images/1.jpg" width="100%" height="100%" border="0"/>
                    </template>
                    <span class="loading">正在加載...</span>
                </li>
                <li class="lazy-loaded">
                    <template>
                        <img src="images/1.jpg" width="100%" height="100%" border="0"/>
                    </template>
                    <span class="loading">正在加載...</span>
                </li>
                <li class="lazy-loaded">
                    <template>
                        <img src="images/1.jpg" width="100%" height="100%" border="0"/>
                    </template>
                    <span class="loading">正在加載...</span>
                </li>
                <li class="lazy-loaded">
                    <template>
                        <img src="images/1.jpg" width="100%" height="100%" border="0"/>
                    </template>
                    <span class="loading">正在加載...</span>
                </li>
                <li class="lazy-loaded">
                    <template>
                        <img src="images/1.jpg" width="100%" height="100%" border="0"/>
                    </template>
                    <span class="loading">正在加載...</span>
                </li>
                <li class="lazy-loaded">
                    <template>
                        <img src="images/1.jpg" width="100%" height="100%" border="0"/>
                    </template>
                    <span class="loading">正在加載...</span>
                </li>
                <li class="lazy-loaded">
                    <template>
                        <img src="images/1.jpg" width="100%" height="100%" border="0"/>
                    </template>
                    <span class="loading">正在加載...</span>
                </li>
                <li class="lazy-loaded">
                    <template>
                        <img src="images/1.jpg" width="100%" height="100%" border="0"/>
                    </template>
                    <span class="loading">正在加載...</span>
                </li>
                <li class="lazy-loaded">
                    <template>
                        <img src="images/1.jpg" width="100%" height="100%" border="0"/>
                    </template>
                    <span class="loading">正在加載...</span>
                </li>
                <li class="lazy-loaded">
                    <template>
                        <img src="images/1.jpg" width="100%" height="100%" border="0"/>
                    </template>
                    <span class="loading">正在加載...</span>
                </li>
                <li class="lazy-loaded">
                    <template>
                        <img src="images/1.jpg" width="100%" height="100%" border="0"/>
                    </template>
                    <span class="loading">正在加載...</span>
                </li>
            </ul>
        </div>
        
    </body>
    <!--  -->
    <script type="text/javascript">
        //獲取dom
        function filterDom(selector) {
            //
            return Array.from(document.querySelectorAll(selector));
        }
        //事件觀察者
        var observer = new IntersectionObserver(observerCall);

        function observerCall(changes) {
            changes.forEach(function(change) {
                setTimeout(function(){
                    if(change.intersectionRatio > 0){
                        var container = change.target;
                        var content = container.querySelector('template').content;
                        container.appendChild(content);
                        container.querySelector('.loading').style.display = 'none';
                        observer.unobserve(container);
                    }
                    
                }, 100);
            });
        }
        //過濾元素
        filterDom('.lazy-loaded').forEach(function (item) {
            observer.observe(item);
        });
    </script>
</html>

通過這個接口來實現更多的功能,比如無限滾動,頁面對於廣告的展示等。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM