zoukankan      html  css  js  c++  java
  • imagepool前端图片加载管理器(JavaScript图片连接池)

    前言

          imagepool是一款管理图片加载的JS工具,通过imagepool可以控制图片并发加载个数。

          对于图片加载,最原始的方式就是直接写个img标签,比如:<img src="图片url" />

          经过不断优化,出现了图片延迟加载方案,这回图片的URL不直接写在src属性中,而是写在某个属性中,比如:<img src="" data-src="图片url" />。这样浏览器就不会自动加载图片,等到一个恰当的时机需要加载了,则用js把data-src属性中的url放到img标签的src属性中,或者读出url后,用js去加载图片,加载完成后再设置src属性,显示出图片。

          这看起来已经控制的很好了,但依然会有问题。

          虽然能做到只加载一部分图片,但这一部分图片,仍然可能是一个比较大的数量级。

          这对于PC端来说,没什么大不了,但对于移动端,图片并发加载数量过多,极有可能引起应用崩溃。

          因此我们迫切需要一种图片缓冲机制,来控制图片加载并发。类似于后端的数据库连接池,既不会创建过多连接,又能充分复用每一个连接。

          至此,imagepool诞生了。

     拙劣的原理图

    使用说明

         首先要初始化连接池:

    1 var imagepool = initImagePool(5);

         initImagePool 是全局方法,任何地方都可以直接使用。作用是创建一个连接池,并且可以指定连接池的最大连接数,可选,默认为5。

         在同一个页面中,多次调用initImagePool均返回同一个核心实例,永远是第一个,有点单例的感觉。比如:

    1 var imagepool1 = initImagePool(3);
    2 var imagepool2 = initImagePool(7);

         此时imagepool1和imagepool2的最大连接数均为3,内部使用的是同一个核心实例。注意,是内部的核心相同,并不是说imagepool1 === imagepool2。

         初始化之后,就可以放心大胆的加载图片了。

         最简单的调用方法如下:

     1 var imagepool = initImagePool(10);
     2 
     3 imagepool.load("图片url",{
     4     success: function(src){
     5         console.log("success:::::"+src);
     6     },
     7     error: function(src){
     8         console.log("error:::::"+src);
     9     }
    10 });

         直接在实例上调用load方法即可。

         load方法有两个参数。第一个参数是需要加载的图片url,第二个参数是各种选项,包含了成功、失败的回调,回调时会传入图片url。

         这样写只能传入一张图片,因此,也可以写成如下形式:

     1 var imagepool = initImagePool(10);
     2 
     3 imagepool.load(["图片1url","图片2url"],{
     4     success: function(src){
     5         console.log("success:::::"+src);
     6     },
     7     error: function(src){
     8         console.log("error:::::"+src);
     9     }
    10 });

         通过传入一个图片url数组,就可以传入多个图片了。

         每一个图片加载成功(或失败),都会调用success(或error)方法,并且传入对应的图片url。

         但有时候我们并不需要这样频繁的回调,传入一个图片url数组,当这个数组中所有的图片都处理完成后,再回调就可以了。

         只需加一个选项即可:

     1 var imagepool = initImagePool(10);
     2 
     3 imagepool.load(["图片1url ","图片2url "],{
     4     success: function(sArray, eArray, count){
     5         console.log("sArray:::::"+sArray);
     6         console.log("eArray:::::"+eArray);
     7         console.log("count:::::"+count);
     8     },
     9     error: function(src){
    10         console.log("error:::::"+src);
    11     },
    12     once: true
    13 });

         通过在选项中加一个once属性,并设置为true,即可实现只回调一次。

         这一次回调,必然回调success方法,此时error方法是被忽略的。

         此时回调success方法,不再是传入一个图片url参数,而是传入三个参数,分别为:成功的url数组、失败的url数组、总共处理的图片个数。

         此外,还有一个方法可以获取连接池内部状态:

    1 var imagepool = initImagePool(10);
    2 
    3 console.log(imagepool.info());

         通过调用info方法,可以得到当前时刻连接池内部状态,数据结构如下:

    •      Object.task.count 连接池中等待处理的任务数量
    •      Object.thread.count 连接池最大连接数
    •      Object.thread.free 连接池空闲连接数

         建议不要频繁调用此方法。

         最后需要说明的是,如果图片加载失败,最多会尝试3次,如果最后还是加载失败,才回调error方法。尝试次数可在源码中修改。

         最最后再强调一下,读者可以尽情的往连接池中push图片,完全不必担心并发过多的问题,imagepool会有条不絮的帮你加载这些图片。

         最最最后,必须说明的是,imagepool理论上不会降低图片加载速度,只不过是平缓的加载。

    源码

      1 (function(exports){
      2     //单例
      3     var instance = null;
      4     var emptyFn = function(){};
      5 
      6     //初始默认配置
      7     var config_default = {
      8         //线程池"线程"数量
      9         thread: 5,
     10         //图片加载失败重试次数
     11         //重试2次,加上原有的一次,总共是3次
     12         "try": 2
     13     };
     14 
     15     //工具
     16     var _helpers = {
     17         //设置dom属性
     18         setAttr: (function(){
     19             var img = new Image();
     20             //判断浏览器是否支持HTML5 dataset
     21             if(img.dataset){
     22                 return function(dom, name, value){
     23                     dom.dataset[name] = value;
     24                     return value;
     25                 };
     26             }else{
     27                 return function(dom, name, value){
     28                     dom.setAttribute("data-"+name, value);
     29                     return value;
     30                 };
     31             }
     32         }()),
     33         //获取dom属性
     34         getAttr: (function(){
     35             var img = new Image();
     36             //判断浏览器是否支持HTML5 dataset
     37             if(img.dataset){
     38                 return function(dom, name){
     39                     return dom.dataset[name];
     40                 };
     41             }else{
     42                 return function(dom, name){
     43                     return dom.getAttribute("data-"+name);
     44                 };
     45             }
     46         }())
     47     };
     48 
     49     /**
     50      * 构造方法
     51      * @param max 最大连接数。数值。
     52      */
     53     function ImagePool(max){
     54         //最大并发数量
     55         this.max = max || config_default.thread;
     56         this.linkHead = null;
     57         this.linkNode = null;
     58         //加载池
     59         //[{img: dom,free: true, node: node}]
     60         //node
     61         //{src: "", options: {success: "fn",error: "fn", once: true}, try: 0}
     62         this.pool = [];
     63     }
     64 
     65     /**
     66      * 初始化
     67      */
     68     ImagePool.prototype.initPool = function(){
     69         var i,img,obj,_s;
     70 
     71         _s = this;
     72         for(i = 0;i < this.max; i++){
     73             obj = {};
     74             img = new Image();
     75             _helpers.setAttr(img, "id", i);
     76             img.onload = function(){
     77                 var id,src;
     78                 //回调
     79                 //_s.getNode(this).options.success.call(null, this.src);
     80                 _s.notice(_s.getNode(this), "success", this.src);
     81 
     82                 //处理任务
     83                 _s.executeLink(this);
     84             };
     85             img.onerror = function(e){
     86                 var node = _s.getNode(this);
     87 
     88                 //判断尝试次数
     89                 if(node.try < config_default.try){
     90                     node.try = node.try + 1;
     91                     //再次追加到任务链表末尾
     92                     _s.appendNode(_s.createNode(node.src, node.options, node.notice, node.group, node.try));
     93 
     94                 }else{
     95                     //error回调
     96                     //node.options.error.call(null, this.src);
     97                     _s.notice(node, "error", this.src);
     98                 }
     99 
    100                 //处理任务
    101                 _s.executeLink(this);
    102             };
    103             obj.img = img;
    104             obj.free = true;
    105             this.pool.push(obj);
    106         }
    107     };
    108 
    109     /**
    110      * 回调封装
    111      * @param node 节点。对象。
    112      * @param status 状态。字符串。可选值:success(成功)|error(失败)
    113      * @param src 图片路径。字符串。
    114      */
    115     ImagePool.prototype.notice = function(node, status, src){
    116         node.notice(status, src);
    117     };
    118 
    119     /**
    120      * 处理链表任务
    121      * @param dom 图像dom对象。对象。
    122      */
    123     ImagePool.prototype.executeLink = function(dom){
    124         //判断链表是否存在节点
    125         if(this.linkHead){
    126             //加载下一个图片
    127             this.setSrc(dom, this.linkHead);
    128             //去除链表头
    129             this.shiftNode();
    130         }else{
    131             //设置自身状态为空闲
    132             this.status(dom, true);
    133         }
    134     };
    135 
    136     /**
    137      * 获取空闲"线程"
    138      */
    139     ImagePool.prototype.getFree = function(){
    140         var length,i;
    141         for(i = 0, length = this.pool.length; i < length; i++){
    142             if(this.pool[i].free){
    143                 return this.pool[i];
    144             }
    145         }
    146 
    147         return null;
    148     };
    149 
    150     /**
    151      * 封装src属性设置
    152      * 因为改变src属性相当于加载图片,所以把操作封装起来
    153      * @param dom 图像dom对象。对象。
    154      * @param node 节点。对象。
    155      */
    156     ImagePool.prototype.setSrc = function(dom, node){
    157         //设置池中的"线程"为非空闲状态
    158         this.status(dom, false);
    159         //关联节点
    160         this.setNode(dom, node);
    161         //加载图片
    162         dom.src = node.src;
    163     };
    164 
    165     /**
    166      * 更新池中的"线程"状态
    167      * @param dom 图像dom对象。对象。
    168      * @param status 状态。布尔。可选值:true(空闲)|false(非空闲)
    169      */
    170     ImagePool.prototype.status = function(dom, status){
    171         var id = _helpers.getAttr(dom, "id");
    172         this.pool[id].free = status;
    173         //空闲状态,清除关联的节点
    174         if(status){
    175             this.pool[id].node = null;
    176         }
    177     };
    178 
    179     /**
    180      * 更新池中的"线程"的关联节点
    181      * @param dom 图像dom对象。对象。
    182      * @param node 节点。对象。
    183      */
    184     ImagePool.prototype.setNode = function(dom, node){
    185         var id = _helpers.getAttr(dom, "id");
    186         this.pool[id].node = node;
    187         return this.pool[id].node === node;
    188     };
    189 
    190     /**
    191      * 获取池中的"线程"的关联节点
    192      * @param dom 图像dom对象。对象。
    193      */
    194     ImagePool.prototype.getNode = function(dom){
    195         var id = _helpers.getAttr(dom, "id");
    196         return this.pool[id].node;
    197     };
    198 
    199     /**
    200      * 对外接口,加载图片
    201      * @param src 可以是src字符串,也可以是src字符串数组。
    202      * @param options 用户自定义参数。包含:success回调、error回调、once标识。
    203      */
    204     ImagePool.prototype.load = function(src, options){
    205         var srcs = [],
    206             free = null,
    207             length = 0,
    208             i = 0,
    209             //只初始化一次回调策略
    210             notice = (function(){
    211                 if(options.once){
    212                     return function(status, src){
    213                         var g = this.group,
    214                             o = this.options;
    215 
    216                         //记录
    217                         g[status].push(src);
    218                         //判断改组是否全部处理完成
    219                         if(g.success.length + g.error.length === g.count){
    220                             //异步
    221                             //实际上是作为另一个任务单独执行,防止回调函数执行时间过长影响图片加载速度
    222                             setTimeout(function(){
    223                                 o.success.call(null, g.success, g.error, g.count);
    224                             },1);
    225                         }
    226                     };
    227                 }else{
    228                     return function(status, src){
    229                         var o = this.options;
    230 
    231                         //直接回调
    232                         setTimeout(function(){
    233                             o[status].call(null, src);
    234                         },1);
    235                     };
    236                 }
    237             }()),
    238             group = {
    239                 count: 0,
    240                 success: [],
    241                 error: []
    242             },
    243             node = null;
    244         options = options || {};
    245         options.success = options.success || emptyFn;
    246         options.error = options.error || emptyFn;
    247         srcs = srcs.concat(src);
    248 
    249         //设置组元素个数
    250         group.count = srcs.length;
    251         //遍历需要加载的图片
    252         for(i = 0, length = srcs.length; i < length; i++){
    253             //创建节点
    254             node = this.createNode(srcs[i], options, notice, group);
    255             //判断线程池是否有空闲
    256             free = this.getFree();
    257             if(free){
    258                 //有空闲,则立即加载图片
    259                 this.setSrc(free.img, node);
    260             }else{
    261                 //没有空闲,将任务添加到链表
    262                 this.appendNode(node);
    263             }
    264         }
    265     };
    266 
    267     /**
    268      * 获取内部状态信息
    269      * @returns {{}}
    270      */
    271     ImagePool.prototype.info = function(){
    272         var info = {},
    273             length = 0,
    274             i = 0,
    275             node = null;
    276         //线程
    277         info.thread = {};
    278         //线程总数量
    279         info.thread.count = this.pool.length;
    280         //空闲线程数量
    281         info.thread.free = 0;
    282         //任务
    283         info.task = {};
    284         //待处理任务数量
    285         info.task.count = 0;
    286 
    287         //获取空闲"线程"数量
    288         for(i = 0, length = this.pool.length; i < length; i++){
    289             if(this.pool[i].free){
    290                 info.thread.free = info.thread.free + 1;
    291             }
    292         }
    293 
    294         //获取任务数量(任务链长度)
    295         node = this.linkHead;
    296         if(node){
    297             info.task.count = info.task.count + 1;
    298             while(node.next){
    299                 info.task.count = info.task.count + 1;
    300                 node = node.next;
    301             }
    302         }
    303 
    304         return info;
    305     };
    306 
    307     /**
    308      * 创建节点
    309      * @param src 图片路径。字符串。
    310      * @param options 用户自定义参数。包含:success回调、error回调、once标识。
    311      * @param notice 回调策略。 函数。
    312      * @param group 组信息。对象。{count: 0, success: [], error: []}
    313      * @param tr 出错重试次数。数值。默认为0。
    314      * @returns {{}}
    315      */
    316     ImagePool.prototype.createNode = function(src, options, notice, group, tr){
    317         var node = {};
    318 
    319         node.src = src;
    320         node.options = options;
    321         node.notice = notice;
    322         node.group = group;
    323         node.try = tr || 0;
    324 
    325         return node;
    326     };
    327 
    328     /**
    329      * 向任务链表末尾追加节点
    330      * @param node 节点。对象。
    331      */
    332     ImagePool.prototype.appendNode = function(node){
    333         //判断链表是否为空
    334         if(!this.linkHead){
    335             this.linkHead = node;
    336             this.linkNode = node;
    337         }else{
    338             this.linkNode.next = node;
    339             this.linkNode = node;
    340         }
    341     };
    342 
    343     /**
    344      * 删除链表头
    345      */
    346     ImagePool.prototype.shiftNode = function(){
    347         //判断链表是否存在节点
    348         if(this.linkHead){
    349             //修改链表头
    350             this.linkHead = this.linkHead.next || null;
    351         }
    352     };
    353 
    354     /**
    355      * 导出对外接口
    356      * @param max 最大连接数。数值。
    357      * @returns {{load: Function, info: Function}}
    358      */
    359     exports.initImagePool = function(max){
    360 
    361         if(!instance){
    362             instance = new ImagePool(max);
    363             instance.initPool();
    364         }
    365 
    366         return {
    367             /**
    368              * 加载图片
    369              */
    370             load: function(){
    371                 instance.load.apply(instance, arguments);
    372             },
    373             /**
    374              * 内部信息
    375              * @returns {*|any|void}
    376              */
    377             info: function(){
    378                 return instance.info.call(instance);
    379             }
    380         };
    381     };
    382 
    383 }(this));
  • 相关阅读:
    css移除a标签及map、area(图片热区映射)点击过后的边框
    PDA后台运行、安装程序
    如何通过.Net Compact Framework来获得应用程序的当前路径
    mysql 实现row_number,获取上一条,下一条
    关于神经网络算法的 Python例程
    C# Windows Service 用代码实现安装、运行和卸载服务的方法
    sql server 递归查询的使用
    sql server 行转列及列转行的使用
    数据库索引优化
    LiveCharts 提示框(DataTooltip)百分比一直为0.00%解决办法
  • 原文地址:https://www.cnblogs.com/iyangyuan/p/4161083.html
Copyright © 2011-2022 走看看