本文同步自我得博客: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