前端優化之圖片懶載入

_wind發表於2018-06-12

http請求

如果你學過計算機網路你就會知道,我們請求一個帶有n張圖片的html檔案實際上會傳送n+1次請求,因為在瀏覽器解析html的時候遇到了src,就會請求src後面的內容。

設想一下如果我們的頁面有1000000張圖片,那麼如果等待這些圖片響應成功並載入完時延是非常大的,而且圖片的載入是同步的,載入時會阻塞瀏覽器繼續向下解析,使用者體驗非常差。

那麼我們可不可以讓圖片按需載入呢?當圖片出現在可視區的時候再載入它而不是一開始就載入完全部圖片。

圖片懶載入

template:

<div @scroll="lazyLoad" ref="lazy">
    <img v-for="(src, index) in imgs" src="##" :dataSrc="src" :key="index">
    <!--more img-->
</div>

改變圖片src

監聽最外層div的滾動事件,觸發滾動時遍歷圖片檢測圖片位置,若在可視區內則顯示

loadImg() {
    var img = this.$refs.lazy.getElementsByClassName("lazyImg"); 
    // 已滾動高度+可視區高度
    var top = this.$refs.lazy.scrollTop + this.$refs.lazy.clientHeight;
    
    for(var i = 0; i < img.length; i++) {
        if(img[i].offsetTop <= top) {  // 在可視區內則顯示圖片
            img[i].src = img[i].getAttribute("datasrc");
        }
    }
},
lazyLoad() { 
    this.loadImg();
}

以上就實現了一個圖片懶載入,本篇文章就到這裡,再見。

橋豆麻袋,突然發現一個嚴重的問題:滾動過程中會不斷觸發lazyLoad對圖片做一個遍歷並判斷,那麼就會做無數次for迴圈,更可怕的是,修改一次src會傳送一個請求,在滾動的時候我們的for迴圈每次都從頭判斷並修改src請求圖片,那麼請求次數可想而知。

函式防抖

如果在滾動過程中不斷觸發遍歷並判斷圖片是否在可視區的監聽事件,會耗費很大的效能,這裡採用函式防抖:當使用者停止滾動時統一遍歷判斷圖片位置

debounce(fn) {
    // 函式防抖:使用者停止操作之後觸發
    clearTimeout(this.timer);
    this.timer = setTimeout(() => {
        fn();
    }, 1000);
}

我們可以將載入圖片的方法放在debounce中

lazyLoad() {
    this.debounce(this.loadImg);
}

這樣當使用者滾動頁面時,鬆開手才會執行loadImg來遍歷判斷圖片位置。

又出現了一個問題:如果使用者在滾動時從頁面底部上拉到頂部一直沒有鬆手,那麼在這期間都不會執行loadImg,這意味著頁面的圖片都不會顯示,非常影響使用者體驗

防抖優化

我們規定,若使用者上拉高度大於500px那麼就自動載入一次可視區內圖片,這裡我們用oldScrollTop記錄上次上拉高度

lazyLoad() {
    // 如果上拉距離大於500px則自動載入
    if(this.$refs.lazy.scrollTop - this.oldScrollTop > 500) {
        this.loadImg();
        this.oldScrollTop = this.$refs.lazy.scrollTop; // 更新oldScrollTop
    } else {  // 如果向下拉但小於500px則防抖載入
        this.debounce(this.loadImg);
    }
}

下拉優化

當使用者下拉的時候我們並不需要執行lazyLoad,因為我們之前的圖片已經載入過了,所以可以修改一下lazyLoad

lazyLoad() {
    // 如果上拉距離大於500px則自動載入
    if(this.$refs.lazy.scrollTop - this.oldScrollTop > 500) {
        this.loadImg();
        this.oldScrollTop = this.$refs.lazy.scrollTop;
    } else if(this.$refs.lazy.scrollTop - this.oldScrollTop < 0) {  // 如果向下拉則不做操作
        return ;
    } else {  // 如果向下拉但小於500px則防抖載入
        this.debounce(this.loadImg);
    }
}

減少遍歷個數

最重要的優化已經做完了,但是還可以從一些小細節更加優化一下,我們的loadImg方法中每次都是從0號下標開始遍歷檢查圖片,但是在使用者上拉操作之後一部分圖片已經被載入了,就不需要再次去檢查了。

我們可以用一個變數len記錄上一次被載入後的最後一個圖片,然後修改一下loadImg

 loadImg() {
    var img = this.getImages(); 
    var top = this.$refs.lazy.scrollTop + window.screen.height;
    
    // 從len開始檢查
    for(var i = this.len; i < img.length; i++) {
        if(img[i].offsetTop <= top) {
            img[i].src = img[i].getAttribute("datasrc");
            this.len = i;  // 更新len
        }
    }
}

結語

一個完整的優化版圖片懶載入就完成了,我將該功能封裝成了一個vue的外掛

原始碼:vue-plugins

該外掛庫會持續更新,歡迎star & fork~ 

相關文章