本文同步自我得博客:http://www.joeray61.com
最近要用javascript
做一个动画功能,为了确保动画在播放的时候能够顺利和平滑,我需要对所用到的图片素材进行预加载,下面跟大家分享一下我实现这个功能的过程
单图片预加载
目前最常见的一种实现方式如下
function preloadImg(url) {
var img = new Image();
img.src = url;
if(img.complete) {
//接下来可以使用图片了
//do something here
}
else {
img.onload = function() {
//接下来可以使用图片了
//do something here
};
}
}
首先实例化一个Image
对象赋值给img
,然后设置img.src
为参数url
指定的图片地址,接着判断img
的complete
属性,如果本地有这张图片的缓存,则该值为true
,此时我们可以直接操作这张图片,如果本地没有缓存,则该值为false
,此时我们需要监听img
的onload
事件,把对img
的操作放在onload
的回调函数里面,经过测试,这种方案基本能够兼容目前所有浏览器
多图片预加载
很多场景下,单图片预加载并不能满足我们的需求,因为像动画这种功能通常都会有很多的图片素材,接下来我们就在原来单图片预加载的基础上来改进我们的函数
function preloadImg(list) {
var imgs = arguments[1] || [], //用于存储预加载好的图片资源
fn = arguments.cal lee;
if(list.length == 0) {
return imgs;
}
var img = new Image();
img.src = list[0];
if(img.complete) {
imgs.push(img);
list.shift();
fn(list, imgs);
}
else {
img.onload = function() {
imgs.push(img);
list.shift();
fn(list, imgs);
};
}
}
var list = [......], //此处省略一万个字符
imgs = preloadImg();
因为帧动画可能需要保证每一帧动画所用的图片的顺序,所以我在这段代码中使用递归的方式,在上一张加载完成之后再去加载下一张图片,每加载一张图片,就把这张图片资源存储到imgs
数组中,并且把这张图片的地址从地址数组list
中去掉,当list
中已经没有地址的时候跳出递归,并返回imgs
数组
设想很美好,现实很残酷,这段代码有2个不能忍受的问题
首先,我很有可能拿不到最后返回的
imgs
数组,因为只要有图片在本地没有缓存,imgs
的存储操作都会放到onload
的回调事件中,而事件监听也属于javascript
中异步操作的一种,在绑定完onload
事件的回调函数后,preloadImg
函数就执行结束了,没有任何返回值,外部imgs
变量接收到的值为undefined
,只有在所有图片都有本地缓存的情况下,外部imgs
变量才能顺利拿到存储了全部预加载图片资源的数组在加载完一张图片之后才去加载下一张,整个预加载图片的过程所需要的时间相对会比较长,用户体验会降低,而且本来异步操作具体速度快的特性,这样的实现方式等于完全弃置了
onload
异步的这个特性
多图片预加载(改进版)
这次我们直接把一个空数组作为参数传进函数,图片全部存储到这个数组里面,下面是改进后的函数代码(假设我们可以使用jQuery
)
function preloadImg(list,imgs) {
var def = $.Deferred(),
len = list.length;
$(list).each(function(i,e) {
var img = new Image();
img.src = e;
if(img.complete) {
imgs[i] = img;
len--;
if(len == 0) {
def.resolve();
}
}
else {
img.onload = (function(j) {
return function() {
imgs[j] = img
len--;
if(len == 0) {
def.resolve();
}
};
})(i);
img.onerror = function() {
len--;
console.log('fail to load image');
};
}
});
return def.promise();
}
var list = [......], //此处省略一万个字符
imgs = [];
$.when(preloadImg(list, imgs)).done(
function() {
//预加载结束
//do something here
}
);
在分别给每一个img
绑定onload
的回调函数时采用了闭包的方式,目的是为了保存住当前的递增变量i
,要是不这么做,结果将会是list
地址中没有本地缓存的图片都存储到imgs
的最后一个元素上
这次每载入一张图片,我们并没有把这张图片的地址从list
数组中去掉,这样后续需要使用list
数组的数据时就能够顺利获取到
在这次的代码中,我们引入了jQuery
的Deferred
对象,这样更方便我把握整个预加载图片的过程,Deferred
对象或者Promise
对象的实现原理可以参看我的这篇文章
thx for reading, hope u enjoy