前端效能優化-圖片懶載入(防抖、節流)

潘勇旭發表於2019-12-31

懶載入使用場景

在一些圖片量比較大的場景(電商首頁,小程式首頁等),如果我們開啟頁面時就載入所有的圖片,那勢必會導致頁面的卡頓以及白屏,給使用者不好的體驗,導致使用者流失。

但是我們仔細想一下,使用者真的需要我們顯示所有圖片一起展示嗎?其實並不是,使用者看到的只是瀏覽器可視區域的內容。所以從這個情況我們可以做一些優化,只顯示使用者可視區域內的圖片,當使用者觸發滾動的瞬間再去請求顯示給使用者。

懶載入的思路

  • img 標籤有自定義屬性 data-src
  • 首屏展示可視區域內的圖片 src 值 替換為 data-src
  • 滾動出現在可視區域的圖片即時展示 (重複第二步)

懶載入的實現

<style>
    html,
    body {
        width: 100%;
        height: 100%;
    }

    .containr {
        width: 100%;
        height: 100%;
        overflow-y: auto;

    }

    .img {
        width: 100%;
        height: 300px;
    }

    .pic {
        width: 100%;
        height: 300px;
    }
</style>

<div class="containr">
        <div class="img">
            <img class="pic" src="" alt="" data-src="./imgs/1.png">
        </div>
        <div class="img">
            <img class="pic" src="" alt="" data-src="./imgs/2.png">
        </div>
        <div class="img">
            <img class="pic" src="" alt="" data-src="./imgs/3.png">
        </div>
        <div class="img">
            <img class="pic" src="" alt="" data-src="./imgs/4.png">
        </div>
        <div class="img">
            <img class="pic" src="" alt="" data-src="./imgs/5.png">
        </div>
        <div class="img">
            <img class="pic" src="" alt="" data-src="./imgs/6.png">
        </div>
        <div class="img">
            <img class="pic" src="" alt="" data-src="./imgs/7.png">
        </div>
        <div class="img">
            <img class="pic" src="" alt="" data-src="./imgs/8.png">
        </div>
        <div class="img">
            <img class="pic" src="" alt="" data-src="./imgs/9.png">
        </div>

</div>
    
<script>
        // 獲取所有圖片的陣列
        const imgs = document.querySelectorAll('.containr .pic')
        // 獲取父元素
        const containr = document.querySelector('.containr')
        // 獲取可視區域高度
        const viewHeight = window.innerHeight
        
        const load = lazyLoad()
        // 首屏渲染
        load()
        function lazyLoad() {
            // 運用閉包 count 進行計數 避免已顯示的圖片重複參與迴圈
            let count = 0
            return () => {
                for (let i = count; i < imgs.length; i++) {
                    // getBoundingClientRect()獲取返回元素的大小及其相對於視口的位置
                    // 獲取第i張圖片是否在可視區域
                    let distance = viewHeight - imgs[i].getBoundingClientRect().top
                    if (distance >= 0) {
                        // 圖片在可視區域時設定圖片的src 為 當前元素 data-src
                        imgs[i].src = imgs[i].getAttribute('data-src')
                        // 圖片已被顯示,下次從count + 1 張開始檢查是否在可視區域
                        count += 1
                    }
                }
            }
        }
        // 新增滾動事件觸發載入
        containr.addEventListener('scroll', load, false)

</script>
複製程式碼

至此我們已經初步完成了我們的懶載入,但是我們大家都知道,scroll這個事件實在太容易被觸發了,使用者一滾動滑鼠就會觸發很多次,如果一直滾勢必會導致重複觸發執行我們的事件,這也會導致我們的效能急劇下降,所以這就引出了我們的混合體 防抖節流 來優化我們的效能。

防抖 && 節流

防抖:在一定時間內,觸發多次事件,只認第一次觸發的,到了時間結束執行事件

 function debounce(fn, time) {
            let oldTime = 0;
            return () => {
                const nowTime = new Date()
                if (nowTime - oldTime >= time) {
                   fn()
                   oldTime = nowTime
                }
            }
        }
複製程式碼

節流:在一定時間內,觸發多次事件,只認最後一次觸發的並且重置時間,到了時間結束執行事件

function throttle(fn, time) {
            let timer = null
            return () => {
                if (timer) {
                    clearTimeout(timer)
                }
                timer = setTimeout(() => {
                    fn()
                }, time);
            }
        }
複製程式碼

在這裡throttle有一個嚴重的問題就是如果使用者一直觸發事件,使用者會一直得不到響應,所以我們可以藉助防抖的思路來優化節流。

 function debounce(fn, time) {
            let oldTime = 0,
                timer = null;
            return function () {
                const nowTime = new Date()
                 // 保留呼叫時的this上下文
                let context = this
                // 保留呼叫時傳入的引數
                let args = arguments
                if (nowTime - oldTime < time) {
                    if (timer) {
                        clearTimeout(timer)
                    }
                    timer = setTimeout(() => {
                        oldTime = nowTime
                        fn.apply(context, args)
                    }, time);
                } else {
                    // 使用者重複觸發,到達事件節點 還是會去執行事件 
                    oldTime = nowTime
                    fn.apply(context, args)
                }
            }
        }
複製程式碼

所以最後我們監聽事件可以修改為containr.addEventListener('scroll', debounce(load, 1000), false),這就完美達到了我們優化的目的。

歡迎大家進行指導留言,謝謝大家。

相關文章