Vue外掛開發初體驗——(懶載入)
前言
閒來無事,想自己開發一個簡單的Vue懶載入外掛,能力的提升我覺得是可以通過編寫外掛實現,研究了一下官網的Vue外掛編寫。馬上自己獨立開始編寫懶載入外掛。
一、寫在前面
由於我在網上看了很多關於vue外掛的例項,發現幾乎都沒有什麼詳細的教程,自己琢磨了半天也沒有什麼進步。都是寫的比較精簡。終於狠下心來,我們來自己憋一個外掛出來吧w(゚Д゚)w!這次我們通過一個常用外掛——懶載入,來體驗一下vue的外掛開發。
萌新小白,前端開發入門一年不到,歡迎交流,給我提出批評意見謝謝!!
(原創來源我的部落格 歡迎交流,GitHub專案地址:vue-simple-lazyload上面是所有原始碼,覺得對寫外掛有幫助就點個star吧)
二、前期準備
2.1 選擇合適的打包工具
合適的打包工具可以達到事半功倍的效果。一開始我的首選有兩個,一個是webpack,一個是rollup。下面簡單介紹一下我為什麼選擇了rollup。
眾所周知,webpack是一個幾乎囊括了所有靜態資源,可以動態按需載入的一個包工具。而rollup也是一個模組打包器,可以把一個大塊複雜的程式碼拆分成各個小模組。
深思熟慮後,我覺得webpack也可以打包,但是首先,有點“殺雞焉用牛刀”的感覺。而我的這個懶載入外掛則需要提供給別人使用,同時又要保證整個外掛的“輕量性”(打包完大概6KB,而webpack則比較大),不喜歡像webpack那樣在這外掛上臃腫的表現。
對於非應用級的程式,我比較傾向於使用rollup.js。
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為工具類,主要是一些可以通用的模組方法。大概的結構就是這樣。
2.3 設計思路
好的設計思路是一個外掛的靈魂,我以自己不在道上的設計能力,借鑑了許多大神的思想!很不自信地設計了懶載入的外掛專案結構,見下:
- index.js:這個檔案是入口總檔案,主要為了暴露vue外掛的install方法,供外部呼叫。
- directive.js:這個檔案是vue指令的檔案,懶載入主要使用到的就是指令,我們這裡定義v-simple-lazy指令邏輯寫到這個檔案內。
- mixin.js:我們的核心骨來了,這個混合(mixin)定義在vue中解釋得有點不清楚,可能是我理解能力有問題吧(ಥ_ಥ)。我們這裡簡單點說,混合就是vue的一些性質(比如雙綁)按照它給的規則,跟你自己的定義,可以把你定義的一些變數,混入到vue例項中,也就是說,當做vue的$vm例項的變數來用,同時擁有了例項的一些特性。
上述都是關於vue外掛的一些檔案,下面我們來說我們自己定義的一些:
- 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.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;
下所示程式碼中:
- 構造器中初始化各種元素陣列,包括所有圖片,已載入的圖片,正在請求的圖片,失敗的圖片等等
- add方法是在item陣列中新增圖片元素
- addPending是在正在請求的圖片陣列中新增元素的方法,類似的還有addFailed,addAlready,_remove等私有和共有方法
- 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就是整個專案的入口檔案,至此我們完成了懶載入外掛的基本程式碼編寫。
四、打包成.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`
]
測試地址:戳我戳我
五、後記
其實這些程式碼的編寫還是比較簡單的,寫完過後進行總結,你會發現,其中最難的是:
整個專案的結構,和程式碼模組之間的邏輯關係。
這個才是最難掌握的,如果涉及到大一點的專案,好一點的專案結構能讓整個專案進度等等因素髮生巨大的變化,提升巨大的效率。而寫外掛最難的就是在這。
如何有效地拆分程式碼?如何有效地進行專案結構的構造? 這才是整個外掛編寫的核心。
之前寫過一個vue關於表單驗證的外掛,也是被專案結構搞得焦頭爛額,這裡把簡單的懶載入基本程式碼做一個總結。寫這個純粹是個人興趣。希望可以給入門的外掛開發新人給予一點點幫助。
所以我深知寫外掛的時候,它結構和模組化的重要性。而結構和模組化的優秀,會讓你事半功倍。另外歡迎大家來我的部落格參觀唐益達的部落格,只寫原創。
- 原創來源我的部落格 http://www.tangyida.top/detai… 歡迎交流。
- GitHub專案地址:https://github.com/xdnloveme/…
萌新小白,前端開發入門一年不到,歡迎交流!