1. 程式人生 > 實用技巧 >實現一個Vue自定義指令懶載入

實現一個Vue自定義指令懶載入

什麼是圖片懶載入

當我們向下滾動的時候圖片資源才被請求到,這也就是我們本次要實現的效果,進入頁面的時候,只請求可視區域的圖片資源這也就是懶載入。

比如我們載入一個頁面,這個頁面很長很長,長到我們的瀏覽器可視區域裝不下,那麼懶載入就是優先載入可視區域的內容,其他部分等進入了可視區域在載入。

這個功能非常常見,你開啟淘寶的首頁,向下滾動,就會看到會有圖片不斷的載入;你在百度中搜索圖片,結果肯定成千上萬條,不可能所有的都一下子加載出來的,很重要的原因就是會有效能問題。你可以在Network中檢視,在頁面滾動的時候,會看到圖片一張張加載出來。

為什麼要做圖片懶載入

懶載入是一種網頁效能優化的方式,它能極大的提升使用者體驗。就比如說圖片,圖片一直是影響網頁效能的主要元凶,現在一張圖片超過幾兆已經是很經常的事了。如果每次進入頁面就請求所有的圖片資源,那麼可能等圖片加載出來使用者也早就走了。所以,我們需要懶載入,進入頁面的時候,只請求可視區域的圖片資源。

總結出來就兩個點:

1.全部載入的話會影響使用者體驗

2.浪費使用者的流量,有些使用者並不像全部看完,全部載入會耗費大量流量。

懶載入原理

圖片的標籤是img標籤,圖片的來源主要是 src屬性,瀏覽器是否發起載入圖片的請求是根據是否有src屬性決定的。

所以可以從img標籤的 src屬性入手,在沒進到可視區域的時候,就先不給 img 標籤的 src屬性賦值。

懶載入實現

實現效果圖:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        div
{ display: flex; flex-direction: column; } img { width: 100%; height: 300px; } </style> </head> <body> <div> <img >"https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657907683.jpeg"> <img >"https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657913523.jpeg"> <img >"https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657925550.jpeg"> <img >"https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657930289.jpeg"> <img >"https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657934750.jpeg"> <img >"https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657918315.jpeg"> </div> </body> </html>

監聽 scroll 事件判斷元素是否進入視口

const imgs = [...document.getElementsByTagName('img')];
 let n = 0;

 lazyload();

 function throttle(fn, wait) {
    let timer = null;
    return function(...args) {
        if(!timer) {
            timer = setTimeout(() => {
                timer = null;
                fn.apply(this, args)
            }, wait)
        }
    }
 }
  
 function lazyload() {
    var innerHeight = window.innerHeight; 
    var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
    for(let i = n; i < imgs.length; i++) {
        if(imgs[i].offsetTop < innerHeight + scrollTop) {
            imgs[i].src = imgs[i].getAttribute("data-src");
            n = i + 1;
        }
        
    }
 }
 window.addEventListener('scroll', throttle(lazyload, 200));

可能會存在下面幾個問題:

每次滑動都要執行一次迴圈,如果有1000多個圖片,效能會很差

每次讀取 scrollTop 都會引起迴流

scrollTop跟DOM的巢狀關係有關,應該根據getboundingclientrect獲取

滑到最後的時候重新整理,會看到所有的圖片都載入了

IntersectionObserver

Intersection Observer API提供了一種非同步觀察目標元素與祖先元素或頂級文件viewport的交集中的變化的方法。

建立一個 IntersectionObserver物件,並傳入相應引數和回撥用函式,該回調函式將會在目標(target)元素和根(root)元素的交集大小超過閾值(threshold)規定的大小時候被執行。

varobserver =newIntersectionObserver(callback, options);

IntersectionObserver是瀏覽器原生提供的建構函式,接受兩個引數:callback是可見性變化時的回撥函式(即目標元素出現在root選項指定的元素中可見時,回撥函式將會被執行),option是配置物件(該引數可選)。

返回的observer是一個觀察器例項。例項的 observe 方法可以指定觀察哪個DOM節點。

具體的用法可以 檢視MDN文件

const imgs = [...document.getElementsByTagName('img')];
// 當監聽的元素進入可視範圍內的會觸發回撥
 if(IntersectionObserver) {
     // 建立一個 intersection observer
     let lazyImageObserver = new IntersectionObserver((entries, observer) => {
         entries.forEach((entry, index) => {
             let lazyImage = entry.target;
             // 相交率,預設是相對於瀏覽器視窗
             if(entry.intersectionRatio > 0) {
                lazyImage.src = lazyImage.getAttribute('data-src');
                // 當前圖片載入完之後需要去掉監聽
                 lazyImageObserver.unobserve(lazyImage);
             }

         })
     })
     for(let i = 0; i < imgs.length; i++) {
        lazyImageObserver.observe(imgs[i]);
     }
 }

vue自定義指令-懶載入

vue自定義指令

下面的api來自官網自定義指令:

鉤子函式

bind: 只調用一次,指令第一次繫結到元素時呼叫。在這裡可以進行一次性的初始化設定。

inserted: 被繫結元素插入父節點時呼叫 (僅保證父節點存在,但不一定已被插入文件中)。

update: 所在元件的 VNode 更新時呼叫,但是可能發生在其子 VNode 更新之前。指令的值可能發生了改變,也可能沒有。但是你可以通過比較更新前後的值來忽略不必要的模板更新

componentUpdated: 指令所在元件的 VNode 及其子 VNode 全部更新後呼叫。

unbind: 只調用一次,指令與元素解綁時呼叫。

鉤子函式引數

指令鉤子函式會被傳入以下引數:

el:指令所繫結的元素,可以用來直接操作 DOM。

binding:一個物件,包含以下 property:

name:指令名,不包括 v- 字首。

value:指令的繫結值,例如:v-my-directive="1 + 1" 中,繫結值為 2。

oldValue:指令繫結的前一個值,僅在 update 和 componentUpdated 鉤子中可用。無論值是否改變都可用。

expression:字串形式的指令表示式。例如 v-my-directive="1 + 1" 中,表示式為 "1 + 1"。

arg:傳給指令的引數,可選。例如 v-my-directive:foo 中,引數為 "foo"。

modifiers:一個包含修飾符的物件。例如:v-my-directive.foo.bar 中,修飾符物件為 { foo: true, bar: true }。

豌豆資源搜尋網站https://55wd.com 電腦刺繡繡花廠 ttp://www.szhdn.com

vnode:Vue 編譯生成的虛擬節點。

oldVnode:上一個虛擬節點,僅在 update 和 componentUpdated 鉤子中可用。

實現 v-lazyload 指令

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <style>
            img {
                width: 100%;
                height: 300px;
            }
        </style>
    </head>
    <body>
        <div id="app">
            <p v-for="item in imgs" :key="item">
                <img v-lazyload="item">
            </p>
        </div>
    </body>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <script>
        Vue.directive("lazyload", {
            // 指令的定義
            bind: function(el, binding) {
                let lazyImageObserver = new IntersectionObserver((entries, observer) => {
                    entries.forEach((entry, index) => {
                        let lazyImage = entry.target;
                        // 相交率,預設是相對於瀏覽器視窗
                        if(entry.intersectionRatio > 0) {
                            lazyImage.src = binding.value;
                            // 當前圖片載入完之後需要去掉監聽
                            lazyImageObserver.unobserve(lazyImage);
                        }

                    })
                })
                lazyImageObserver.observe(el);
            },
        });
        var app = new Vue({
            el: "#app",
            data: {
                imgs: [
                    'https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657907683.jpeg',
                    'https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657913523.jpeg',
                    'https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657925550.jpeg',
                    'https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657930289.jpeg',
                    'https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657934750.jpeg',
                    'https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657918315.jpeg',
                ]
            },
        });
    </script>
</html>