JavaScript前端圖片加載管理器imagepool使用詳解
前言
imagepool是一款管理圖片加載的JS工具,通過imagepool可以控制圖片并發(fā)加載個數(shù)。
對于圖片加載,最原始的方式就是直接寫個img標簽,比如:<img src="圖片url" />。
經(jīng)過不斷優(yōu)化,出現(xiàn)了圖片延遲加載方案,這回圖片的URL不直接寫在src屬性中,而是寫在某個屬性中,比如:<img src="" data-src="圖片url" />。這樣瀏覽器就不會自動加載圖片,等到一個恰當?shù)臅r機需要加載了,則用js把data-src屬性中的url放到img標簽的src屬性中,或者讀出url后,用js去加載圖片,加載完成后再設(shè)置src屬性,顯示出圖片。
這看起來已經(jīng)控制的很好了,但依然會有問題。
雖然能做到只加載一部分圖片,但這一部分圖片,仍然可能是一個比較大的數(shù)量級。
這對于PC端來說,沒什么大不了,但對于移動端,圖片并發(fā)加載數(shù)量過多,極有可能引起應(yīng)用崩潰。
因此我們迫切需要一種圖片緩沖機制,來控制圖片加載并發(fā)。類似于后端的數(shù)據(jù)庫連接池,既不會創(chuàng)建過多連接,又能充分復(fù)用每一個連接。
至此,imagepool誕生了。
拙劣的原理圖
使用說明
首先要初始化連接池:
var imagepool = initImagePool(5);
initImagePool 是全局方法,任何地方都可以直接使用。作用是創(chuàng)建一個連接池,并且可以指定連接池的最大連接數(shù),可選,默認為5。
在同一個頁面中,多次調(diào)用initImagePool均返回同一個核心實例,永遠是第一個,有點單例的感覺。比如:
var imagepool1 = initImagePool(3);
var imagepool2 = initImagePool(7);
此時imagepool1和imagepool2的最大連接數(shù)均為3,內(nèi)部使用的是同一個核心實例。注意,是內(nèi)部的核心相同,并不是說imagepool1 === imagepool2。
初始化之后,就可以放心大膽的加載圖片了。
最簡單的調(diào)用方法如下:
var imagepool = initImagePool(10);
imagepool.load("圖片url",{
success: function(src){
console.log("success:::::"+src);
},
error: function(src){
console.log("error:::::"+src);
}
});
直接在實例上調(diào)用load方法即可。
load方法有兩個參數(shù)。第一個參數(shù)是需要加載的圖片url,第二個參數(shù)是各種選項,包含了成功、失敗的回調(diào),回調(diào)時會傳入圖片url。
這樣寫只能傳入一張圖片,因此,也可以寫成如下形式:
var imagepool = initImagePool(10);
imagepool.load(["圖片1url","圖片2url"],{
success: function(src){
console.log("success:::::"+src);
},
error: function(src){
console.log("error:::::"+src);
}
});
通過傳入一個圖片url數(shù)組,就可以傳入多個圖片了。
每一個圖片加載成功(或失敗),都會調(diào)用success(或error)方法,并且傳入對應(yīng)的圖片url。
但有時候我們并不需要這樣頻繁的回調(diào),傳入一個圖片url數(shù)組,當這個數(shù)組中所有的圖片都處理完成后,再回調(diào)就可以了。
只需加一個選項即可:
var imagepool = initImagePool(10);
imagepool.load(["圖片1url ","圖片2url "],{
success: function(sArray, eArray, count){
console.log("sArray:::::"+sArray);
console.log("eArray:::::"+eArray);
console.log("count:::::"+count);
},
error: function(src){
console.log("error:::::"+src);
},
once: true
});
通過在選項中加一個once屬性,并設(shè)置為true,即可實現(xiàn)只回調(diào)一次。
這一次回調(diào),必然回調(diào)success方法,此時error方法是被忽略的。
此時回調(diào)success方法,不再是傳入一個圖片url參數(shù),而是傳入三個參數(shù),分別為:成功的url數(shù)組、失敗的url數(shù)組、總共處理的圖片個數(shù)。
此外,還有一個方法可以獲取連接池內(nèi)部狀態(tài):
var imagepool = initImagePool(10);
console.log(imagepool.info());
通過調(diào)用info方法,可以得到當前時刻連接池內(nèi)部狀態(tài),數(shù)據(jù)結(jié)構(gòu)如下:
Object.task.count 連接池中等待處理的任務(wù)數(shù)量
Object.thread.count 連接池最大連接數(shù)
Object.thread.free 連接池空閑連接數(shù)
建議不要頻繁調(diào)用此方法。
最后需要說明的是,如果圖片加載失敗,最多會嘗試3次,如果最后還是加載失敗,才回調(diào)error方法。嘗試次數(shù)可在源碼中修改。
最最后再強調(diào)一下,讀者可以盡情的往連接池中push圖片,完全不必擔心并發(fā)過多的問題,imagepool會有條不絮的幫你加載這些圖片。
最最最后,必須說明的是,imagepool理論上不會降低圖片加載速度,只不過是平緩的加載。
源碼
(function(exports){
//單例
var instance = null;
var emptyFn = function(){};
//初始默認配置
var config_default = {
//線程池"線程"數(shù)量
thread: 5,
//圖片加載失敗重試次數(shù)
//重試2次,加上原有的一次,總共是3次
"try": 2
};
//工具
var _helpers = {
//設(shè)置dom屬性
setAttr: (function(){
var img = new Image();
//判斷瀏覽器是否支持HTML5 dataset
if(img.dataset){
return function(dom, name, value){
dom.dataset[name] = value;
return value;
};
}else{
return function(dom, name, value){
dom.setAttribute("data-"+name, value);
return value;
};
}
}()),
//獲取dom屬性
getAttr: (function(){
var img = new Image();
//判斷瀏覽器是否支持HTML5 dataset
if(img.dataset){
return function(dom, name){
return dom.dataset[name];
};
}else{
return function(dom, name){
return dom.getAttribute("data-"+name);
};
}
}())
};
/**
* 構(gòu)造方法
* @param max 最大連接數(shù)。數(shù)值。
*/
function ImagePool(max){
//最大并發(fā)數(shù)量
this.max = max || config_default.thread;
this.linkHead = null;
this.linkNode = null;
//加載池
//[{img: dom,free: true, node: node}]
//node
//{src: "", options: {success: "fn",error: "fn", once: true}, try: 0}
this.pool = [];
}
/**
* 初始化
*/
ImagePool.prototype.initPool = function(){
var i,img,obj,_s;
_s = this;
for(i = 0;i < this.max; i++){
obj = {};
img = new Image();
_helpers.setAttr(img, "id", i);
img.onload = function(){
var id,src;
//回調(diào)
//_s.getNode(this).options.success.call(null, this.src);
_s.notice(_s.getNode(this), "success", this.src);
//處理任務(wù)
_s.executeLink(this);
};
img.onerror = function(e){
var node = _s.getNode(this);
//判斷嘗試次數(shù)
if(node.try < config_default.try){
node.try = node.try + 1;
//再次追加到任務(wù)鏈表末尾
_s.appendNode(_s.createNode(node.src, node.options, node.notice, node.group, node.try));
}else{
//error回調(diào)
//node.options.error.call(null, this.src);
_s.notice(node, "error", this.src);
}
//處理任務(wù)
_s.executeLink(this);
};
obj.img = img;
obj.free = true;
this.pool.push(obj);
}
};
/**
* 回調(diào)封裝
* @param node 節(jié)點。對象。
* @param status 狀態(tài)。字符串。可選值:success(成功)|error(失敗)
* @param src 圖片路徑。字符串。
*/
ImagePool.prototype.notice = function(node, status, src){
node.notice(status, src);
};
/**
* 處理鏈表任務(wù)
* @param dom 圖像dom對象。對象。
*/
ImagePool.prototype.executeLink = function(dom){
//判斷鏈表是否存在節(jié)點
if(this.linkHead){
//加載下一個圖片
this.setSrc(dom, this.linkHead);
//去除鏈表頭
this.shiftNode();
}else{
//設(shè)置自身狀態(tài)為空閑
this.status(dom, true);
}
};
/**
* 獲取空閑"線程"
*/
ImagePool.prototype.getFree = function(){
var length,i;
for(i = 0, length = this.pool.length; i < length; i++){
if(this.pool[i].free){
return this.pool[i];
}
}
return null;
};
/**
* 封裝src屬性設(shè)置
* 因為改變src屬性相當于加載圖片,所以把操作封裝起來
* @param dom 圖像dom對象。對象。
* @param node 節(jié)點。對象。
*/
ImagePool.prototype.setSrc = function(dom, node){
//設(shè)置池中的"線程"為非空閑狀態(tài)
this.status(dom, false);
//關(guān)聯(lián)節(jié)點
this.setNode(dom, node);
//加載圖片
dom.src = node.src;
};
/**
* 更新池中的"線程"狀態(tài)
* @param dom 圖像dom對象。對象。
* @param status 狀態(tài)。布爾。可選值:true(空閑)|false(非空閑)
*/
ImagePool.prototype.status = function(dom, status){
var id = _helpers.getAttr(dom, "id");
this.pool[id].free = status;
//空閑狀態(tài),清除關(guān)聯(lián)的節(jié)點
if(status){
this.pool[id].node = null;
}
};
/**
* 更新池中的"線程"的關(guān)聯(lián)節(jié)點
* @param dom 圖像dom對象。對象。
* @param node 節(jié)點。對象。
*/
ImagePool.prototype.setNode = function(dom, node){
var id = _helpers.getAttr(dom, "id");
this.pool[id].node = node;
return this.pool[id].node === node;
};
/**
* 獲取池中的"線程"的關(guān)聯(lián)節(jié)點
* @param dom 圖像dom對象。對象。
*/
ImagePool.prototype.getNode = function(dom){
var id = _helpers.getAttr(dom, "id");
return this.pool[id].node;
};
/**
* 對外接口,加載圖片
* @param src 可以是src字符串,也可以是src字符串數(shù)組。
* @param options 用戶自定義參數(shù)。包含:success回調(diào)、error回調(diào)、once標識。
*/
ImagePool.prototype.load = function(src, options){
var srcs = [],
free = null,
length = 0,
i = 0,
//只初始化一次回調(diào)策略
notice = (function(){
if(options.once){
return function(status, src){
var g = this.group,
o = this.options;
//記錄
g[status].push(src);
//判斷改組是否全部處理完成
if(g.success.length + g.error.length === g.count){
//異步
//實際上是作為另一個任務(wù)單獨執(zhí)行,防止回調(diào)函數(shù)執(zhí)行時間過長影響圖片加載速度
setTimeout(function(){
o.success.call(null, g.success, g.error, g.count);
},1);
}
};
}else{
return function(status, src){
var o = this.options;
//直接回調(diào)
setTimeout(function(){
o[status].call(null, src);
},1);
};
}
}()),
group = {
count: 0,
success: [],
error: []
},
node = null;
options = options || {};
options.success = options.success || emptyFn;
options.error = options.error || emptyFn;
srcs = srcs.concat(src);
//設(shè)置組元素個數(shù)
group.count = srcs.length;
//遍歷需要加載的圖片
for(i = 0, length = srcs.length; i < length; i++){
//創(chuàng)建節(jié)點
node = this.createNode(srcs[i], options, notice, group);
//判斷線程池是否有空閑
free = this.getFree();
if(free){
//有空閑,則立即加載圖片
this.setSrc(free.img, node);
}else{
//沒有空閑,將任務(wù)添加到鏈表
this.appendNode(node);
}
}
};
/**
* 獲取內(nèi)部狀態(tài)信息
* @returns {{}}
*/
ImagePool.prototype.info = function(){
var info = {},
length = 0,
i = 0,
node = null;
//線程
info.thread = {};
//線程總數(shù)量
info.thread.count = this.pool.length;
//空閑線程數(shù)量
info.thread.free = 0;
//任務(wù)
info.task = {};
//待處理任務(wù)數(shù)量
info.task.count = 0;
//獲取空閑"線程"數(shù)量
for(i = 0, length = this.pool.length; i < length; i++){
if(this.pool[i].free){
info.thread.free = info.thread.free + 1;
}
}
//獲取任務(wù)數(shù)量(任務(wù)鏈長度)
node = this.linkHead;
if(node){
info.task.count = info.task.count + 1;
while(node.next){
info.task.count = info.task.count + 1;
node = node.next;
}
}
return info;
};
/**
* 創(chuàng)建節(jié)點
* @param src 圖片路徑。字符串。
* @param options 用戶自定義參數(shù)。包含:success回調(diào)、error回調(diào)、once標識。
* @param notice 回調(diào)策略。 函數(shù)。
* @param group 組信息。對象。{count: 0, success: [], error: []}
* @param tr 出錯重試次數(shù)。數(shù)值。默認為0。
* @returns {{}}
*/
ImagePool.prototype.createNode = function(src, options, notice, group, tr){
var node = {};
node.src = src;
node.options = options;
node.notice = notice;
node.group = group;
node.try = tr || 0;
return node;
};
/**
* 向任務(wù)鏈表末尾追加節(jié)點
* @param node 節(jié)點。對象。
*/
ImagePool.prototype.appendNode = function(node){
//判斷鏈表是否為空
if(!this.linkHead){
this.linkHead = node;
this.linkNode = node;
}else{
this.linkNode.next = node;
this.linkNode = node;
}
};
/**
* 刪除鏈表頭
*/
ImagePool.prototype.shiftNode = function(){
//判斷鏈表是否存在節(jié)點
if(this.linkHead){
//修改鏈表頭
this.linkHead = this.linkHead.next || null;
}
};
/**
* 導(dǎo)出對外接口
* @param max 最大連接數(shù)。數(shù)值。
* @returns {{load: Function, info: Function}}
*/
exports.initImagePool = function(max){
if(!instance){
instance = new ImagePool(max);
instance.initPool();
}
return {
/**
* 加載圖片
*/
load: function(){
instance.load.apply(instance, arguments);
},
/**
* 內(nèi)部信息
* @returns {*|any|void}
*/
info: function(){
return instance.info.call(instance);
}
};
};
}(this));
以上就是這款特別棒的javascript前端圖片加載管理器的使用方法示例,小伙伴們學(xué)會使用了嗎?
相關(guān)文章
JavaScript 判斷判斷某個對象是Object還是一個Array
在開發(fā)中,我們經(jīng)常需要判斷某個對象是否為數(shù)組類型,在Js中檢測對象類型的常見方法都有哪些呢?2010-01-01Mobile Web開發(fā)基礎(chǔ)之四--處理手機設(shè)備的橫豎屏問題
這篇文章主要介紹了Mobile Web開發(fā)基礎(chǔ)之-—處理手機設(shè)備的橫豎屏,window.orientation屬性與onorientationchange事件以及media query方式是開發(fā)過程中需要注意到的兩種解決方式,需要的朋友可以參考下2017-08-08javascript 學(xué)習(xí)筆記(四) 倒計時程序代碼
javascript 學(xué)習(xí)筆記(四) 倒計時程序代碼,需要的朋友可以參考下。2011-04-04Javascript學(xué)習(xí)筆記9 prototype封裝繼承
在上文中,我利用prototype的原理做了一個封裝的New,然后我就想到,我是否可以用prototype的原理進一步封裝面向?qū)ο蟮囊恍┗咎卣髂??比如繼承。2010-01-01javascript中的=等號個數(shù)問題兩個跟三個有什么區(qū)別
一個等號就是個賦值的作用,主要問題在于兩個跟三個等號的區(qū)別,想必有很多的朋友都不知道吧,在本文有個不錯的示例主要介紹下兩者到底有什么區(qū)別,感興趣的朋友不要錯過2013-10-10深入理解JavaScript系列(19):求值策略(Evaluation strategy)詳解
這篇文章主要介紹了深入理解JavaScript系列(19):求值策略(Evaluation strategy)詳解,本文講解了一般理論、按值傳遞、按引用傳遞、按共享傳遞(Call by sharing)、按共享傳遞是按值傳遞的特例等內(nèi)容,需要的朋友可以參考下2015-03-03