Vue图片懒加载(自定义一个v-lazyload)

321 阅读3分钟

功能分析

  • 懒加载的原理很简单,通过对滚动事件的监听,判断图片是否进入视口,如果没进入视口则不进行渲染
  • 所以很明显,要实现一个懒加载,需要的函数有以下几个:
    1. 判断图片是否进入了可视区
    2. 监听滚动事件
    3. 检查图片是否已经加载过了(不要指望用户一定会一下滑过一整张图)
    4. 如果图片已经加载过了,不重复加载

准备工作:准备好所有功能函数

  • 首先准备两个数组,一个放已经加载过的图片,一个放没有加载的图片;以及准备一个默认图片,如果滚动过快先显示默认图片
    let init = { //默认图片的src
        default:'又不是不能用.jpg'
    }
    let futureList = []; // 未加载的图片
    let cacheList = []; // 已加载的图片
  • 定义一个方法,检查图片是否已经加载过
 if (!Array.prototype.remove) {
        Array.prototype.remove = function (item) {
            // 如果此项不存在,即没有加载过,不进行操作直接返回
            if (!this.length) {
                return
            }
            let index = this.indexOf(item)
            // 如果已经加载过了,就截取掉
            if (index > -1) {
                this.splice(index, 1)
                return this
            }
        }
    }
  • 检查图片是否已经加载
    const isLoad = (imgSrc) => {
            //如果在缓存数组中存在,则已经加载过,返回true,否则false
            if (cacheList.indexOf(imgSrc) > -1) {
                return true
            } else {
                return false
            }
    }
  • 检查图片是否进入可视区
 const scrollIn = (item) => {
        let el = item.el;
        let src = item.src;
        // 获取图片距离页面顶部的距离
        // el.getBoundingClientRect()用于获取元素相对于视口的位置,返回left, top, right, bottom等,是原生api
        let top = el.getBoundingClientRect().top
        // 获取页面可视区的高度
        let winHeight = window.innerHeight;
        // top+5 此时已经进入了可视区5px
        if (top + 5 < winHeight) {
            let image = new Image()
            image.src = src
            image.onload = function () {
                el.src = src
                cacheList.push(src)
                futureList.remove(item)
            }
            return true
        } else {
            return false
        }
    }
  • 监听滚动
    const listenScroll = () => {
        window.addEventListener('scroll',function(){
            let length = futureList.length
            for(let i = 0; i < length; i++) {
                scrollIn(futureList[i])
            }
        })
    }

函数主体

const addListener = (el,binding) =>{
    let imgSrc = bing.value
    // 检查图片是否已加载,如果已经加载过,直接给src赋值
    if(isLoad(imgSrc)){
        el.src = imgSrc
        return false
    }
    let item = {
        el:el,
        src:imgSrc
    }
    //挂载默认图片
    el.src = init default
    //检查图片是否可以显示
    if(scrollIn(item)) {
        return
    }
    //如果还未到可视区,不能显示,push到futureList中
    futureList.push(item)
    // 监听滚动事件
    listenScroll()
}

注册指令

  • vue为我们提供了自定义指令时可以用到的一些钩子函数,这里用到的有两个:
    1. update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新
    2. inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
  • 注册
 Vue.directive('lazyload', {
        inserted: addListener,
        updated: addListener
    })

全部代码

export default (Vue) => {
     let init = {
        default: 'http://wx4.sinaimg.cn/large/a910d180gy1fwdiyoghi3j20to18uacp.jpg'
    }
    let futureList = [];
    let cacheList = [];

    const listenScroll = () => {
        window.addEventListener('scroll', function () {
            let length = futureList.length
            for (let i = 0; i < length; i++) {
                scrollIn(futureList[i])
            }
        })
    }
    
    const isLoad = (imgSrc) => {
        if (cacheList.indexOf(imgSrc) > -1) {
            return true
        } else {
            return false
        }
    }

    const scrollIn = (item) => {
        let el = item.el;
        let src = item.src;
        let top = el.getBoundingClientRect().top
        let winHeight = window.innerHeight;
        if (top + 5 < winHeight) {
            let image = new Image()
            image.src = src
            image.onload = function () {
                el.src = src
                cacheList.push(src)
                futureList.remove(item)
            }
            return true
        } else {
            return false
        }
    }

    if (!Array.prototype.remove) {
        Array.prototype.remove = function (item) {
            if (!this.length) {
                return
            }
            let index = this.indexOf(item)
            if (index > -1) {
                this.splice(index, 1)
                return this
            }
        }
    }

    const addListener = (el, binding) => {
        let imgSrc = binding.value
        if (isLoad(imgSrc)) {
            el.src = imgSrc
            return false
        }
        let item = {
            el,
            src: imgSrc
        }
        el.src = init.default
        if (scrollIn(item)) {
            return
        }
        futureList.push(item)
        listenScroll()
    }

    Vue.directive('lazyload', {
        inserted: addListener,
        updated: addListener
    })
    // 之后只需要在main.js中import,并use即可使用
    // <img v-lazyload="./xx/xx.png">
}