一般用戶打開網頁時,整個網頁的內容將被下載而且呈如今一個頁面中,雖然容許瀏覽器緩存頁面,可是不能保證用戶查看全部下載的的內容,例如一個照片牆應用,可能用戶僅僅查看第一個圖片以後離開,結果就是白白浪費了內存和帶寬。所以咱們須要當用戶須要訪問頁面的一部分時纔去加載內容,而不是一看是就去加載所有內容。node
當有人向網頁(圖像,視頻)等資源,資源引用一個小的佔位符,當用戶瀏覽網頁,實際的資源被瀏覽器緩存,而且當資源在屏幕上可見時替換佔位符,例如,若是用戶加載網頁並當即離開網頁,則除了網頁的頂部以外沒有任何內容被加載。 api
以加載圖片爲例子,咱們須要將img
標籤中設置一個data-src
屬性,它指向的是實際上咱們須要加載的圖像,而img
的src
指向一張默認的圖片,若是爲空的話也會向服務器發送請求。瀏覽器
<img src="default.jpg" data-src="www.example.com/1.jpg"> 複製代碼
以後當用戶訪問的可視區域的img
元素時,將src
得值替換爲data-src
指向的實際資源加載的圖像緩存
具體代碼bash
const lazy = (el) => { let scrTop = getTop(); let windowHeight = document.documentElement.clientHeight; function getTop(){ return document.documentElement.scrollTop || document.body.scrollTop; } function getOffset(node){ return node.getBoundingClientRect().top + scrTop; } function inView(node){ // 設立閾值 const threshold = 0; const viewTop = scrTop; const viewBot = viewTop + windowHeight; const nodeTop = getOffset(node); const nodeBot = nodeTop + node.offsetHeight; const offset = (threshold / 100) * windowHeight; console.log((nodeBot >= viewTop - offset), (nodeTop <= viewBot + offset)) return (nodeBot >= viewTop - offset) && (nodeTop <= viewBot + offset) } function check(node){ let el = document.querySelector(node); let images = [...el.querySelectorAll('img')]; images.forEach(img => { if(inView(img)){ img.src = img.dataset.src; } }) } check(el); } window.onscroll = function(){ lazy('.foo'); } 複製代碼
經過上面例子的實現,咱們要實現懶加載都須要去監聽scroll事件,儘管咱們能夠經過函數節流的方式來阻止高頻率的執行函數,可是咱們仍是須要去計算scrollTop
,offsetHeight
等屬性,有沒有簡單的不須要計算這些屬性的方式呢,答案是有的---IntersectionObserver服務器
根據MDN:markdown
IntersectionObserver API爲開發者提供了一種能夠異步監聽目標元素與其祖先或視窗(viewport)處於交叉狀態的方式。祖先元素與視窗(viewport)被稱爲根(root)。app
簡單來講就是觀察一個元素和另外一個元素是否重疊。框架
IntersectionObserver初始化的過程當中提供了三個主要元素的配置:異步
root
指向的是瀏覽器的視口,但實際上能夠是任意的DOM元素,要注意的是:root
在這種狀況下,要觀察元素的必選要在root表明的Dom元素內部
margin
CSS相似,好比rootMargin: '50px 20px 10px 40px'
(top, right, bottom, left)
intersectionObserver
咱們想要得配置,咱們只須要將咱們得config
對象和咱們的回調函數一塊兒傳遞到Observer構造函數中const config = { root: null, rootMargin: '0px', threshold: 0.5 } let observer = new IntersectionObserver(fucntion(entries){ // ... }, config) 複製代碼
如今咱們須要去給IntersectionObserver
實際觀察的元素
const img = document.querySelector('image'); observer.observe(img); 複製代碼
關於這個實際觀察的元素須要注意幾點
root
表明的DOM元素中IntersectionObserver
一次只能接受一個觀察元素,不支持批量觀察。這意味着若是你須要觀察幾個元素(好比說一個頁面上的幾個圖像),你必須遍歷全部元素並分別觀察它們中的每個const images = document.querySelecttorAll('img'); images.forEach(image => { observer.observe(image) }) 複製代碼
IntersectionObserver回調函數
new IntersectionObserver(function(entries, self)) 複製代碼
在entries
咱們獲得咱們的回調函數做爲Array是特殊類型的:IntersectionObserverEntry
首先IntersectionObserverEntry
含有三個不一樣的矩形的信息
root
+ rootMargin
)'的矩形IntersectionObserverEntry
還提供了isIntersecting
,這是一個方便的屬性,返回觀察元素是否與捕獲框架相交, 另外,IntersectionObserverEntry
提供了利於計算的遍歷屬性intersctionRatio
:返回intersectionRect 與 boundingClientRect 的比例值.
target
則返回要觀察的元素 好了簡單介紹完,讓咱們回到正題,用這個IntersectionObserver
來實現代化的懶加載方式吧
const images = document.querySelectorAll('[data-src]') const config = { rootMargin: '0px', threshold: 0 }; let observer = new IntersectionObserver((entries, self)=>{ entries.forEach(entry => { if(entry.isIntersecting){ // 加載圖像 preloadImage(entry.target); // 解除觀察 self.unobserve(entry.target) } }) }, config) images.forEach(image => { observer.observe(image); }); function preloadImage(img) { const src = img.dataset.src if (!src) { return; } img.src = src; } 複製代碼
相比於以前懶加載的方式是否是更加簡潔,並且只有當觀察元素和捕捉框架交叉或重疊時,纔會觸發回掉函數(加載頁面時也會觸發回調函數,不過咱們能夠用isIntersecting
來進行判斷是否和觀察元素相交)
參考:
Now You See Me: How To Defer, Lazy-Load And Act With IntersectionObserver