VUI建立日誌(一)——圖片懶加載指令的實現

1. 項目實現介紹

vue 項目搭建參考《Webpack4 搭建 Vue 項目》前端

文檔使用 vuepress, 官方文檔 vuepress.vuejs.orgvue

發佈文檔 github pages + gh-pagewebpack

項目地址 github.com/zxpsuper/vu…git

文檔地址 zxpsuper.github.io/vui-vuegithub

處於自我摸索階段,期待留下您的寶貴意見!web

2. v-lazy 的基本實現

圖片懶加載的基本原理:npm

  1. 先用佔位圖代替目標圖片的 src 屬性值
  2. 當圖片的 offsetTop < innerHeight + scrollTop 時,即圖片出如今窗口內部,此時修改 src 值爲 data-src 的值
  3. 固然,這一切須要不斷地監聽滾動事件

先實現一個懶加載函數

var img = document.getElementsByTagName('img');
function lazyload() {
        //監聽頁面滾動事件
        var seeHeight = window.innerHeight; //可見區域高度
        var scrollTop =
            document.documentElement.scrollTop || document.body.scrollTop; //滾動條距離頂部高度
        for (var i = 0; i < img.length; i++) {
            if (img[i].getAttribute('data-image-show')) continue; // 若是已經加載完成,則不需走下面流程
            if (img[i].offsetTop < seeHeight + scrollTop) {
                console.log(img[i].offsetTop, seeHeight, scrollTop);
                if (img[i].getAttribute('src') == Vue.$vuiLazyLoad.img) {
                    img[i].src = img[i].getAttribute('data-src');
                    img[i].setAttribute('data-image-show', 'true'); // 給個標識,表示已經加載完成
                }
            }
        }
    }
複製代碼

滾動監聽

滾動監聽,不斷滾動便會不斷觸發滾動監聽的函數,影響性能,所以在此須要加入一個防抖函數canvas

// 防抖函數
function debounce(event, time) {
    let timer = null;
    return function(...args) {
        if (timer) clearTimeout(timer)
        timer = setTimeout(() => {
                timer = null;
                event.apply(this, args);
            }, time);
    };
}
複製代碼

添加監聽和去除監聽segmentfault

window.removeEventListener('scroll', debounce(lazyload, 800));
window.addEventListener('scroll', debounce(lazyload, 800));
複製代碼

添加指令

這裏用到了自定義指令中的三個鉤子函數 bind,inserted,unbind, 咱們要讓指令中佔位圖可修改,所以寫成函數形式app

const lazyload = function(Vue) {
    var img = document.getElementsByTagName('img'), evenFunction;
    function lazyload() {
        //監聽頁面滾動事件
        var seeHeight = window.innerHeight; //可見區域高度
        var scrollTop =
            document.documentElement.scrollTop || document.body.scrollTop; //滾動條距離頂部高度
        for (var i = 0; i < img.length; i++) {
            if (img[i].getAttribute('data-image-show')) continue; // 若是已經加載完成,則不需走下面流程
            if (img[i].offsetTop < seeHeight + scrollTop) {
                console.log(img[i].offsetTop, seeHeight, scrollTop);
                if (img[i].getAttribute('src') == Vue.$vuiLazyLoad.img) {
                    img[i].src = img[i].getAttribute('data-src');
                    img[i].setAttribute('data-image-show', 'true'); // 給個標識,表示已經加載完成
                }
            }
        }
    }
    // vui中的默認配置,用戶可經過修改 Vue.$vuiLazyLoad.img 進行修改佔位圖
    Vue.$vuiLazyLoad = {
        img:
            'https://github.com/zxpsuper/Demo/blob/master/images/avatar.jpg?raw=true',
        imgLength: 0, // 懶加載的圖片數量,當數量爲 0 的時候移除滾動監聽
    };
    
    lazyload(); //頁面載入完畢加載但是區域內的圖片
    evenFunction = debounce(lazyload, 800);
    window.removeEventListener('scroll', evenFunction);
    window.addEventListener('scroll', evenFunction);
    return {
        name: 'lazy',
        // 綁定
        bind(el, binding) {
            el.setAttribute('src', Vue.$vuiLazyLoad.img);
            el.setAttribute('data-src', binding.value);
            Vue.$vuiLazyLoad.imgLength++;
        },
        // 插入
        inserted(el) {
            // 暫時空
        },
        // 解綁
        unbind() {
            Vue.$vuiLazyLoad.imgLength--; // 每次解綁,自減
            if (!Vue.$vuiLazyLoad.imgLength && evenFunction)
                window.removeEventListener('scroll', evenFunction);
        },
    };
}
複製代碼

使用新特性 IntersectionObserver

IntersectionObserver接口(從屬於Intersection Observer API)爲開發者提供了一種能夠異步監聽目標元素與其祖先或視窗(viewport)交叉狀態的手段。

觀察元素是否與視窗交叉,如果則修改 scrdata-src 值,並解除觀察狀態,固然這一切的前提是你在圖片建立的時候觀察圖片自己,所以在圖片插入時的鉤子函數內

inserted(el) {
    if (IntersectionObserver) lazyImageObserver.observe(el);
},
複製代碼

具體使用方法:

let lazyImageObserver
if (IntersectionObserver) {
    lazyImageObserver = new IntersectionObserver((entries, observer) => {
        entries.forEach((entry, index) => {
            let lazyImage = entry.target;
            // 若是元素可見
            if (entry.intersectionRatio > 0) {
                if (lazyImage.getAttribute('src') == Vue.$vuiLazyLoad.img) {
                    lazyImage.src = lazyImage.getAtstribute('data-src');
                }
                lazyImageObserver.unobserve(lazyImage); // 解除觀察
            }
        });
    });
}
複製代碼

固然咱們優先使用 IntersectionObserver, 若不支持則使用傳統方法

註冊指令

import Vue from 'vue'
Vue.directive('lazy', lazyLoad(Vue));
複製代碼

3. 完整代碼

const lazyLoad = function(Vue) {
    var img = document.getElementsByTagName('img');
    function lazyload() {
        console.log('滾動lazy');
        //監聽頁面滾動事件
        var seeHeight = window.innerHeight; //可見區域高度
        var scrollTop =
            document.documentElement.scrollTop || document.body.scrollTop; //滾動條距離頂部高度
        for (var i = 0; i < img.length; i++) {
            if (img[i].getAttribute('data-image-show')) continue;
            if (img[i].offsetTop < seeHeight + scrollTop) {
                console.log(img[i].offsetTop, seeHeight, scrollTop);
                if (img[i].getAttribute('src') == Vue.$vuiLazyLoad.img) {
                    img[i].src = img[i].getAttribute('data-src');
                    img[i].setAttribute('data-image-show', 'true');
                }
            }
        }
    }
    Vue.$vuiLazyLoad = {
        img:
            'https://github.com/zxpsuper/Demo/blob/master/images/avatar.jpg?raw=true',
        imgLength: 0,
    };

    var lazyImageObserver, evenFunction;

    if (IntersectionObserver) {
        lazyImageObserver = new IntersectionObserver((entries, observer) => {
            entries.forEach((entry, index) => {
                let lazyImage = entry.target;
                // 若是元素可見
                if (entry.intersectionRatio > 0) {
                    if (lazyImage.getAttribute('src') == Vue.$vuiLazyLoad.img) {
                        lazyImage.src = lazyImage.getAttribute('data-src');
                    }
                    lazyImageObserver.unobserve(lazyImage);
                }
            });
        });
    } else {
        lazyload(); //頁面載入完畢加載但是區域內的圖片
        evenFunction = debounce(lazyload, 800);
        window.removeEventListener('scroll', evenFunction);
        window.addEventListener('scroll', evenFunction);
    }
    return {
        name: 'lazy',
        bind(el, binding) {
            el.setAttribute('src', Vue.$vuiLazyLoad.img);
            el.setAttribute('data-src', binding.value);
            Vue.$vuiLazyLoad.imgLength++;
        },
        inserted(el) {
            if (IntersectionObserver) lazyImageObserver.observe(el);
        },
        unbind() {
            Vue.$vuiLazyLoad.imgLength--;
            if (!Vue.$vuiLazyLoad.imgLength && evenFunction)
                window.removeEventListener('scroll', evenFunction);
        },
    };
};

export default lazyLoad;

function debounce(event, time) {
    let timer = null;
    return function(...args) {
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => {
            timer = null;
            event.apply(this, args);
        }, time);
    };
}

複製代碼

總結

本文是對vue自定義指令及懶加載原理的綜合實現,如有錯誤,望指出共同進步。

更多推薦

前端進階小書(advanced_front_end)

前端每日一題(daily-question)

webpack4 搭建 Vue 應用(createVue)

Canvas 進階(一)二維碼的生成與掃碼識別

Canvas 進階(二)寫一個生成帶logo的二維碼npm插件

Canvas 進階(三)ts + canvas 重寫」辨色「小遊戲

Canvas 進階(四)實現一個「刮刮樂」遊戲

相關文章
相關標籤/搜索