摘要:插件開發(fā)初體驗(yàn)懶加載前言閑來無事,想自己開發(fā)一個(gè)簡單的懶加載插件,能力的提升我覺得是可以通過編寫插件實(shí)現(xiàn),研究了一下官網(wǎng)的插件編寫。
Vue插件開發(fā)初體驗(yàn)——(懶加載) 前言
閑來無事,想自己開發(fā)一個(gè)簡單的Vue懶加載插件,能力的提升我覺得是可以通過編寫插件實(shí)現(xiàn),研究了一下官網(wǎng)的Vue插件編寫。馬上自己獨(dú)立開始編寫懶加載插件。一、寫在前面
由于我在網(wǎng)上看了很多關(guān)于vue插件的實(shí)例,發(fā)現(xiàn)幾乎都沒有什么詳細(xì)的教程,自己琢磨了半天也沒有什么進(jìn)步。都是寫的比較精簡。終于狠下心來,我們來自己憋一個(gè)插件出來吧w(?Д?)w!這次我們通過一個(gè)常用插件——懶加載,來體驗(yàn)一下vue的插件開發(fā)。
萌新小白,前端開發(fā)入門一年不到,歡迎交流,給我提出批評(píng)意見謝謝!!
(原創(chuàng)來源我的博客 歡迎交流,GitHub項(xiàng)目地址:vue-simple-lazyload上面是所有源碼,覺得對(duì)寫插件有幫助就點(diǎn)個(gè)star吧)
二、前期準(zhǔn)備 2.1 選擇合適的打包工具合適的打包工具可以達(dá)到事半功倍的效果。一開始我的首選有兩個(gè),一個(gè)是webpack,一個(gè)是rollup。下面簡單介紹一下我為什么選擇了rollup。
眾所周知,webpack是一個(gè)幾乎囊括了所有靜態(tài)資源,可以動(dòng)態(tài)按需加載的一個(gè)包工具。而rollup也是一個(gè)模塊打包器,可以把一個(gè)大塊復(fù)雜的代碼拆分成各個(gè)小模塊。
深思熟慮后,我覺得webpack也可以打包,但是首先,有點(diǎn)“殺雞焉用牛刀”的感覺。而我的這個(gè)懶加載插件則需要提供給別人使用,同時(shí)又要保證整個(gè)插件的“輕量性”(打包完大概6KB,而webpack則比較大),不喜歡像webpack那樣在這插件上臃腫的表現(xiàn)。
對(duì)于非應(yīng)用級(jí)的程序,我比較傾向于使用rollup.js。
2.2 確認(rèn)項(xiàng)目結(jié)構(gòu)|——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為工具類,主要是一些可以通用的模塊方法。大概的結(jié)構(gòu)就是這樣。
2.3 設(shè)計(jì)思路好的設(shè)計(jì)思路是一個(gè)插件的靈魂,我以自己不在道上的設(shè)計(jì)能力,借鑒了許多大神的思想!很不自信地設(shè)計(jì)了懶加載的插件項(xiàng)目結(jié)構(gòu),見下:
index.js:這個(gè)文件是入口總文件,主要為了暴露vue插件的install方法,供外部調(diào)用。
directive.js:這個(gè)文件是vue指令的文件,懶加載主要使用到的就是指令,我們這里定義v-simple-lazy指令邏輯寫到這個(gè)文件內(nèi)。
mixin.js:我們的核心骨來了,這個(gè)混合(mixin)定義在vue中解釋得有點(diǎn)不清楚,可能是我理解能力有問題吧(?_?)。我們這里簡單點(diǎn)說,混合就是vue的一些性質(zhì)(比如雙綁)按照它給的規(guī)則,跟你自己的定義,可以把你定義的一些變量,混入到vue實(shí)例中,也就是說,當(dāng)做vue的$vm實(shí)例的變量來用,同時(shí)擁有了實(shí)例的一些特性。
上述都是關(guān)于vue插件的一些文件,下面我們來說我們自己定義的一些:
imagebox.js(類):顧名思義,這個(gè)就是一個(gè)圖像的盒子(box),用來存儲(chǔ)你定義懶加載的圖片的一些預(yù)加載的地址和一些方法。我們這里設(shè)計(jì)5個(gè)數(shù)組用來存放,分別是:item,itemAlready,itemPending,itemFailed,itemAll,分別作用是:用來存放當(dāng)前需要加載的圖片地址和元素,已經(jīng)加載完的圖片地址和元素,正在加載中的圖片地址和元素,加載失敗的圖片地址和元素,所有綁定了指令的圖片地址和元素。原理我們下面會(huì)說。
core的eventlistener.js(類):這個(gè)文件很重要,主要存放當(dāng)我們監(jiān)聽滾動(dòng)條的時(shí)候,需要處理的一些邏輯。
lazyload.js:這個(gè)主要用來存放一些不變的量,比如加載圖片的默認(rèn)地址等等。
2.4 編寫思路和原理原理如下:
通過監(jiān)聽滾動(dòng)條滾動(dòng),來不停地遍歷上述imagebox里面的item數(shù)組(這個(gè)數(shù)組存放著需要懶加載的圖片預(yù)加載地址),如果item里面有值,那么就進(jìn)行圖片的請求。進(jìn)行請求的同時(shí),我們把這個(gè)元素加入到itemPending里面去,如果加載完了就放到itemAlready里面,失敗的放到failed里面,這就是基本的實(shí)現(xiàn)思路。
懶加載的實(shí)現(xiàn)過程,我們這里先精簡化。具體思路如下:
=》把所有用指令綁定的元素添加數(shù)組初始化
=》監(jiān)聽滾動(dòng)條滾動(dòng)
=》判斷元素是否進(jìn)入可視范圍
=》如果進(jìn)入可視范圍,進(jìn)行src預(yù)加載(存入緩存數(shù)組)
=》對(duì)于pending的圖片,進(jìn)行正在加載賦值,對(duì)于finsh完的圖片,加載預(yù)加載src里面的值,對(duì)于error的圖片,進(jìn)行錯(cuò)誤圖片src賦值
三、主要代碼的編寫 3.1 確認(rèn)入口文件Vue插件里面介紹是這樣的
MyPlugin.install = function (Vue, options) { // 1. 添加全局方法或?qū)傩? Vue.myGlobalMethod = function () { // 邏輯... } // 2. 添加全局資源 Vue.directive("my-directive", { bind (el, binding, vnode, oldVnode) { // 邏輯... } ... }) // 3. 注入組件 Vue.mixin({ created: function () { // 邏輯... } ... }) // 4. 添加實(shí)例方法 Vue.prototype.$myMethod = function (methodOptions) { // 邏輯... } }
在外面暴露的方法就是install,使用的時(shí)候直接Vue.use("插件名稱")直接可以使用。我們在install方法里面填寫關(guān)于指令(directive)和混合(mixin),然后對(duì)外公開這個(gè)方法,option沒填寫的話就是默認(rèn)空對(duì)象。
混合主要是為了混入vue內(nèi)部屬性,是除了以上全局方法后又可以在全局使用的一種方式。
3.2 先編寫配置文件工欲善其事必先利其器,我們先編寫rollup的配置代碼(這里做了精簡,復(fù)雜的可以參照我的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({//關(guān)于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/...是路徑,這里注意一下,我們只需要運(yùn)行
npm run main
就可以進(jìn)行打包了。
關(guān)于rollup不懂的地方或者使用,我有一篇博文也簡單介紹了從零開始學(xué)習(xí)Rollup.js的前端打包利器
3.3 編寫主程序代碼首先我們要先畫好雛形,如何將eventlistener文件和directive聯(lián)系在一起?
第一步首先肯定是引用eventlistener,同時(shí)因?yàn)樗且粋€(gè)類,我們這里只要指令每次inserted的時(shí)候,我們都新new一個(gè)對(duì)象進(jìn)行初始化,把能傳的值都傳過去。可以看到下面的操作
import eventlistener from "./cores/eventlistener" var listener = null; export default { inserted: function (el,binding, vnode, oldVnode) { var EventListener = new eventlistener(el,binding, vnode);//這里我們new一個(gè)新對(duì)象,把el(元素),binding(綁定的值),vnode(虛擬node)都傳過去 listener = EventListener; EventListener.init();//假設(shè)有一個(gè)init初始化函數(shù) EventListener.startListen();//這里初始化完進(jìn)行監(jiān)聽 }, update: function(el,{name,value,oldValue,expression}, vnode, oldVnode){ }, unbind: function(){ } }
其次我們要考慮在update鉤子中,我們需要干什么?
有這樣一種業(yè)務(wù):當(dāng)你一次性綁定完所有數(shù)據(jù)的時(shí)候,如果這個(gè)圖片已經(jīng)預(yù)加載完了,那么你再怎么改變這個(gè)指令綁定的值,都不能夠?qū)崿F(xiàn)刷新圖片了,所以我們在update更新新的圖片地址是有必要的。同時(shí)解綁的時(shí)候,取消綁定也是有必要的,那么繼續(xù)往下寫:
import eventlistener from "./cores/eventlistener" var listener = null; export default { inserted: function (el,binding, vnode, oldVnode) { var EventListener = new eventlistener(el,binding, vnode);//這里我們new一個(gè)新對(duì)象,把el(元素),binding(綁定的值),vnode(虛擬node)都傳過去 listener = EventListener; EventListener.init();//假設(shè)有一個(gè)init初始化函數(shù) EventListener.startListen();//這里初始化完進(jìn)行監(jiān)聽 }, update: function(el,{name,value,oldValue,expression}, vnode, oldVnode){ if(value === oldValue){//沒有變化就返回 return; } listener.update(el,value);//有變化就進(jìn)行更新(假設(shè)有update這個(gè)方法) }, unbind: function(){ listener.removeListen();//解綁移除監(jiān)聽 } }
先寫生命周期中inserted的時(shí)候綁定監(jiān)聽。加入的時(shí)候new一個(gè)監(jiān)聽對(duì)象保存所有包括所有dom的。我們繼續(xù)往下看。
3.4 核心代碼首先先把之前說到的幾個(gè)數(shù)組都初始化了。那么作為圖像盒子(imagebox)的對(duì)象實(shí)例,我們需要哪些方法或?qū)傩阅兀?/p>
首先初始化的時(shí)候的add方法肯定要,需要判斷一下是否有這個(gè)元素,沒有的話就加入到item里面去。同時(shí)類似的還有addFailed,addPending等方法。
如果item中的元素加載完了,那么隨之而來的就需要?jiǎng)h除item中的元素,那么對(duì)應(yīng)的remove方法也是必須要的,同時(shí)類似的還有removePending等方法。
export default class ImageBox { constructor() { this.eleAll = []; this.item = []; this.itemAlready = []; this.itemPending = []; this.itemFailed = []; } add(ele,src) {//insert插入的時(shí)候把所有的dom加入到數(shù)組中去初始化 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); } }
上述是一個(gè)圖片的box,用于存取頁面加載時(shí)候,image圖片對(duì)象的box存取。主要思路是分了三個(gè)數(shù)組,一個(gè)存儲(chǔ)所有的圖片,一個(gè)存儲(chǔ)正在加載的圖片,一個(gè)存儲(chǔ)加載失敗的圖片,然后最重要的是!!!
把這個(gè)imagebox要混入到全局,使其可以當(dāng)做全局變量在全局使用。
import imagebox from "./imagebox" const mixin = { data () { return { imagebox: new imagebox()//這里聲明一個(gè)new對(duì)象,存在全局的變量中,混入vue內(nèi)部,可以全局使用 } } } export default mixin;
下所示代碼中:
構(gòu)造器中初始化各種元素?cái)?shù)組,包括所有圖片,已加載的圖片,正在請求的圖片,失敗的圖片等等
add方法是在item數(shù)組中添加圖片元素
addPending是在正在請求的圖片數(shù)組中添加元素的方法,類似的還有addFailed,addAlready,_remove等私有和共有方法
util是各種工具類方法,包括判斷圖片是否進(jìn)入視野
根據(jù)上述思路,完成下列代碼:
(補(bǔ)充:這里有個(gè)update方法,思路是更新了后,進(jìn)行所有的數(shù)組遍歷,找到相對(duì)應(yīng)的元素,然后進(jìn)行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); } } }
const isSeen = function(item,imagebox){ var ele = item.ele; var src = item.src; //圖片距離頁面頂部的距離 var top = ele.getBoundingClientRect().top; //頁面可視區(qū)域的高度 var windowHeight = document.documentElement.clientHeight || document.body.clientHeight; //top + 10 已經(jīng)進(jìn)入了可視區(qū)域10像素 if(top + 10 < windowHeight){ return true; }else{ return false; } } export { isSeen };
這個(gè)文件主要是監(jiān)聽的一些邏輯,那么肯定需要一些對(duì)象實(shí)例的屬性。首先el元素肯定需要,binding,vnode,$vm肯定都先寫進(jìn)來。
其次imagebox肯定也需要,是圖片的對(duì)象實(shí)例。
init的方法這里和imagebox中的add方法聯(lián)系起來,init一個(gè)就加入imagebox中的item一個(gè)新的元素。
startListen方法是用于在監(jiān)聽后進(jìn)行邏輯操作。
import {isSeen} from "../utils/utils"http://引入工具類的里面的是否看得見元素這個(gè)方法判斷 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進(jìn)去的選項(xiàng) } 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);//每有一個(gè)item,就往box中增加一個(gè)新的元素 this.listenProcess(); } startListen(){ const _self = this; document.addEventListener("scroll",(e)=>{ _self.listenProcess(e);//這里開始操作 }) } }
上面主要初始化了很多屬性,包括vue的虛擬dom和各種包括el元素dom,binding指令傳過來的值等等初始化。
此文件主要為了處理監(jiān)聽頁面滾動(dòng)的,監(jiān)聽是否圖片進(jìn)入到可視范圍內(nèi),然后進(jìn)行一系列下方的各種操作。
根據(jù)image.onload,image.onerror方法進(jìn)行圖片預(yù)加載的邏輯操作,如果看得見這個(gè)圖片,那么就進(jìn)行圖片的加載(同時(shí)加入到pending里面去),加載完進(jìn)行判斷。
下列是process的函數(shù)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一個(gè)image對(duì)象進(jìn)行緩存,緩沖一下,可以做后續(xù)的加載或失敗的函數(shù)處理 image.src = item.src; _self._imageStyle(item);//改變item的樣式 _self.imagebox.addPending(item.ele,item.src);//在對(duì)象imagebox中加入了正在pending請求的item(后續(xù)會(huì)介紹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);//添加到已經(jīng)加載完的item數(shù)組里面 this._imageSet(item.ele,item.src) } imageOnerror(item){//出現(xiàn)錯(cuò)誤的時(shí)候 this._removeImageStyle(item.ele); this.imagebox.addFailed(item.ele,item.src);//添加到出現(xiàn)錯(cuò)誤item數(shù)組里面 this._imageSet(item.ele,this.$lazyload.options.errorUrl)//把配置中的錯(cuò)誤圖片url填入 } _imageStyle(item){ item.ele.style.background = `url(${this.$lazyload.options.loadUrl}) no-repeat center`; } _removeImageStyle(ele){ ele.style.background = ""; } _imageSet(ele,value){//關(guān)于圖片賦值src的操作 ele.src = value; }
補(bǔ)充一個(gè)update方法:
update(ele,src){ console.log("更新了"); console.log(this.imagebox); this.imagebox.update(ele,src);//調(diào)用imagebox中的update方法 this.listenProcess();//再進(jìn)行是否看得見的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(); } //開始監(jiān)聽 startListen(){ var listenProcess = this.listenProcess; document.addEventListener("scroll",listenProcess.bind(this),false); } //移除監(jiān)聽 removeListen(){ var listenProcess = this.listenProcess; document.removeEventListener("scroll",listenProcess.bind(this),false); } //監(jiān)聽的操作函數(shù),包括判斷image的box進(jìn)行請求等 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); } } }) } //進(jìn)行最新圖片地址的更新 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) } //圖片加載錯(cuò)誤的操作 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 = ""; } //對(duì)圖片進(jìn)行賦值 _imageSet(ele,value){ ele.src = value; } }
所有的解釋都已經(jīng)寫在上面的代碼塊里面了。
最后把一些加載的圖片默認(rèn)配置或者失敗圖片地址完成下列代碼:
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); } }
此類暫時(shí)用來存儲(chǔ)各種配置和lazy的預(yù)定默認(rèn)值,options里面存加載的時(shí)候的圖片地址和錯(cuò)誤加載的時(shí)候的圖片地址。
默認(rèn)值是最上面兩個(gè)值,是不傳數(shù)據(jù)默認(rèn)的配置。
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 };
把上述所有的進(jìn)行一個(gè)綜合,放在這個(gè)入口文件進(jìn)行向外暴露。
index就是整個(gè)項(xiàng)目的入口文件,至此我們完成了懶加載插件的基本代碼編寫。
四、打包成.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; //頁面可視區(qū)域的高度 var windowHeight = document.documentElement.clientHeight || document.body.clientHeight; //top + 10 已經(jīng)進(jìn)入了可視區(qū)域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",//這里寫你的加載時(shí)候的圖片配置 errorUrl:"./404.svg"http://錯(cuò)誤加載的圖片配置 });元素中使用指令 imageArr測試數(shù)據(jù)
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" ]
測試地址:戳我戳我
五、后記其實(shí)這些代碼的編寫還是比較簡單的,寫完過后進(jìn)行總結(jié),你會(huì)發(fā)現(xiàn),其中最難的是:
整個(gè)項(xiàng)目的結(jié)構(gòu),和代碼模塊之間的邏輯關(guān)系。
這個(gè)才是最難掌握的,如果涉及到大一點(diǎn)的項(xiàng)目,好一點(diǎn)的項(xiàng)目結(jié)構(gòu)能讓整個(gè)項(xiàng)目進(jìn)度等等因素發(fā)生巨大的變化,提升巨大的效率。而寫插件最難的就是在這。
如何有效地拆分代碼?如何有效地進(jìn)行項(xiàng)目結(jié)構(gòu)的構(gòu)造? 這才是整個(gè)插件編寫的核心。
之前寫過一個(gè)vue關(guān)于表單驗(yàn)證的插件,也是被項(xiàng)目結(jié)構(gòu)搞得焦頭爛額,這里把簡單的懶加載基本代碼做一個(gè)總結(jié)。寫這個(gè)純粹是個(gè)人興趣。希望可以給入門的插件開發(fā)新人給予一點(diǎn)點(diǎn)幫助。
所以我深知寫插件的時(shí)候,它結(jié)構(gòu)和模塊化的重要性。而結(jié)構(gòu)和模塊化的優(yōu)秀,會(huì)讓你事半功倍。另外歡迎大家來我的博客參觀唐益達(dá)的博客,只寫原創(chuàng)。
原創(chuàng)來源我的博客 http://www.tangyida.top/detai... 歡迎交流。
GitHub項(xiàng)目地址:https://github.com/xdnloveme/...
萌新小白,前端開發(fā)入門一年不到,歡迎交流!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/96142.html
摘要:是一個(gè)注冊在指定源和路徑下的事件驅(qū)動(dòng)。可以提供有效有效的離線體驗(yàn),攔截網(wǎng)絡(luò)請求。出于安全原因,要求必須在下才能運(yùn)行。返回一個(gè)對(duì)象,的結(jié)果是對(duì)象值對(duì)象組成的數(shù)組。當(dāng)事件的處理程序執(zhí)行完畢后,可以認(rèn)為安裝完成了。 在前端越來越重的這個(gè)時(shí)代,頁面加載速度成為了一個(gè)重要的指標(biāo)。對(duì)于這個(gè)問題,業(yè)界也有一些解決方案。 瀏覽器緩存、協(xié)議緩存、強(qiáng)緩存 懶加載(首屏) CDN 多域名突破下載并發(fā)限制。...
摘要:是一個(gè)注冊在指定源和路徑下的事件驅(qū)動(dòng)。可以提供有效有效的離線體驗(yàn),攔截網(wǎng)絡(luò)請求。出于安全原因,要求必須在下才能運(yùn)行。返回一個(gè)對(duì)象,的結(jié)果是對(duì)象值對(duì)象組成的數(shù)組。當(dāng)事件的處理程序執(zhí)行完畢后,可以認(rèn)為安裝完成了。 在前端越來越重的這個(gè)時(shí)代,頁面加載速度成為了一個(gè)重要的指標(biāo)。對(duì)于這個(gè)問題,業(yè)界也有一些解決方案。 瀏覽器緩存、協(xié)議緩存、強(qiáng)緩存 懶加載(首屏) CDN 多域名突破下載并發(fā)限制。...
摘要:其實(shí)就是我們開始掛載上去的我們在這里出去,我們就可以在回調(diào)里面只處理我們的業(yè)務(wù)邏輯,而其他如斷網(wǎng)超時(shí)服務(wù)器出錯(cuò)等均通過攔截器進(jìn)行統(tǒng)一處理。 showImg(https://segmentfault.com/img/remote/1460000015472616?w=845&h=622); 開始之前 隨著業(yè)務(wù)的不斷累積,目前我們 ToC 端主要項(xiàng)目,除去 node_modules, bu...
摘要:其實(shí)就是我們開始掛載上去的我們在這里出去,我們就可以在回調(diào)里面只處理我們的業(yè)務(wù)邏輯,而其他如斷網(wǎng)超時(shí)服務(wù)器出錯(cuò)等均通過攔截器進(jìn)行統(tǒng)一處理。 showImg(https://segmentfault.com/img/remote/1460000015472616?w=845&h=622); 開始之前 隨著業(yè)務(wù)的不斷累積,目前我們 ToC 端主要項(xiàng)目,除去 node_modules, bu...
摘要:基礎(chǔ)理論是二進(jìn)制協(xié)議這是一個(gè)復(fù)用協(xié)議。使用現(xiàn)狀案例淘寶案例京東案例知乎案例二月的公司不好意思,我們還沒用上這么牛逼的協(xié)議。本文內(nèi)容很淺,想要了解更多的知識(shí),可以去知乎搜相關(guān)文章和回答。 目的 http2出來也有段時(shí)間了,很多網(wǎng)站都已經(jīng)實(shí)際應(yīng)用了它,而我還活在http1.1的時(shí)代,趁著還年輕,記性還行,花點(diǎn)時(shí)間研究了http2在nodejs中的使用。 http2基礎(chǔ)理論 HTTP2是二...
閱讀 2683·2021-09-26 10:19
閱讀 2136·2021-09-24 10:27
閱讀 2520·2021-09-01 10:42
閱讀 2302·2019-08-29 16:09
閱讀 2485·2019-08-29 15:17
閱讀 1447·2019-08-29 15:09
閱讀 633·2019-08-29 11:14
閱讀 2301·2019-08-26 13:25