背景javascript
在網頁開發的過程當中,咱們經常須要判斷某個元素是否進入了"視口"(viewport),即用戶能不能看到它。css
通常採用這樣的方法實現,兼容scroll事件,而後調用方法獲取目標元素的座標,判斷是否在視口以內。代碼不只繁瑣,並且因爲scroll事件密集發生,計算量很大一不當心沒有函數去抖就又可能致使嚴重的性能問題。html
IntersectionObserverjava
如今咱們有了更好的選擇—— IntersectionObserver API ,IntersectionObserver 容許你配置一個回調函數,每當 target ,元素和設備視口或者其餘指定元素髮生交集的時候該回調函數將會被執行。這個 API 的設計是異步的,並且保證你的回調執行次數是很是有限的,並且回調是會在主線程空閒時才執行,在性能方面表現更優,使用起來也更簡單。那麼IntersectionObserver是如何實現的呢?git
這裏咱們翻譯一下Intersection就大概能明白其實現原理了。intersection 是交集的意思github
接口的原理就是經過觀察兩個元素之間是不是否有交集,而後對其進行監控操做。如今用一個簡單的例子展現web
<!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
如下是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>
經過這個接口來實現更多的功能,好比無限滾動,頁面對於廣告的展現等。