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

文章首次發表在 我的博客

什麼是圖片懶加載

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

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

這個功能很是常見,你打開淘寶的首頁,向下滾動,就會看到會有圖片不斷的加載;你在百度中搜索圖片,結果確定成千上萬條,不可能全部的都一會兒加載出來的,很重要的緣由就是會有性能問題。你能夠在Network中查看,在頁面滾動的時候,會看到圖片一張張加載出來。node

lazyLoad

爲何要作圖片懶加載

懶加載是一種網頁性能優化的方式,它能極大的提高用戶體驗。就好比說圖片,圖片一直是影響網頁性能的主要元兇,如今一張圖片超過幾兆已是很常常的事了。若是每次進入頁面就請求全部的圖片資源,那麼可能等圖片加載出來用戶也早就走了。因此,咱們須要懶加載,進入頁面的時候,只請求可視區域的圖片資源。git

總結出來就兩個點:github

1.所有加載的話會影響用戶體驗express

2.浪費用戶的流量,有些用戶並不像所有看完,所有加載會耗費大量流量。npm

懶加載原理

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

因此能夠從 img標籤的 src屬性入手,在沒進到可視區域的時候,就先不給 img 標籤的 src屬性賦值。瀏覽器

懶加載實現

實現效果圖:性能優化

imgLazyLoad

<!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 data-src="https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657907683.jpeg">
        <img data-src="https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657913523.jpeg">
        <img data-src="https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657925550.jpeg">
        <img data-src="https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657930289.jpeg">
        <img data-src="https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657934750.jpeg">
        <img data-src="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));

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

  1. 每次滑動都要執行一次循環,若是有1000多個圖片,性能會不好
  2. 每次讀取 scrollTop 都會引發迴流
  3. scrollTop跟DOM的嵌套關係有關,應該根據getboundingclientrect獲取
  4. 滑到最後的時候刷新,會看到全部的圖片都加載了

IntersectionObserver

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

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

var observer = new IntersectionObserver(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 }。
  • 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>

參考

相關文章
相關標籤/搜索