Vue插件開發初體驗——(懶加載)

Vue插件開發初體驗——(懶加載)

前言

閒來無事,想本身開發一個簡單的Vue懶加載插件,能力的提高我以爲是能夠經過編寫插件實現,研究了一下官網的 Vue插件編寫。立刻本身獨立開始編寫懶加載插件。

1、寫在前面

因爲我在網上看了不少關於vue插件的實例,發現幾乎都沒有什麼詳細的教程,本身琢磨了半天也沒有什麼進步。都是寫的比較精簡。終於狠下心來,咱們來本身憋一個插件出來吧w(゚Д゚)w!此次咱們經過一個經常使用插件——懶加載,來體驗一下vue的插件開發。javascript

萌新小白,前端開發入門一年不到,歡迎交流,給我提出批評意見謝謝!!html

原創來源個人博客 歡迎交流,GitHub項目地址:vue-simple-lazyload上面是全部源碼,以爲對寫插件有幫助就點個star吧前端

2、前期準備

2.1 選擇合適的打包工具

合適的打包工具能夠達到事半功倍的效果。一開始個人首選有兩個,一個是webpack,一個是rollup。下面簡單介紹一下我爲何選擇了rollup。vue

衆所周知,webpack是一個幾乎囊括了全部靜態資源,能夠動態按需加載的一個包工具。而rollup也是一個模塊打包器,能夠把一個大塊複雜的代碼拆分紅各個小模塊。java

深思熟慮後,我以爲webpack也能夠打包,可是首先,有點「殺雞焉用牛刀」的感受。而個人這個懶加載插件則須要提供給別人使用,同時又要保證整個插件的「輕量性」(打包完大概6KB,而webpack則比較大),不喜歡像webpack那樣在這插件上臃腫的表現。node

對於非應用級的程序,我比較傾向於使用rollup.js。react

2.2 確認項目結構

|——package.json
|——config
|    |——rollup.config.js
|——dist
|    |——bundle.js
|——src
|    |——index.js
|    |——directive.js
|    |——mixin.js
|    |——imagebox.js
|    |——lazyload.js
|    |——utils
|    |    |——utils.js
|    |——cores
|        |——eventlistener.js

config文件夾下放置rollup的配置文件。src爲源文件夾,cores下面的文件夾爲主要的模塊,utils爲工具類,主要是一些能夠通用的模塊方法。大概的結構就是這樣。webpack

2.3 設計思路

好的設計思路是一個插件的靈魂,我以本身不在道上的設計能力,借鑑了許多大神的思想!很不自信地設計了懶加載的插件項目結構,見下:git

  • index.js:這個文件是入口總文件,主要爲了暴露vue插件的install方法,供外部調用。
  • directive.js:這個文件是vue指令的文件,懶加載主要使用到的就是指令,咱們這裏定義v-simple-lazy指令邏輯寫到這個文件內。
  • mixin.js:咱們的核心骨來了,這個混合(mixin)定義在vue中解釋得有點不清楚,多是我理解能力有問題吧(ಥ_ಥ)。咱們這裏簡單點說,混合就是vue的一些性質(好比雙綁)按照它給的規則,跟你本身的定義,能夠把你定義的一些變量,混入到vue實例中,也就是說,當作vue的$vm實例的變量來用,同時擁有了實例的一些特性。

上述都是關於vue插件的一些文件,下面咱們來講咱們本身定義的一些:github

  • imagebox.js(類):顧名思義,這個就是一個圖像的盒子(box),用來存儲你定義懶加載的圖片的一些預加載的地址和一些方法。咱們這裏設計5個數組用來存放,分別是:item,itemAlready,itemPending,itemFailed,itemAll,分別做用是:用來存放當前須要加載的圖片地址和元素已經加載完的圖片地址和元素正在加載中的圖片地址和元素加載失敗的圖片地址和元素全部綁定了指令的圖片地址和元素。原理咱們下面會說。
  • core的eventlistener.js(類):這個文件很重要,主要存放當咱們監聽滾動條的時候,須要處理的一些邏輯。
  • lazyload.js:這個主要用來存放一些不變的量,好比加載圖片的默認地址等等。

2.4 編寫思路和原理

原理以下:

經過監聽滾動條滾動,來不停地遍歷上述imagebox裏面的item數組(這個數組存放着須要懶加載的圖片預加載地址),若是item裏面有值,那麼就進行圖片的請求。進行請求的同時,咱們把這個元素加入到itemPending裏面去,若是加載完了就放到itemAlready裏面,失敗的放到failed裏面,這就是基本的實現思路。

懶加載的實現過程,咱們這裏先精簡化。具體思路以下:

=》把全部用指令綁定的元素添加數組初始化

=》監聽滾動條滾動

=》判斷元素是否進入可視範圍

=》若是進入可視範圍,進行src預加載(存入緩存數組)

=》對於pending的圖片,進行正在加載賦值,對於finsh完的圖片,加載預加載src裏面的值,對於error的圖片,進行錯誤圖片src賦值

3、主要代碼的編寫

3.1 確認入口文件

Vue插件裏面介紹是這樣的

MyPlugin.install = function (Vue, options) {
  // 1. 添加全局方法或屬性
  Vue.myGlobalMethod = function () {
    // 邏輯...
  }

  // 2. 添加全局資源
  Vue.directive('my-directive', {
    bind (el, binding, vnode, oldVnode) {
      // 邏輯...
    }
    ...
  })

  // 3. 注入組件
  Vue.mixin({
    created: function () {
      // 邏輯...
    }
    ...
  })

  // 4. 添加實例方法
  Vue.prototype.$myMethod = function (methodOptions) {
    // 邏輯...
  }
}

在外面暴露的方法就是install,使用的時候直接Vue.use("插件名稱")直接可使用。咱們在install方法裏面填寫關於指令(directive)和混合(mixin),而後對外公開這個方法,option沒填寫的話就是默認空對象。

混合主要是爲了混入vue內部屬性,是除了以上全局方法後又能夠在全局使用的一種方式。

3.2 先編寫配置文件

工欲善其事必先利其器,咱們先編寫rollup的配置代碼(這裏作了精簡,複雜的能夠參照個人github程序)。

rollup.config.js

import buble from 'rollup-plugin-buble';
import babel from 'rollup-plugin-babel';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';

export default {
  input: 'src/index.js',//入口
  output: {
    file: 'dist/bundle.js',//輸出的出口
    format: 'umd',//格式:類似的還有cjs,amd,iife等
  },
  moduleName: 'LazyLoad',//打包的模塊名稱,能夠再Vue.use()方法使用
  plugins:[
      resolve(),
      commonjs(),//支持commonJS
      buble(),
      babel({//關於ES6
          exclude: 'node_modules/**' // 只編譯咱們的源代碼
      })
  ]
};

package.json

{
  "name": "lazyload",
  "version": "1.0.0",
  "description": "vue懶加載插件",
  "main": "index.js",
  "scripts": {
    "main": "rollup -c config/rollup.config.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "TangHy",
  "license": "MIT",
  "dependencies": {
    "path": "^0.12.7",
    "rollup": "^0.57.1"
  },
  "devDependencies": {
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.4",
    "babel-preset-env": "^1.6.1",
    "babel-preset-react": "^6.24.1",
    "rollup-plugin-babel": "^3.0.3",
    "rollup-plugin-buble": "^0.19.2",
    "rollup-plugin-commonjs": "^9.1.0",
    "rollup-plugin-node-resolve": "^3.3.0"
  }
}

注意其中的命令:

rollup -c config/rollup.config.js

後面的config/...是路徑,這裏注意一下,咱們只須要運行

npm run main

就能夠進行打包了。

關於rollup不懂的地方或者使用,我有一篇博文也簡單介紹了從零開始學習Rollup.js的前端打包利器

3.3 編寫主程序代碼

3.3.1 directive.js

首先咱們要先畫好雛形,如何將eventlistener文件和directive聯繫在一塊兒?

第一步首先確定是引用eventlistener,同時由於它是一個類,咱們這裏只要指令每次inserted的時候,咱們都新new一個對象進行初始化,把能傳的值都傳過去。能夠看到下面的操做

import eventlistener from './cores/eventlistener'

var listener = null;

export default {

    inserted: function (el,binding, vnode, oldVnode) {
        var EventListener = new eventlistener(el,binding, vnode);//這裏咱們new一個新對象,把el(元素),binding(綁定的值),vnode(虛擬node)都傳過去
        listener = EventListener;
        EventListener.init();//假設有一個init初始化函數
        EventListener.startListen();//這裏初始化完進行監聽
  },
    update: function(el,{name,value,oldValue,expression}, vnode, oldVnode){

    },
    unbind: function(){

    }
    
}

其次咱們要考慮在update鉤子中,咱們須要幹什麼?

有這樣一種業務:當你一次性綁定完全部數據的時候,若是這個圖片已經預加載完了,那麼你再怎麼改變這個指令綁定的值,都不可以實現刷新圖片了,因此咱們在update更新新的圖片地址是有必要的。同時解綁的時候,取消綁定也是有必要的,那麼繼續往下寫:

import eventlistener from './cores/eventlistener'

var listener = null;

export default {

    inserted: function (el,binding, vnode, oldVnode) {
        var EventListener = new eventlistener(el,binding, vnode);//這裏咱們new一個新對象,把el(元素),binding(綁定的值),vnode(虛擬node)都傳過去
        listener = EventListener;
        EventListener.init();//假設有一個init初始化函數
        EventListener.startListen();//這裏初始化完進行監聽
  },
    update: function(el,{name,value,oldValue,expression}, vnode, oldVnode){
        if(value === oldValue){//沒有變化就返回
            return;
        }
        listener.update(el,value);//有變化就進行更新(假設有update這個方法)
    },
    unbind: function(){
        listener.removeListen();//解綁移除監聽
    }
    
}

先寫生命週期中inserted的時候綁定監聽。加入的時候new一個監聽對象保存全部包括全部dom的。咱們繼續往下看。

3.4 核心代碼

3.4.1 imagebox.js類

首先先把以前說到的幾個數組都初始化了。那麼做爲圖像盒子(imagebox)的對象實例,咱們須要哪些方法或屬性呢?

首先初始化的時候的add方法確定要,須要判斷一下是否有這個元素,沒有的話就加入到item裏面去。同時相似的還有addFailed,addPending等方法。

若是item中的元素加載完了,那麼隨之而來的就須要刪除item中的元素,那麼對應的remove方法也是必需要的,同時相似的還有removePending等方法。

export default class ImageBox {
  constructor() {
    this.eleAll = [];
    this.item = [];
    this.itemAlready = [];
    this.itemPending = [];
    this.itemFailed = [];
  }

  add(ele,src) {//insert插入的時候把全部的dom加入到數組中去初始化
      const index = this.itemAlready.findIndex((_item)=>{
          return _item.ele === ele;
      })
      if(index === -1){
          this.item.push({
            ele:ele,
            src:src
        })
      }
  }
    
  addPending(ele,src){
      this._addPending(ele,src);
      this._remove(ele);
  }
}

上述是一個圖片的box,用於存取頁面加載時候,image圖片對象的box存取。主要思路是分了三個數組,一個存儲全部的圖片一個存儲正在加載的圖片一個存儲加載失敗的圖片,而後最重要的是!!!

把這個imagebox要混入到全局,使其能夠當作全局變量在全局使用

mixin.js
import imagebox from './imagebox'

const mixin = {
    data () {
      return {
          imagebox: new imagebox()//這裏聲明一個new對象,存在全局的變量中,混入vue內部,能夠全局使用
      }
  }
}

export default mixin;

下所示代碼中:

  1. 構造器中初始化各類元素數組,包括全部圖片,已加載的圖片,正在請求的圖片,失敗的圖片等等
  2. add方法是在item數組中添加圖片元素
  3. addPending是在正在請求的圖片數組中添加元素的方法,相似的還有addFailed,addAlready,_remove等私有和共有方法
  4. util是各類工具類方法,包括判斷圖片是否進入視野

根據上述思路,完成下列代碼:

(補充:這裏有個update方法,思路是更新了後,進行全部的數組遍歷,找到相對應的元素,而後進行src就是其值的更新)

export default class ImageBox {
  constructor() {
    this.eleAll = [];
    this.item = [];
    this.itemAlready = [];
    this.itemPending = [];
    this.itemFailed = [];
  }

  add(ele,src) {
      const index = this.itemAlready.findIndex((_item)=>{
          return _item.ele === ele;
      })
      if(index === -1){
          this.item.push({
            ele:ele,
            src:src
        })
      }
  }

  update(ele,src){
    let index = this.itemAlready.findIndex(item=>{
      return item.ele === ele;
    });

    if(index != -1){
      this.itemAlready.splice(index,1);
      this.add(ele,src);
      return;
    };

    let _index = this.itemFailed.findIndex(item=>{
      return item.ele === ele;
    });

    if(_index !=-1){
      this.itemFailed.splice(_index,1);
      this.add(ele,src);
      return;
    };

  }

  addFailed(ele,src){
      this._addFailed(ele,src);
      this._removeFromPending(ele);
  }

  addPending(ele,src){
      this._addPending(ele,src);
      this._remove(ele);
  }

  addAlready(ele,src){
      this._addAlready(ele,src);
      this._removeFromPending(ele);
  }

  _addAlready(ele,src) {
      const index = this.itemAlready.findIndex((_item)=>{
          return _item.ele === ele;
      })
      if(index === -1){
          this.itemAlready.push({
            ele:ele,
            src:src
        })
      }
  }

  _addPending(ele,src) {
      const index = this.itemPending.findIndex((_item)=>{
          return _item.ele === ele;
      })
      if(index === -1){
          this.itemPending.push({
            ele:ele,
            src:src
        })
      }
  }

  _addFailed(ele,src) {
      const index = this.itemFailed.findIndex((_item)=>{
          return _item.ele === ele;
      })
      if(index === -1){
          this.itemFailed.push({
            ele:ele,
            src:src
        })
      }
  }

  _remove(ele) {
      const index = this.item.findIndex((_item)=>{
          return _item.ele === ele;
      });
      if(index!=-1){
          this.item.splice(index,1);
      }
  }

  _removeFromPending(ele) {
      const index = this.itemPending.findIndex((_item)=>{
          return _item.ele === ele;
      });
      if(index!=-1){
          this.itemPending.splice(index,1);
      }
  }
}
3.4.2 utils.js
const isSeen = function(item,imagebox){
  var ele = item.ele;
  var src = item.src;
  //圖片距離頁面頂部的距離
  var top = ele.getBoundingClientRect().top;
  //頁面可視區域的高度
  var windowHeight = document.documentElement.clientHeight || document.body.clientHeight;
  //top + 10 已經進入了可視區域10像素
  if(top + 10 < windowHeight){
      return true;
  }else{
      return false;
  }
}

export {
  isSeen
};
3.4.3 eventlistener.js

這個文件主要是監聽的一些邏輯,那麼確定須要一些對象實例的屬性。首先el元素確定須要,binding,vnode,$vm確定都先寫進來。

其次imagebox確定也須要,是圖片的對象實例。

init的方法這裏和imagebox中的add方法聯繫起來,init一個就加入imagebox中的item一個新的元素。

startListen方法是用於在監聽後進行邏輯操做。

import {isSeen} from '../utils/utils'//引入工具類的裏面的是否看得見元素這個方法判斷

export default class EventListener {
  constructor(el,binding,vnode) {
    this.el = el;//初始化各類須要的屬性
    this.binding = binding;
    this.vnode = vnode;
    this.imagebox = null;
    this.$vm = vnode.context;
    this.$lazyload = vnode.context.$lazyload//混合mixin進去的選項
  }

  init(){
      if(!typeof this.binding.value === 'string'){
      throw new Error("您的圖片源不是String類型,請重試");
      return;
    }
      this.imagebox = this.vnode.context.imagebox;
    this.imagebox.add(this.el,this.binding.value);//每有一個item,就往box中增長一個新的元素
    this.listenProcess();
  }

  startListen(){
      const _self = this;
      document.addEventListener('scroll',(e)=>{
          _self.listenProcess(e);//這裏開始操做
      })
  }
}

上面主要初始化了不少屬性,包括vue的虛擬dom和各類包括el元素dom,binding指令傳過來的值等等初始化。

此文件主要爲了處理監聽頁面滾動的,監聽是否圖片進入到可視範圍內,而後進行一系列下方的各類操做。

根據image.onload,image.onerror方法進行圖片預加載的邏輯操做,若是看得見這個圖片,那麼就進行圖片的加載(同時加入到pending裏面去),加載完進行判斷。

下列是process的函數listenProcess

const _self = this;
if(this.imagebox.item.length == 0){
    return;
};
this.imagebox.item.forEach((item)=>{
    if(isSeen(item)){//這裏判斷元素是否看得見
        var image = new Image();//這裏在賦值src前new一個image對象進行緩存,緩衝一下,能夠作後續的加載或失敗的函數處理
        image.src = item.src;
        _self._imageStyle(item);//改變item的樣式

        _self.imagebox.addPending(item.ele,item.src);//在對象imagebox中加入了正在pending請求的item(後續會介紹imagebox類)

        image.onload = function(){//加載成功的處理
            if(image.complete){
                _self.imageOnload(item);
            }
        }

        image.onerror = function(){//加載失敗的處理
            _self.imageOnerror(item);
        }
    }
})

還有其他的一些方法:

imageOnload(item){//圖片加載完的操做
      this._removeImageStyle(item.ele);
    this.imagebox.addAlready(item.ele,item.src);//添加到已經加載完的item數組裏面
    this._imageSet(item.ele,item.src)
  }

  imageOnerror(item){//出現錯誤的時候
      this._removeImageStyle(item.ele);
    this.imagebox.addFailed(item.ele,item.src);//添加到出現錯誤item數組裏面
    this._imageSet(item.ele,this.$lazyload.options.errorUrl)//把配置中的錯誤圖片url填入
  }

  _imageStyle(item){
    item.ele.style.background = `url(${this.$lazyload.options.loadUrl}) no-repeat center`;
  }

  _removeImageStyle(ele){
      ele.style.background = '';
  }

  _imageSet(ele,value){//關於圖片賦值src的操做
    ele.src = value;
  }

補充一個update方法:

update(ele,src){
    console.log("更新了");
    console.log(this.imagebox);
    this.imagebox.update(ele,src);//調用imagebox中的update方法
    this.listenProcess();//再進行是否看得見的process操做
}

下面是全部的eventlistener.js代碼:

import {isSeen} from '../utils/utils'

export default class EventListener {
  constructor(el,binding,vnode) {
    this.el = el;
    this.binding = binding;
    this.vnode = vnode;
    this.imagebox = null;
    this.$vm = vnode.context;
    this.$lazyload = vnode.context.$lazyload
  }
  //綁定初始化
  init(){
    if(!typeof this.binding.value === 'string'){
      throw new Error("您的圖片源不是String類型,請重試");
      return;
    }
      this.imagebox = this.vnode.context.imagebox;
    this.imagebox.add(this.el,this.binding.value);
    this.listenProcess();
  }
  //開始監聽
  startListen(){
    var listenProcess = this.listenProcess;
      document.addEventListener('scroll',listenProcess.bind(this),false);
  }
  //移除監聽
  removeListen(){
    var listenProcess = this.listenProcess;
    document.removeEventListener('scroll',listenProcess.bind(this),false);
  }
  //監聽的操做函數,包括判斷image的box進行請求等
  listenProcess(){
      const _self = this;
    if(this.imagebox.item.length == 0){
      return;
    };

      this.imagebox.item.forEach((item)=>{
            if(isSeen(item)){
          var image = new Image();
          image.src = item.src;
          _self._imageStyle(item);
          
          _self.imagebox.addPending(item.ele,item.src);

          image.onload = function(){
            if(image.complete){
                _self.imageOnload(item);
            }
          }

          image.onerror = function(){
            _self.imageOnerror(item);
          }
            }
        })
  }
  //進行最新圖片地址的更新
  update(ele,src){
    console.log("更新了");
    console.log(this.imagebox);
    this.imagebox.update(ele,src);
    this.listenProcess();
  }
  //具體得圖片加載的操做
  imageOnload(item){
      this._removeImageStyle(item.ele);
    this.imagebox.addAlready(item.ele,item.src);
    this._imageSet(item.ele,item.src)
  }
  //圖片加載錯誤的操做
  imageOnerror(item){
      this._removeImageStyle(item.ele);
    this.imagebox.addFailed(item.ele,item.src);
    this._imageSet(item.ele,this.$lazyload.options.errorUrl)
  }
  //加載圖片地址的賦值
  _imageStyle(item){
    item.ele.style.background = `url(${this.$lazyload.options.loadUrl}) no-repeat center`;
  }
  //移除加載圖片的地址
  _removeImageStyle(ele){
      ele.style.background = '';
  }
  //對圖片進行賦值
  _imageSet(ele,value){
    ele.src = value;
  }
}

全部的解釋都已經寫在上面的代碼塊裏面了。

3.4.4 lazyload.js

最後把一些加載的圖片默認配置或者失敗圖片地址完成下列代碼:

const DEFAULT_ERROR_URL = './404.svg';
const DEFAULT_LOAD_URL = './loading-spin.svg';

export default class LazyLoad {
    constructor() {
    this.options = {
        loadUrl: DEFAULT_LOAD_URL,
        errorUrl: DEFAULT_ERROR_URL
    };
  }

  register(options){
      Object.assign(this.options, options);
  }
}

此類暫時用來存儲各類配置和lazy的預約默認值,options裏面存加載的時候的圖片地址和錯誤加載的時候的圖片地址。

默認值是最上面兩個值,是不傳數據默認的配置。

3.4.5 index.js
import directive from './directive';
import mixin from './mixin';
import lazyload from './lazyload';

const install = ( Vue,options = {} )=>{
    const lazy = new lazyload();
    lazy.register(options);

    Vue.prototype.$lazyload = lazy

    Vue.mixin(mixin);
    
    Vue.directive('simple-lazy',directive);

}

export default {
    install
};

把上述全部的進行一個綜合,放在這個入口文件進行向外暴露。

index就是整個項目的入口文件,至此咱們完成了懶加載插件的基本代碼編寫。

4、打包成.js能夠外部直接引用

打包後的代碼:

(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global.LazyLoad = factory());
}(this, (function () { 'use strict';

  var isSeen = function isSeen(item, imagebox) {
    var ele = item.ele;
    var src = item.src;
    //圖片距離頁面頂部的距離
    var top = ele.getBoundingClientRect().top;
    //頁面可視區域的高度
    var windowHeight = document.documentElement.clientHeight || document.body.clientHeight;
    //top + 10 已經進入了可視區域10像素
    if (top + 10 < windowHeight) {
      return true;
    } else {
      return false;
    }
  };

  var EventListener = function EventListener(el, binding, vnode) {
    this.el = el;
    this.binding = binding;
    this.vnode = vnode;
    this.imagebox = null;
    this.$vm = vnode.context;
    this.$lazyload = vnode.context.$lazyload;
  };

  EventListener.prototype.init = function init() {
    this.imagebox = this.vnode.context.imagebox;
    this.imagebox.add(this.el, this.binding.value);
    this.listenProcess();
  };

  EventListener.prototype.startListen = function startListen() {
    var listenProcess = this.listenProcess;
    window.addEventListener('scroll', listenProcess.bind(this), false);
  };

  EventListener.prototype.removeListen = function removeListen() {
    var listenProcess = this.listenProcess;
    window.removeEventListener('scroll', listenProcess.bind(this), false);
  };

  EventListener.prototype.listenProcess = function listenProcess() {
    var _self = this;
    if (this.imagebox.item.length == 0) {
      return;
    }
    this.imagebox.item.forEach(function (item) {
      if (isSeen(item)) {

        var image = new Image();
        image.src = item.src;
        _self._imageStyle(item);

        _self.imagebox.addPending(item.ele, item.src);

        image.onload = function () {
          if (image.complete) {
            _self.imageOnload(item);
          }
        };

        image.onerror = function () {
          _self.imageOnerror(item);
        };
      }
    });
  };

  EventListener.prototype.update = function update(ele, src) {
    console.log("更新了");
    console.log(this.imagebox);
    this.imagebox.update(ele, src);
    this.listenProcess();
  };

  EventListener.prototype.imageOnload = function imageOnload(item) {
    this._removeImageStyle(item.ele);
    this.imagebox.addAlready(item.ele, item.src);
    this._imageSet(item.ele, item.src);
  };

  EventListener.prototype.imageOnerror = function imageOnerror(item) {
    this._removeImageStyle(item.ele);
    this.imagebox.addFailed(item.ele, item.src);
    this._imageSet(item.ele, this.$lazyload.options.errorUrl);
  };

  EventListener.prototype._imageStyle = function _imageStyle(item) {
    item.ele.style.background = "url(" + this.$lazyload.options.loadUrl + ") no-repeat center";
  };

  EventListener.prototype._removeImageStyle = function _removeImageStyle(ele) {
    ele.style.background = '';
  };

  EventListener.prototype._imageSet = function _imageSet(ele, value) {
    ele.src = value;
  };

  var listener = null;

  var directive = {

      inserted: function inserted(el, binding, vnode, oldVnode) {
          var EventListener$$1 = new EventListener(el, binding, vnode);
          listener = EventListener$$1;
          EventListener$$1.init();
          EventListener$$1.startListen();
      },
      update: function update(el, ref, vnode, oldVnode) {
          var name = ref.name;
          var value = ref.value;
          var oldValue = ref.oldValue;
          var expression = ref.expression;

          if (value === oldValue) {
              return;
          }
          listener.update(el, value);
      },
      unbind: function unbind() {
          listener.removeListen();
      }

  };

  var ImageBox = function ImageBox() {
    this.eleAll = [];
    this.item = [];
    this.itemAlready = [];
    this.itemPending = [];
    this.itemFailed = [];
  };

  ImageBox.prototype.add = function add(ele, src) {
    var index = this.itemAlready.findIndex(function (_item) {
      return _item.ele === ele;
    });
    if (index === -1) {
      this.item.push({
        ele: ele,
        src: src
      });
    }
  };

  ImageBox.prototype.update = function update(ele, src) {
    var index = this.itemAlready.findIndex(function (item) {
      return item.ele === ele;
    });

    if (index != -1) {
      this.itemAlready.splice(index, 1);
      this.add(ele, src);
      return;
    }
    var _index = this.itemFailed.findIndex(function (item) {
      return item.ele === ele;
    });

    if (_index != -1) {
      this.itemFailed.splice(_index, 1);
      this.add(ele, src);
      return;
    }};

  ImageBox.prototype.addFailed = function addFailed(ele, src) {
    this._addFailed(ele, src);
    this._removeFromPending(ele);
  };

  ImageBox.prototype.addPending = function addPending(ele, src) {
    this._addPending(ele, src);
    this._remove(ele);
  };

  ImageBox.prototype.addAlready = function addAlready(ele, src) {
    this._addAlready(ele, src);
    this._removeFromPending(ele);
  };

  ImageBox.prototype._addAlready = function _addAlready(ele, src) {
    var index = this.itemAlready.findIndex(function (_item) {
      return _item.ele === ele;
    });
    if (index === -1) {
      this.itemAlready.push({
        ele: ele,
        src: src
      });
    }
  };

  ImageBox.prototype._addPending = function _addPending(ele, src) {
    var index = this.itemPending.findIndex(function (_item) {
      return _item.ele === ele;
    });
    if (index === -1) {
      this.itemPending.push({
        ele: ele,
        src: src
      });
    }
  };

  ImageBox.prototype._addFailed = function _addFailed(ele, src) {
    var index = this.itemFailed.findIndex(function (_item) {
      return _item.ele === ele;
    });
    if (index === -1) {
      this.itemFailed.push({
        ele: ele,
        src: src
      });
    }
  };

  ImageBox.prototype._remove = function _remove(ele) {
    var index = this.item.findIndex(function (_item) {
      return _item.ele === ele;
    });
    if (index != -1) {
      this.item.splice(index, 1);
    }
  };

  ImageBox.prototype._removeFromPending = function _removeFromPending(ele) {
    var index = this.itemPending.findIndex(function (_item) {
      return _item.ele === ele;
    });
    if (index != -1) {
      this.itemPending.splice(index, 1);
    }
  };

  var mixin = {
      data: function data() {
          return {
              imagebox: new ImageBox()
          };
      }
  };

  var DEFAULT_ERROR_URL = './404.svg';
  var DEFAULT_LOAD_URL = './loading-spin.svg';

  var LazyLoad = function LazyLoad() {
    this.options = {
      loadUrl: DEFAULT_LOAD_URL,
      errorUrl: DEFAULT_ERROR_URL
    };
  };

  LazyLoad.prototype.register = function register(options) {
    Object.assign(this.options, options);
  };

  var install = function install(Vue, options) {
      if (options === void 0) options = {};

      var lazy = new LazyLoad();
      lazy.register(options);

      Vue.prototype.$lazyload = lazy;

      Vue.mixin(mixin);

      Vue.directive('simple-lazy', directive);
  };

  var index = {
      install: install
  };

  return index;

})));

使用方法

Vue.use(LazyLoad,{
    loadUrl:'./loading-spin.svg',//這裏寫你的加載時候的圖片配置
    errorUrl:'./404.svg'//錯誤加載的圖片配置
});

元素中使用指令

<img v-simple-lazy="item" v-for="(item,$key) in imageArr">

imageArr測試數據

imageArr:[
    'http://covteam.u.qiniudn.com/test16.jpg?imageView2/2/format/webp',
    'http://covteam.u.qiniudn.com/test14.jpg?imageView2/2/format/webp',
    'http://covteam.u.qiniudn.com/test15.jpg?imageView2/2/format/webp',
    'http://covteam.u.qiniudn.com/test17.jpg?imageView2/2/format/webp',
    'http://hilongjw.github.io/vue-lazyload/dist/test9.jpg',
    'http://hilongjw.github.io/vue-lazyload/dist/test10.jpg',
    'http://hilongjw.github.io/vue-lazyload/dist/test14.jpg'
]

測試地址:戳我戳我

5、後記

其實這些代碼的編寫仍是比較簡單的,寫完事後進行總結,你會發現,其中最難的是:

整個項目的結構,和代碼模塊之間的邏輯關係

這個纔是最難掌握的,若是涉及到大一點的項目,好一點的項目結構能讓整個項目進度等等因素髮生巨大的變化,提高巨大的效率。而寫插件最難的就是在這。

如何有效地拆分代碼?如何有效地進行項目結構的構造? 這纔是整個插件編寫的核心。

以前寫過一個vue關於表單驗證的插件,也是被項目結構搞得焦頭爛額,這裏把簡單的懶加載基本代碼作一個總結。寫這個純粹是我的興趣。但願能夠給入門的插件開發新人給予一點點幫助。

因此我深知寫插件的時候,它結構和模塊化的重要性。而結構和模塊化的優秀,會讓你事半功倍。另外歡迎你們來個人博客參觀唐益達的博客,只寫原創。

萌新小白,前端開發入門一年不到,歡迎交流!

相關文章
相關標籤/搜索