Preface
imagepool is a JS tool for managing image loading. Imagepool can control the number of concurrent image loadings.
For picture loading, the most primitive way is to write an img tag directly, such as: <img src="image url" />.
After continuous optimization, an image delay loading scheme has appeared. This time, the URL of the image is not written directly in the src attribute, but in a certain attribute, such as: <img src="" data-src="image url" />. In this way, the browser will not automatically load the picture. When the appropriate time is needed, use js to put the url in the data-src attribute into the src attribute of the img tag, or after reading the url, use js to load the picture, and set the src attribute after loading, and display the picture.
This seems to be well controlled, but there will still be problems.
Although it can only load a part of the picture, this part of the picture may still be a relatively large order of magnitude.
This is no big deal for the PC side, but for the mobile side, too many concurrent images are loaded, which is very likely to cause application crashes.
Therefore, we urgently need an image buffering mechanism to control image loading concurrency. Similar to the backend database connection pool, it does not create too many connections, and can fully reuse each connection.
At this point, imagepool was born.
Bad schematic diagram
Instructions for use
First, initialize the connection pool:
var imagepool = initImagePool(5);
initImagePool is a global method that can be used directly anywhere. The function is to create a connection pool, and you can specify the maximum number of connections to the connection pool, optionally, the default is 5.
In the same page, multiple calls to initImagePool return the same core instance, which is always the first, with a bit of a singleton feeling. for example:
The code copy is as follows:
var imagepool1 = initImagePool(3);
var imagepool2 = initImagePool(7);
At this time, the maximum number of connections between imagepool1 and imagepool2 is 3, and the same core instance is used internally. Note that the internal core is the same, not that imagepool1 === imagepool2.
After initialization, you can load the picture with confidence.
The easiest way to call is as follows:
The code copy is as follows:
var imagepool = initImagePool(10);
imagepool.load("image url",{
success: function(src){
console.log("success:::::"+src);
},
error: function(src){
console.log("error::::"+src);
}
});
Just call the load method on the instance.
The load method has two parameters. The first parameter is the image url that needs to be loaded, and the second parameter is various options, including successful and failed callbacks. The image url will be passed in during callbacks.
This way, you can only pass in one picture, so it can also be written in the following form:
The code copy is as follows:
var imagepool = initImagePool(10);
imagepool.load(["Image 1url","Image 2url"],{
success: function(src){
console.log("success:::::"+src);
},
error: function(src){
console.log("error::::"+src);
}
});
By passing in an image url array, you can pass in multiple images.
When each image is loaded successfully (or failed), the success (or error) method will be called and the corresponding image url will be passed.
But sometimes we don’t need to callbacks like this frequently. Pass in an image url array. When all the images in this array are processed, then callbacks are enough.
Just add one option:
The code copy is as follows:
var imagepool = initImagePool(10);
imagepool.load(["Image 1url","Image 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
});
By adding an once attribute to the option and setting it to true, you can only achieve a callback once.
This time, the success method must be called back, and the error method is ignored at this time.
At this time, the callback success method is no longer passing in an image url parameter, but passing in three parameters, namely: successful url array, failed url array, and total number of images processed.
In addition, there is a way to get the internal state of the connection pool:
The code copy is as follows:
var imagepool = initImagePool(10);
console.log(imagepool.info());
By calling the info method, you can get the internal state of the connection pool at the current time, and the data structure is as follows:
Object.task.count Number of tasks waiting for processing in the connection pool
Object.thread.count The maximum number of connections to the connection pool
Object.thread.free Number of free connections to the connection pool
It is recommended not to call this method frequently.
Finally, it should be noted that if the image fails to load, it will try at most 3 times. If the image fails to load in the end, the error method will be called back. The number of attempts can be modified in the source code.
Finally, let me emphasize that readers can push images into the connection pool as much as possible, without worrying about excessive concurrency. Imagepool will help you load these images in a mess.
Finally, it must be noted that imagepool will not theoretically reduce the image loading speed, it is just a smooth loading.
Source code
The code copy is as follows:
(function(exports){
//Single
var instance = null;
var emptyFn = function(){};
//Initial default configuration
var config_default = {
//The number of "threads" in the thread pool
thread: 5,
//The number of retry failed to load the picture
//Try twice, add the original one, a total of 3 times
"try": 2
};
//tool
var _helpers = {
//Set dom attribute
setAttr: (function(){
var img = new Image();
//Judge whether the browser supports 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;
};
}
}()),
//Get dom attribute
getAttr: (function(){
var img = new Image();
//Judge whether the browser supports HTML5 dataset
if(img.dataset){
return function(dom, name){
return dom.dataset[name];
};
}else{
return function(dom, name){
return dom.getAttribute("data-"+name);
};
}
}())
};
/**
* Constructing method
* @param max Maximum number of connections. Value.
*/
function ImagePool(max){
//Maximum number of concurrency
this.max = max || config_default.thread;
this.linkHead = null;
this.linkNode = null;
//Loading pool
//[{img: dom,free: true, node: node}]
//node
//{src: "", options: {success: "fn", error: "fn", once: true}, try: 0}
this.pool = [];
}
/**
* Initialization
*/
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;
//Callback
//_s.getNode(this).options.success.call(null, this.src);
_s.Notice(_s.getNode(this), "success", this.src);
//Processing tasks
_s.executeLink(this);
};
img.onerror = function(e){
var node = _s.getNode(this);
//Judge the number of attempts
if(node.try < config_default.try){
node.try = node.try + 1;
//Add to the end of the task list again
_s.appendNode(_s.createNode(node.src, node.options, node.Notice, node.group, node.try));
}else{
//Error callback
//node.options.error.call(null, this.src);
_s.Notice(node, "error", this.src);
}
//Processing tasks
_s.executeLink(this);
};
obj.img = img;
obj.free = true;
this.pool.push(obj);
}
};
/**
* Callback encapsulation
* @param node node. Object.
* @param status status. String. Optional value: success|error(failed)
* @param src Image path. String.
*/
ImagePool.prototype.Notice = function(node, status, src){
node.Notice(status, src);
};
/**
* Processing linked list tasks
* @param dom Image dom object. Object.
*/
ImagePool.prototype.executeLink = function(dom){
//Discern whether there are nodes in the linked list
if(this.linkHead){
//Load the next picture
this.setSrc(dom, this.linkHead);
//Remove the link header
this.shiftNode();
}else{
//Set your own status to idle
this.status(dom, true);
}
};
/**
* Get idle "thread"
*/
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;
};
/**
* Encapsulate src attribute settings
* Because changing the src attribute is equivalent to loading the picture, encapsulate the operation
* @param dom Image dom object. Object.
* @param node node. Object.
*/
ImagePool.prototype.setSrc = function(dom, node){
//Set the "thread" in the pool to be non-idle
this.status(dom, false);
//Affiliated node
this.setNode(dom, node);
//Load the picture
dom.src = node.src;
};
/**
* Update the "thread" status in the pool
* @param dom Image dom object. Object.
* @param status status. Boolean. Optional value: true (idle) | false (non-idle)
*/
ImagePool.prototype.status = function(dom, status){
var id = _helpers.getAttr(dom, "id");
this.pool[id].free = status;
//Idle state, clear the associated node
if(status){
this.pool[id].node = null;
}
};
/**
* Update the associated node of "thread" in the pool
* @param dom Image dom object. Object.
* @param node node. Object.
*/
ImagePool.prototype.setNode = function(dom, node){
var id = _helpers.getAttr(dom, "id");
this.pool[id].node = node;
return this.pool[id].node === node;
};
/**
* Get the associated node of the "thread" in the pool
* @param dom Image dom object. Object.
*/
ImagePool.prototype.getNode = function(dom){
var id = _helpers.getAttr(dom, "id");
return this.pool[id].node;
};
/**
* External interface, load pictures
* @param src can be an src string or an array of src strings.
* @param options User-defined parameters. Includes: success callback, error callback, once tag.
*/
ImagePool.prototype.load = function(src, options){
var srcs = [],
free = null,
length = 0,
i = 0,
//Only initialize the callback strategy once
notice = (function(){
if(options.once){
return function(status, src){
var g = this.group,
o = this.options;
//Record
g[status].push(src);
//Discern whether all reorganizations have been processed
if(g.success.length + g.error.length === g.count){
//asynchronous
//In fact, it is executed separately as another task to prevent the callback function from executing too long and affecting the image loading speed
setTimeout(function(){
o.success.call(null, g.success, g.error, g.count);
},1);
}
};
}else{
return function(status, src){
var o = this.options;
// Direct callback
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);
//Set the number of group elements
group.count = srcs.length;
//Travel over the pictures that need to be loaded
for(i = 0, length = srcs.length; i < length; i++){
//Create a node
node = this.createNode(srcs[i], options, notice, group);
//Judge whether the thread pool is free
free = this.getFree();
if(free){
//If you have free time, load the picture immediately
this.setSrc(free.img, node);
}else{
//No idle, add the task to the linked list
this.appendNode(node);
}
}
};
/**
* Get internal status information
* @returns {{}}
*/
ImagePool.prototype.info = function(){
var info = {},
length = 0,
i = 0,
node = null;
//Thread
info.thread = {};
//Total number of threads
info.thread.count = this.pool.length;
//Number of idle threads
info.thread.free = 0;
//Task
info.task = {};
//Number of pending tasks
info.task.count = 0;
//Get the number of free "threads"
for(i = 0, length = this.pool.length; i < length; i++){
if(this.pool[i].free){
info.thread.free = info.thread.free + 1;
}
}
//Get the number of tasks (task chain length)
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;
};
/**
* Create a node
* @param src Image path. String.
* @param options User-defined parameters. Includes: success callback, error callback, once tag.
* @param notice Callback strategy. function.
* @param group group information. Object. {count: 0, success: [], error: []}
* @param tr Number of retry errors. Value. The default is 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;
};
/**
* Append nodes to the end of the task list
* @param node node. Object.
*/
ImagePool.prototype.appendNode = function(node){
//Judge whether the linked list is empty
if(!this.linkHead){
this.linkHead = node;
this.linkNode = node;
}else{
this.linkNode.next = node;
this.linkNode = node;
}
};
/**
* Delete the header of the link
*/
ImagePool.prototype.shiftNode = function(){
//Discern whether there are nodes in the linked list
if(this.linkHead){
//Modify the link list header
this.linkHead = this.linkHead.next || null;
}
};
/**
* Export external interface
* @param max Maximum number of connections. Value.
* @returns {{load: Function, info: Function}}
*/
exports.initImagePool = function(max){
if(!instance){
instance = new ImagePool(max);
instance.initPool();
}
return {
/**
* Loading pictures
*/
load: function(){
instance.load.apply(instance, arguments);
},
/**
* Internal information
* @returns {*|any|void}
*/
info: function(){
return instance.info.call(instance);
}
};
};
}(this));
The above is an example of how to use this particularly awesome JavaScript front-end image loading manager. Have you learned how to use it?