現代化懶載入的方式

SGAMER-rain發表於2018-02-04

為什麼需要懶載入

通常使用者開啟網頁時,整個網頁的內容將被下載並且呈現在一個頁面中,雖然允許瀏覽器快取頁面,但是不能保證使用者檢視所有下載的的內容,例如一個照片牆應用,可能使用者僅僅檢視第一個圖片之後離開,結果就是白白浪費了記憶體和頻寬。因此我們需要當使用者需要訪問頁面的一部分時才去載入內容,而不是一看是就去載入全部內容。

如何實現懶載入

當有人向網頁(影像,視訊)等資源,資源引用一個小的佔位符,當使用者瀏覽網頁,實際的資源被瀏覽器快取,並且當資源在螢幕上可見時替換佔位符,例如,如果使用者載入網頁並立即離開網頁,則除了網頁的頂部之外沒有任何內容被載入。

現代化懶載入的方式

懶載入具體實現

以載入圖片為例子,我們需要將img標籤中設定一個data-src屬性,它指向的是實際上我們需要載入的影像,而imgsrc指向一張預設的圖片,如果為空的話也會向伺服器傳送請求。

<img src="default.jpg" data-src="www.example.com/1.jpg">
複製程式碼

之後當使用者訪問的可視區域的img元素時,將src得值替換為data-src指向的實際資源載入的影像

具體程式碼

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:

IntersectionObserver API為開發者提供了一種可以非同步監聽目標元素與其祖先或視窗(viewport)處於交叉狀態的方式。祖先元素與視窗(viewport)被稱為根(root)。

簡單來說就是觀察一個元素和另一個元素是否重疊。

IntersectionObserver初始化的過程中提供了三個主要元素的配置:

  • root: 這是用於觀察的根元素。他定義了可觀察元素的基本捕獲框架,預設情況下,root指向的是瀏覽器的視口,但實際上可以是任意的DOM元素,要注意的是:root在這種情況下,要觀察元素的必選要在root代表的Dom元素內部
    現代化懶載入的方式
  • rootMargin: 計算交叉時新增到根(root)邊界盒bounding box的矩形偏移量, 可以有效的縮小或擴大根的判定範圍從而滿足計算需要。值得選項與marginCSS類似,比如rootMargin: '50px 20px 10px 40px'(top, right, bottom, left)
    現代化懶載入的方式
  • threshold: 一個包含閾值的list, 升序排列, list中的每個閾值都是監聽物件的交叉區域與邊界區域的比率。當監聽物件的任何閾值被越過時,都會生成一個通知(Notification)。如果構造器未傳入值, 則預設值為0.
    現代化懶載入的方式
    為了告訴我們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)
})
複製程式碼
  • 當使用Observer載入頁面時,您可能會注意到,IntersectionObserver所有觀察到的元素的回撥已經被觸發了。我們可以通過回撥函式來解決這個問題

IntersectionObserver回撥函式

new IntersectionObserver(function(entries, self))
複製程式碼

entries我們得到我們的回撥函式作為Array是特殊型別的:IntersectionObserverEntry 首先IntersectionObserverEntry含有三個不同的矩形的資訊

  • rootBounds: '捕捉框架(root + rootMargin)'的矩形
  • boundClientRect: 觀察元素本身的矩形
  • intersectionRect: 捕捉框架和觀察元素相交的矩形
    現代化懶載入的方式
    此外,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來進行判斷是否和觀察元素相交)

延遲載入的好處

  • 延遲載入在優化內容載入和簡化終端使用者體驗之間達成了平衡。
  • 使用者可以更快地載入到內容,因為使用者第一次開啟網站時只需要載入一部分內容。
  • 網站看到更高的使用者保留,因為不斷向使用者提供內容,減少了使用者離開網站的機會。
  • 網站看到較低的資源成本,因為內容只在使用者需要時才載入,而不是一次完成。

參考:

MDN

Now You See Me: How To Defer, Lazy-Load And Act With IntersectionObserver

What is Lazy Loading?

相關文章