[轉]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的調用。
複製代碼
相關文章
相關標籤/搜索