vue-图片懒加载

268 阅读2分钟

当用户向服务端请求一组图片数据后,还需对每个图片的src再次请求,当图片数量较多时,图片的响应和加载完成会消耗大量时间,影响用户体验。本文旨在通过vue插件的方式实现图片懒加载,原理是监听滚动元素的滚动事件,当图片将要出现在视口时再进行加载

定义图片监听构造函数

// vue-lazyload/lazy.js
  class ReactiveListener {
    constructor({el, src, options, elRender}){
      this.el = el;
      this.src = src;
      this.options = options;
      this.elRender = elRender;
      this.state = {loading: false}
    }

    checkInview = () => {
      let { top } = this.el.getBoundingClientRect();
      return top < window.innerHeight * this.options.preLoad
    }
    
    loadImgAsync = (src, resolve, reject) => {
        let image = new Image();
        image.src = src;
        image.onload = resolve;
        image.onerror = reject
    }

    load = () => { // 3.2. 加载:开始渲染和渲染前 默认loading;异步加载成功时,渲染真实的图片;失败,传递失败态
      this.elRender(this, 'loading');
      this.loadImgAsync(this.src, () => {
        this.state.loading = true
        this.elRender(this, 'loaded');
      }, () => {
        this.elRender(this, 'error');
      });
    }
  }

定义懒加载构造函数

// vue-lazyload/lazy.js
class LazyClass {
    constructor(options) {
      this.options = options;
      this.listenerQuene = [];
      this.bindHandler = false;
    }

    lazyLoadHandler = throttle(() => {
      let catIn = false; 
      this.listenerQuene.forEach(listener => {
        if (listener.state.loading) return; // 已经渲染过不再处理
        catIn = listener.checkInview();
        catIn && listener.load();
      })
    }, 300)
    
    elRender = (listener, state) => { // 2.1.图片渲染函数
      let {el, options, src} = listener;
      
      let imgSrc = ''
      switch (state) {
        case 'loading':
          imgSrc = options.loading
          break;
        case 'error':
          imgSrc = options.error || ''
          break;
        default:
          imgSrc = src
          break;
      }
      el.setAttribute('src', imgSrc)
    }
  
    add = (el, bindings) => {
      Vue.nextTick(() => { // 在bind时无法获取真实的dom,所以使用$nextTick
        function scrollParent() {
          let parent = el.parentNode;
          while(parent) {
            if (/scroll|auto/.test(getComputedStyle(parent)['overflow'])) {
              return parent
            }
            parent = parent.parentNode;
          }
          return parent
        }
        let parent = scrollParent();
        let src = bindings.value;
        let listener = new ReactiveListener({
          el,
          src,
          options: this.options,
          elRender: this.elRender
        });
        this.listenerQuene.push(listener);
        if(!this.bindHandler) {
          this.bindHandler = true;
          parent.addEventListener('scroll', this.lazyLoadHandler);
        }
        this.lazyLoadHandler(); // 默认执行一次,进行内容渲染
      })
    }
}

定义install方法

当通过Vue.use引入插件时,默认会调用插件的install方法;在install方法中,创建Lazy实例,定义v-lazy指令,当指令绑定在元素上时,执行实例的add方法

// 文件vue-lazyload/index.js

import Lazy from  './lazy'
export default {
  install(Vue, options){
    const LazyClass = Lazy(Vue);
    const lazy = new LazyClass(options);
    Vue.directive('lazy', {
      bind: lazy.add.bind(lazy)
    })
  }
}

引入VueLazyload插件

import Vuelazyload from './vue-lazyload'

Vue.use(Vuelazyload, {
  preLoad: 1.3,
  loading: loading,
})

使用

<li v-for="(img, index) in imgs" :key='index'>
     <img v-lazy="img">
</li>
    ---------------------------------------------
data() {
    return {
      imgs:['https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3892521478,1695688217&fm=26&gp=0.jpg','https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3892521478,1695688217&fm=26&gp=0.jpg','https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3892521478,1695688217&fm=26&gp=0.jpg','https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3892521478,1695688217&fm=26&gp=0.jpg','https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3892521478,1695688217&fm=26&gp=0.jpg','https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3892521478,1695688217&fm=26&gp=0.jpg','https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3892521478,1695688217&fm=26&gp=0.jpg','https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3892521478,1695688217&fm=26&gp=0.jpg','https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3892521478,1695688217&fm=26&gp=0.jpg','https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3892521478,1695688217&fm=26&gp=0.jpg','https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3892521478,1695688217&fm=26&gp=0.jpg','https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3892521478,1695688217&fm=26&gp=0.jpg','https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3892521478,1695688217&fm=26&gp=0.jpg','https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3892521478,1695688217&fm=26&gp=0.jpg','https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3892521478,1695688217&fm=26&gp=0.jpg','https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3892521478,1695688217&fm=26&gp=0.jpg','https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3892521478,1695688217&fm=26&gp=0.jpg','https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3892521478,1695688217&fm=26&gp=0.jpg','https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3892521478,1695688217&fm=26&gp=0.jpg','https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3892521478,1695688217&fm=26&gp=0.jpg']
    }
}