MORE
了解详细内容
当前位置:首页 > 新闻观点 > 建站知识 >

教您如何实现网站预加载的功能

文章来源:畅想网络 | 本文作者:小编 | 发布时间:2017-05-27 16:39:50 | Tag标签:网站建设知识
05

27 2017

网站开发时经常需要在某个页面需要实现对大量图片的浏览,如果考虑流量的话,大可每个页面只显示一张图片,让用户每看一张图片就需要重新下载一下整个页面。在web2.0时代,更多人愿意用javascript来实现一个图片浏览器,让用户无需等待过长的时间就能看到其他图片。所以,对于一个网站来说,网页的预加载就显得尤为重要。
 
一、常规实现图片预加载的方法
 
知道了一张图片的地址,需要把它在一个固定大小的html容器(可以是div等)里边显示出来,最重要的当然是需要知道这张即将显示的图片的宽和高,然后再结合容器的宽和高,按照一定的缩放比例使图片显示出来。因此,实现图片预加载就成为图片浏览器的核心功能了。
 
做过图片翻转效果的朋友其实都知道,要让图片轮换的时候不出现等待,最好是先让图片下载到本地,让浏览器缓存起来。这时,一般都会用到js里边的Image对象。一般的手段无非这样:
 
function preLoadImg(url) {
   var img = new Image();
   img.src = url;
  }
 
通过调用preLoadImg函数,传入图片的url,就能使图片预先下载下来了。实际上,马海祥觉得这里用到的预下载功能也和这基本一致。图片预下载下来后,通过img的width和height属性,就能知道图片的宽和高了。
 
但是需要考虑到,在做图片浏览器功能时,图片都是实时显示的。比如你点了显示的按钮,这个时候才会调用上边类似的代码来加载图片。因此,如果你直接用img.width的时候,图片还没有完全下载下来。因此,需要用一些异步的方法,等到图片下载完毕的时候才会再对img的width和height进行调用。
 
实现这样的异步方法实际上不难,图片的下载完毕事件也很简单,就是简单的onload事件。因此,我们可以利用下面的代码:
 
function loadImage(url, callback) {
    var img = new Image();
     img.src = url;
     img.onload = function(){ //图片下载完毕时异步调用callback函数。
       callback.call(img);   // 将callback函数this指针切换为img。
     };
  }
 
测试用例:
 
function imgLoaded(){
     alert(this.width);
  }
  <input type="button" value="loadImage" onclick="loadImage('aaa.jpg',imgLoaded)"/>
 
在firefox中测试一下,发现不错,果然和预想的效果一样,在图片下载后,就会弹出图片的宽度来。无论点击多少次或者刷新结果都一样。
 
不过,做到这一步,先别高兴太早——还需要考虑一下浏览器的兼容性,于是,赶紧到ie里边测试一下。没错,同样弹出了图片的宽度。但是,再点击load的时候,情况就不一样了,什么反应都没有了。刷新一下,也同样如此。
 
经过对多个浏览器版本的测试,发现ie6、opera都会这样,而firefox和safari则表现正常。其实,原因也挺简单的,就是因为浏览器的缓存了。当图片加载过一次以后,如果再有对该图片的请求时,由于浏览器已经缓存住这张图片了,不会再发起一次新的请求,而是直接从缓存中加载过来。
 
对于firefox和safari,它们视图使这两种加载方式对用户透明,同样会引起图片的onload事件,而ie和opera则忽略了这种同一性,不会引起图片的onload事件,因此上边的代码在它们里边不能得以实现效果。
 
怎么办呢?最好的情况是Image可以有一个状态值表明它是否已经载入成功了。从缓存加载的时候,因为不需要等待,这个状态值就直接是表明已经下载了,而从http请求加载时,因为需要等待下载,这个值显示为未完成。这样的话,就可以搞定了。
 
经过一些分析,马海祥终于发现一个为各个浏览器所兼容的Image的属性——complete。所以,在图片onload事件之前先对这个值做一下判断即可。最后,代码变成如下的样子:
 
function loadImage(url, callback) {
      var img = new Image(); //创建一个Image对象,实现图片的预下载
       img.src = url;
      if (img.complete) { // 如果图片已经存在于浏览器缓存,直接调用回调函数
           callback.call(img);
          return; // 直接返回,不用再处理onload事件
       }
       img.onload = function () { //图片下载完毕时异步调用callback函数。
           callback.call(img);//将回调函数的this替换为Image对象
       };
  };
 
 
二、动态图片的预加载技术
 
一般来说,技术人员在实现图片预加载的大体思路都是这样的:
 
function loadImage(url, callback) {    
      var img = new Image(); //创建一个Image对象,实现图片的预下载    
      img.src = url;         
      if (img.complete) { // 如果图片已经存在于浏览器缓存,直接调用回调函数    
          callback(img);    
          return; // 直接返回,不用再处理onload事件    
      }     
   img.onload = function () { //图片下载完毕时异步调用callback函数。        
       callback(img);    
    };  
  };
 
小编觉得这个方法功能是ok的,但是有一些隐患,具体如下:
 
1、创建了一个临时匿名函数来作为图片的onload事件处理函数,形成了闭包。
 
相信大家都看到过ie下的内存泄漏模式的文章,其中有一个模式就是循环引用,而闭包就有保存外部运行环境的能力(依赖于作用域链的实现),所以img.onload这个函数内部又保存了对img的引用,这样就形成了循环引用,导致内存泄漏。(这种模式的内存泄漏只存在低版本的ie6中,打过补丁的ie6以及高版本的ie都解决了循环引用导致的内存泄漏问题)。
 
2、只考虑了静态图片的加载,忽略了gif等动态图片,这些动态图片可能会多次触发onload。
 
要解决上面两个问题很简单,其实很简单,代码如下:
 
img.onload = function () { //图片下载完毕时异步调用callback函数。        
      img.onload = null;   
      callback(img);    
  };
 
这样既能解决内存泄漏的问题,又能避免动态图片的事件多次触发问题。
 
在一些相关博文中,也有人注意到了要把img.onload 设置为null,只不过时机不对,大部分文章都是在callback运行以后,才将img.onload设置为null,这样虽然能解决循环引用的问题,但是对于动态图片来说,如果callback运行比较耗时的话,还是有多次触发的隐患的。
 
隐患经过上面的修改后,就消除了,但是这个代码还有优化的余地:
 
if (img.complete) { // 如果图片已经存在于浏览器缓存,直接调用回调函数    
        callback(img);    
        return; // 直接返回,不用再处理onload事件    
  }
 
经过对多个浏览器版本的测试,发现ie、opera下,当图片加载过一次以后,如果再有对该图片的请求时,由于浏览器已经缓存住这张图片了,不会再发起一次新的请求,而是直接从缓存中加载过来。对于 firefox和safari,它们试图使这两种加载方式对用户透明,同样会引起图片的onload事件,而ie和opera则忽略了这种同一性,不会引起图片的onload事件,因此上边的代码在它们里边不能得以实现效果。
 
确实,在ie,opera下,对于缓存图片的初始状态,与firefox和safari,chrome下是不一样的(有兴趣的话,可以在不同浏览器下,测试一下在给img的src赋值缓存图片的url之前,img的状态),但是对onload事件的触发,却是一致的,不管是什么浏览器。产生这个问题的根本原因在于,img的src赋值与 onload事件的绑定,顺序不对(在ie和opera下,先赋值src,再赋值onload,因为是缓存图片,就错过了onload事件的触发)。应该先绑定onload事件,然后再给src赋值,代码如下:
 
function loadImage(url, callback) {    
      var img = new Image(); //创建一个Image对象,实现图片的预下载    
      img.onload = function(){
          img.onload = null;
          callback(img);
      }
      img.src = url;
  }
 
三、比onload更快获取图片尺寸的预加载技术
 
大部分技术人员使用预加载获取图片大小的方法,基本都是通过如下的代码实现的:
 
var imgLoad = function (url, callback) {
      var img = new Image();
      img.src = url;
      if (img.complete) {
          callback(img.width, img.height);
      } else {
          img.onload = function () {
              callback(img.width, img.height);
              img.onload = null;
          };
      };
  };
 
从以上代码,我们可以看到上面必须等待图片加载完毕才能获取尺寸,其速度马海祥还真不敢恭维,对此,我们需要改进。
 
web应用程序区别于桌面应用程序,响应速度才是最好的用户体验。如果想要速度与优雅兼得,那就必须提前获得图片尺寸,如何在图片没有加载完毕就能获取图片尺寸呢?
 
据马海祥十多年的上网经验:浏览器在加载图片的时候你会看到图片会先占用一块地然后才慢慢加载完毕,并且不需要预设width与height属性,因为浏览器能够获取图片的头部数据。基于此,只需要使用javascript定时侦测图片的尺寸状态便可得知图片尺寸就绪的状态。
 
当然实际中会有一些兼容陷阱,如width与height检测各个浏览器的不一致,还有webkit new Image()建立的图片会受以处在加载进程中同url图片影响,经过反复测试后的最佳处理方式:
 
// 更新:
  // 05.27: 1、保证回调执行顺序:error > ready > load;2、回调函数this指向img本身
  // 04-02: 1、增加图片完全加载后的回调 2、提高性能
  /**
   * 图片头数据加载就绪事件 - 更快获取图片尺寸
   * @version 2011.05.27
   * @author  TangBin
   * @see    http://www.mahaixiang.cn/wyzz/546.html
   * @param   {String}    图片路径
   * @param   {Function}  尺寸就绪
   * @param   {Function}  加载完毕 (可选)
   * @param   {Function}  加载错误 (可选)
   * @example imgReady('http://www.mahaixiang.cn/uploads/allimg/1405/1-14050212013ML.jpg', function () {
          alert('size ready: width=' + this.width + '; height=' + this.height);
      });
   */
var imgReady = (function () {
    var list = [], intervalId = null,
    // 用来执行队列
    tick = function () {
        var i = 0;
        for (; i < list.length; i++) {
            list[i].end ? list.splice(i--, 1) : list[i]();
        };
        !list.length && stop();
    },
    // 停止所有定时器队列
    stop = function () {
        clearInterval(intervalId);
        intervalId = null;
    };
    return function (url, ready, load, error) {
        var onready, width, height, newWidth, newHeight,
            img = new Image();
        img.src = url;
        // 如果图片被缓存,则直接返回缓存数据
        if (img.complete) {
            ready.call(img);
            load && load.call(img);
            return;
        };
        width = img.width;
        height = img.height;
        // 加载错误后的事件
        img.onerror = function () {
            error && error.call(img);
            onready.end = true;
            img = img.onload = img.onerror = null;
        };
        // 图片尺寸就绪
        onready = function () {
            newWidth = img.width;
            newHeight = img.height;
            if (newWidth !== width || newHeight !== height ||
                // 如果图片已经在其他地方加载可使用面积检测
                newWidth * newHeight > 1024
            ) {
                ready.call(img);
                onready.end = true;
            };
        };
        onready();
        // 完全加载完毕的事件
        img.onload = function () {
            // onload在定时器时间差范围内可能比onready快
            // 这里进行检查并保证onready优先执行
            !onready.end && onready();
            load && load.call(img);
            // IE gif动画会循环执行onload,置空onload即可
            img = img.onload = img.onerror = null;
        };
        // 加入队列中定期执行
        if (!onready.end) {
            list.push(onready);
            // 无论何时只允许出现一个定时器,减少浏览器性能损耗
            if (intervalId === null) intervalId = setInterval(tick, 40);
        };
    };
})();
 
这样的方式获取摄影级别照片尺寸的速度往往是onload方式的几十多倍,而对于web普通(800×600内)浏览级别的图片能达到秒杀效果。看了这个再回忆一下你见过的web相册,是否绝大部分都可以重构一下的。
相关内容
网站导航:首 页 | 关于我们 | 服务项目 | 新闻观点 | 客户案例 | 解决方案 | 联系我们