美文网首页
vue-lazyload探索

vue-lazyload探索

作者: AmazRan | 来源:发表于2018-09-08 23:41 被阅读610次
    lazyload流程图

    原理简述

    vue-lazyload是通过指令的方式实现的,定义的指令是v-lazy指令
    指令被bind时会创建一个listener,并将其添加到listener queue里面, 并且搜索target dom节点,为其注册dom事件(如scroll事件)
    上面的dom事件回调中,会遍历listener queue里的listener,判断此listener绑定的dom是否处于页面中perload的位置,如果处于则加载异步加载当前图片的资源
    同时listener会在当前图片加载的过程的loading,loaded,error三种状态触发当前dom渲染的函数,分别渲染三种状态下dom的内容


    源码解析

    1. 在组件install安装时,调用LazyClass返回了一个class对象,然后创建了一个class实例。
    2. 其核心是lazyLoadHandler()函数,是经过节流函数处理的图片加载的入口函数。
    this.lazyLoadHandler = throttle(() => {
      let catIn = false
      this.ListenerQueue.forEach(listener => {
        if (listener.state.loaded) return
        catIn = listener.checkInView()
        catIn && listener.load()
      })
    }, 200)
    checkInView () { 
      this.getRect() // 调用dom的getBoundingClientRect()
      return (this.rect.top < window.innerHeight * this.options.preLoad && this.rect.bottom > this.options.preLoadTop) &&
            (this.rect.left < window.innerWidth * this.options.preLoad && this.rect.right > 0)
      // 判断dom的顶部是否到了preload的位置,判断dom的底部是否到达了preload的位置,X轴同理。
    }
    
    1. 主要操作:找到对应的target(用于注册dom事件的dom节点;比如:页面滚动的dom节点),为其注册dom事件;为当前dom创建Listenr并添加到listener queue中。最后代用lazyLoadHandler()函数,加载图片。
    2. 当满足条件,调用load()函数异步加载图片。
    load () {
        // 如果当前尝试加载图片的次数大于指定的次数, 并且当前状态还是错误的, 停止加载动作
        if ((this.attempt > this.options.attempt - 1) && this.state.error) {
            if (!this.options.silent) console.log('error end')
            return
        }
    
        if (this.state.loaded || imageCache[this.src]) { //如果已缓存
            return this.render('loaded', true) // 使用缓存渲染图片
        }
    
        this.render('loading', false) // 调用lazy中的 elRender()函数, 用户切换img的src显示数据,并触发相应的状态的回调函数
    
        this.attempt++ // 尝试次数累加
    
        this.record('loadStart') // 记录当前状态的时间
    
        // 异步加载图片, 使用Image对象实现
        loadImageAsync({
            src: this.src
        }, data => {
            this.naturalHeight = data.naturalHeight
            this.naturalWidth = data.naturalWidth
            this.state.loaded = true
            this.state.error = false
            this.record('loadEnd')
            this.render('loaded', false) // 渲染 loaded状态的 dom的内容
            imageCache[this.src] = 1 // 当前图片缓存在浏览器里面了
        }, err => {
            this.state.error = true
            this.state.loaded = false
            this.render('error', false)
        })
    }
    
    1. loadImageAsync异步加载图片方法,通过image对象实现的网络请求
    const loadImageAsync = (item, resolve, reject) => {
        let image = new Image()
        image.src = item.src
    
        image.onload = function () {
            resolve({
                naturalHeight: image.naturalHeight, // 图片的 实际高度
                naturalWidth: image.naturalWidth,
                src: image.src
            })
        }
    
        image.onerror = function (e) {
            reject(e)
        }
    }
    
    1. lazy class的update()函数,也就是v-lazy指令绑定的数据发生改变的时候出发的回调函数
    update (el, binding) { // 获取当前dom绑定的 图片src的数据, 如果当前dom执行过load过程, 重置当前dom的图片数据和状态
      let { src, loading, error } = this.valueFormatter(binding.value) // 当前绑定的value是 obj, 从中选取{src, loading, error}; 是string, 则用作src
      // 找到当前dom绑定的listener
      const exist = find(this.ListenerQueue, item => item.el === el)
      // 更新listener的状态和状态对应的图片资源
      exist && exist.update({
          src,
          loading,
          error
      })
      this.lazyLoadHandler()
      Vue.nextTick(() => this.lazyLoadHandler())
    }
    

    总结

    作者代码结构的设计,我们可以看到Lazy load模块和listener模块他们的业务职责分工明确。lazy负责和dom相关的处理,包括为dom创建listener,为target注册dom事件,渲染dom;而listener只负责状态的控制,在不同状态执行不同的业务。
    vue中定义了lazy、lazy-container指令,方便通过attrs的形式进行配置。

    :v-lazy.container在for循环渲染中,需要配置key否则会有”bug“,git中很多issue提到这个问题。
    my research

    1. 前提概要:第一次add触发render正常,第二次update如果数量小于等于前一次,第二次不会update且沿用前一个数组的图片。
    2. 经过排查根源是attrs的确是更新了,但是v-lazy没有根据变化触发lazy.add添加key后是能正常执行
    3. 因为不同key会重新添加元素重新触发bind即执行add因此正常显示
    4. 埋点跟踪确定是update函数的书写问题。修改attr触发update通过ListenerQueue去取exist项但是loaded状态的图片会去掉listener因为它的加载使命已完成,因此就不会执行我们需要的update。
    5. 仔细分析应该不算是bug因为作者本意update就是用作未加载出的图片更新src,对于已加载的图片作整体update刷新应该通过key重新add是最佳体验。

    参考

    Vue-lazyload原理详解之源码解析
    vue-lazyload官方github

    相关文章

      网友评论

          本文标题:vue-lazyload探索

          本文链接:https://www.haomeiwen.com/subject/bgvjiftx.html