本文同步自我得博客:http://www.joeray61.comjavascript
最近要用javascript
作一个动画功能,为了确保动画在播放的时候可以顺利和平滑,我须要对所用到的图片素材进行预加载,下面跟你们分享一下我实现这个功能的过程java
目前最多见的一种实现方式以下数组
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
的回调函数里面,通过测试,这种方案基本可以兼容目前全部浏览器promise
不少场景下,单图片预加载并不能知足咱们的需求,由于像动画这种功能一般都会有不少的图片素材,接下来咱们就在原来单图片预加载的基础上来改进咱们的函数浏览器
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