[转]Javascript实现图片的预加载

作过图片翻转效果的朋友其实都知道,要让图片轮换的时候不出现等待,最好是先让图片下载到本地,让浏览器缓存起来。这时,通常都会用到js里边的Image对象。通常的手段无非这样:chrome

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的时候,状况就不同了,什么反应都没有了。刷新一下,也一样如此。

通过对多个浏览器版本的测试,发现ie六、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事件处理函数,造成了闭包。this

相信你们都看到过ie下的内存泄漏模式的文章,其中有一个模式就是循环引用,而闭包就有保存外部运行环境的能力(依赖于做用域链的实现),因此 img.onload这个函数内部又保存了对img的引用,这样就造成了循环引用,致使内存泄漏。(这种模式的内存泄漏只存在低版本的ie6中,打过补丁 的ie6以及高版本的ie都解决了循环引用致使的内存泄漏问题)。url

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;
}
 
 这样内存泄漏,动态图片的加载问题都获得了解决,并且也以统一的方式,实现了callback的调用。
复制代码
相关文章
相关标签/搜索