天天看點

加載檔案CSS檔案動态加載(續)—— 殘酷的真相

題記:寫這篇部落格要主是加深自己對加載檔案的認識和總結現實算法時的一些驗經和訓教,如果有錯誤請指出,萬分感激。

    note:本文重要參考了Stoyan Stefanov的文章《When is a stylesheet really loaded?》

    在之前的文章《CSS檔案态動加載》中,我們提到了在态動加載CSS檔案的時候,如何檢測加載是否是成完。意注,這裡的加載成完包含了兩種情況:

    1)加載功成 2)加載失敗

    也就是說,這裡并沒有将功成與失敗的情況辨識開來。看到這裡你可能困惑了,就态動加載個CSS檔案,洋洋灑灑寫了一兩百行代碼,連是否是加載功成/失敗都沒能辨識開來,這仿佛有些可不懂得。

美妙的假象——如何斷判CSS加載成完

    這裡先不抛出論斷,而是先思考一個問題:如何态動加載CSS檔案?

    很單簡,就上面幾行代碼:

var node = document.createElement('link');
node.rel = 'stylesheet';
node.href = 'style.css';
document.getElementsByTagName('head')[0].appendChild(node);      

很好,那麼接來下的問題是: 怎麼斷判CSS檔案是否是加載成完?

    那還不單簡,幾行代碼就搞定的情事,前端的老朋友onload、onerror閃亮退場:

var node = document.createElement('link');
node.rel = 'stylesheet';
node.type = 'text/css';
node.href = 'style.css';
node.onload = function(){
    alert('加載功成啦!');
};
node.onerror = function(){
    alert('加載失敗啦!');
};
document.getElementsByTagName('head')[0].appendChild(node);      

嗯,這麼寫是沒錯。。。從理論上。。。看下HTML 5裡關于資源加載成完的述描,歸納綜合起來就是:

  1. CSS檔案加載功成,在link節點上觸發load件事
  2. CSS檔案加載失敗,在link節點上觸發error件事

    Once the attempts to obtain the resource and its critical subresources are complete, the user agent must, if the loads were successful, queue a task to fire a simple event named 

load

 at the 

link

 element, or, if the resource or one of its critical subresources failed to completely load for any reason (e.g. DNS error, HTTP 404 response, a connection being prematurely closed, unsupported Content-Type), queue a task to fire a simple event named 

error

 at the 

link

element. Non-network errors in processing the resource or its subresources (e.g. CSS parse errors, PNG decoding errors) are not failures for the purposes of this paragraph.

    看上去很美妙的模樣。我們曉得,這個界世從來都不美完,最少對于前端說來,這個界世跟美完這個詞沒半毛錢關系。JS中始終為人诟病的文法,浏覽器糟的相容性問題神馬的。将上面那段代碼放到IE(本版9及以下,10沒有測過)面裡,将檔案接鍊向指一個不存在的檔案,比如在fiddler裡将傳回替換成404:

var node = document.createElement('link');
node.href = 'none_exist_file.css';  //其他性屬設定略省
node.onload = function(){
    alert('加載功成啦!');
};
node.onerror = function(){
    alert('加載失敗啦!');
};
document.getElementsByTagName('head')[0].appendChild(node);      

于是你看到一句麗華麗的提示:

    “加載功成啦!”

    看到這裡是否是對這個界世産生了深深的困惑——我認承我時當把軟微開辟IE浏覽器的兄弟們家全都問候了一下。

    好吧,這篇文章是不并關于IE的吐槽文,在CSS檔案加載态狀的檢測這個問題上,IE的表示雖不美完,但相比之下還不算别特糟。

    慢着!意思是——還有更糟的?是的,比如初期本版的firefox,連onload都不撐支。

如何斷判CSS檔案加載成完——五種計劃

    抛開一切的怨埋與不滿,按照過往的教訓,如何斷判一個檔案是否是加載成完?一般有以下幾種方法:

  1. 監聽link.load
  2. 監聽link.addEventListener('load', loadHandler, false);
  3. 監聽link.onreadystatechange
  4. 監聽document.styleSheets的化變
  5. 通過setTimeout時定檢查你先預建創好的簽标的款式是否是發生化變(該簽标付與了在态動加載的CSS檔案裡才聲明的款式)

 示例代碼如下:

//計劃一
link.onload = function(){
    alert('CSS onload!');
}      
//計劃二
link.addEventListener('load', function(){
    alert('addEventListener loaded !');
}, false);      
//計劃三
link.onreadystatechange = function(){
    var readyState = this.readyState;
    if(readyState=='complete' || readyState=='loaded'){
        alert('readystatechange loaded !');
    }
};      
//計劃四
var curCSSNum = document.styleSheets.length;
var timer = setInterval(function(){
    if(document.styleSheets.length>curCSSNum){
        //意注:當你一次性加載很多檔案的時候,要需斷判究竟是哪個檔案加載成完了
        alert('document.styleSheets loaded !');
        clearInterval(timer);
    }
}, 50);      
var div = document.createElement('div');
div.className = 'pre_defined_class';    //加載的CSS檔案裡才有的款式
var timer = setTimeout(function(){
    //假設getStyle方法的作用:獲得簽标特性款式的值
    if(getStyle(div, 'display')=='none'){
        alert('setTimeout check style loaded !');
        return;
    }
    setTimeout(arguments.callee, 50);    //續繼檢查
}, 50);      

五種計劃的際實測試結果

    際實測試的結果如何呢?如下:

    每日一道理

成熟是一種明亮而不刺眼的光輝,一種圓潤而不膩耳的音響,一種不要需對别人察顔觀色的從容,一種于終停止了向周圍申訴求告的大氣,一種不理會哄鬧的微笑,一種洗刷了偏激的淡漠,一種無須聲張的厚實,一種其實不陡峭的高度。

浏覽器 檢查onload(onload/addEventListener) link.onreadystatechange 檢查document.styleSheets.length 檢查特定簽标的款式
IE ok,但404等情況也會觸發onload

可行,但404等情況下readyState

也為complete或loaded

 測試結果與網上說的不一緻

需再加驗證

ok
chrome

1、老本版:not ok

2、新本版:ok(如24.0)

 not ok ok(檔案加載成完後才變改length)  ok 
firefox

1、老本版:not ok(3.X)

2、新本版:ok(如16.0)

 not ok  not ok(節點插入時,length就變改) ok 
safari

1、老本版:not ok(?)

2、新本版:ok(如6.0) 

 not ok  ok(檔案加載成完後才變改length) ok 
opera ok  not ok   not ok(節點插入時,length就變改) ok

    計劃一、計劃二本質上是一樣的;而如果可能的話,stoyan議建盡可能不必計劃五,原因如下:

    1)性能銷開(計劃四也好不到哪去)

    2)需添加額定無用款式,要需對CSS檔案有充足的控制權(CSS檔案可能是不并自己的隊團在護維)

    那好,時暫将計劃五消除在外(其實相容性是最好的),從上表格可以曉得,各浏覽器别分可采取計劃如下:

浏覽器 可采取計劃
IE 計劃一、計劃二、計劃三
chrome 計劃四
firefox
safari 計劃四
opera 計劃一、二

    firefox竟然。。。霎時光心坎萬千隻草泥馬在歡快地奔騰。。。對于firefox,stoyan大神也嘗試了其他方法,比如:

    1、MozAfterPaint(這是神馬還沒查,總之失敗了,求導指~)

    2、document.styleSheets[n].cssRules,隻有當CSS檔案加載來下的時候,document.styleSheets[n].cssRules才會發生化變;但是,由于ff 3.5的全安制限,如果CSS檔案跨域的話,JS拜訪document.styleSheets[n].cssRules會錯出

如在何老本版的firefox裡斷判CSS是否是加載成完

    就在stoyan大神即将失望之際,Zach Leatherman 童鞋發現了firefox下的處理計劃:

  1. you create a 

    style

     element, not a 

    link

  2. add 

    @import "URL"

  3. poll for access to that style node's 

    cssRules

     collection

    這個計劃利用了上面提到的第二點,同時處理了跨域的問題。代碼如下(代碼引用自原文):

var style = document.createElement('style');
style.textContent = '@import "' + url + '"';
 
var fi = setInterval(function() {
  try {
    style.sheet.cssRules; // <--- MAGIC: only populated when file is loaded
    CSSDone('listening to @import-ed cssRules');
    clearInterval(fi);
  } catch (e){}
}, 10);  
 
head.appendChild(style);      

根據stoyan、Zach的路思,  Ryan Grove 在LazyLoad裡将現實,有趣興的可以看下  源代碼 

    Ryan Grove的代碼有些小問題,比如:

    1、CSS檔案的阻塞式加載,比如加載A.css、B.css,要需等A.css加載完了,才開始加載B.css

    2、某些斷判語句的失誤,緻導CSS檔案記錄功成的情況下,檢測失誤(見pollWebkit方法第一個while環循)

    盡管如此,還是要感激Ryan的動勞(撒花),LZ根據際實要需,将LazyLoad裡js加載分部的代碼剔除,并上面提到的兩個較比顯明的bug fix了,修改後的源碼以及demo可見參《CSS檔案态動加載》一文 :)

如何斷判CSS檔案加載失敗

    始終到這裡,我們于終處理了如何檢測CSS檔案是否是加載成完的問題。 接來下又有一個峻嚴的問題擺在我們眼前:如何斷判一個檔案加載失敗?

    不要忘了onerror童鞋!onerror的撐支情況如何呢?—— 際實測試了下,情況其實不悲觀,直接引用先輩的動勞結晶,原文接鍊如下:http://seajs.org/tests/research/load-js-css/test.html

    css

    : Chrome / Safari: -

    WebKit >= 535.23 後撐支 onload / onerror

    - 之前的本版無任何件事觸發 Firefox: -

    Firefox >= 9.0 後撐支 onload / onerror

    - 之前的本版無任何件事觸發 Opera: - 會觸發 onload -

    但 css 404 時,不會觸發 onerror

    IE6-8: -

    下載下傳功成和失敗時都市觸發 onload 和 onreadystatechange,無 onerror

    IE9: - 同 IE6-8 - onreadystatechange 會重複觸發 處理計劃: - Old WebKit 和 Old Firefox 下,用 poll 方法:

    load-css.html

    - 其他浏覽器用 onload / onerror 足不: -

    Opera 下如果 404,沒有任何件事觸發,有可能緻導賴依該 css 的子產品始終處于等待态狀

    - IE6-8 下辨識不出 onerror - poll 探測難以辨識出 onerror

    可見,之前的計劃,其實不能美完處理“斷判CSS檔案加載失敗”這個問題(當相使人喪沮,有主張的童鞋千萬要留言告訴我 TAT)

    現在有兩種路思,其實并沒有全完處理問題:

    1、逾時失敗定判:設定t值,當加載時光過超t時,認定其加載失敗(單簡粗魯,現在采取方法)

    2、定判加載成完後,通過上面的計劃五(檢查款式),斷判CSS檔案是否是加載失敗 —— 提前是沒有被認定為“逾時失敗”

    多方讨教後,外門部的事同tom供給了一個不錯的的路思,該現實計劃經已有線上項目作為實際撐支:JSONP

CSS加載失敗斷判——不一樣的路思JSONP

    假設有style.css(際實想要加載的檔案)、style.js;style.js裡是個回調方法CSSLoadedCallback,CSSLoadedCallback做兩件情事

    1)打标記,辨別style.js加載功成(即面頁拿到了style.css裡的款式字元串)

    2)建創link簽标,并将CSSLoadedCallback裡傳入的款式字元串寫到link簽标裡

    style.js裡的代碼大緻如下:

//第一個參數style.css為際實想要加載的CSS的檔案名
//第二個參數:style.css裡的款式
CSSLoadedCallback("style.css", ".hide{display:'none';} .title{font-size:14px;}");      

 于是,由先原的斷判CSS是否是加載失敗,轉為斷判JS是否是加載失敗;關于JS是否是加載失敗,先輩的測試如下,原文接鍊請點選 這裡:

 關于IE6-8沒法辨識onerror,在這裡是不并問題(可通過斷判變量是否是存在現實),就是說JSONP是個靠譜的處理計劃。

    js: Chrome / Firefox / Safari / Opera: - 下載下傳功成時觸發 onload, 下載下傳失敗時觸發 onerror - 下載下傳功成括包 200, 302, 304 等,隻要下載下傳來下了就好 - 下載下傳失敗指沒下載下傳來下,比如 404 - Opera 老本版對 empty.js 這類空檔案時不會觸發 onload,新本版已無問題 IE6-8: - 下載下傳功成和失敗時都市觸發 onreadystatechange, 無 onload / onerror - 功成和失敗的義含同上 IE9: - 有 onload / onerror,同時也有 onreadystatechange 處理計劃: - 在 Firefox、Chrome、Safari、Opera、IE9 下,用 onload + onerror - 在 IE6-8 下,用 onreadystatechange 足不: -

    IE6-8 下辨識不出 onerror

小結:

    1、可檢測CSS檔案是否是加載功成(通過多種手段斷判檔案加載成完的情況下,結合檢查簽标款式的方法)

    2、可大緻檢測CSS檔案是否是加載失敗(提前是斷判CSS經已加載成完,在chrome、opera老本版裡沒法确準斷判)

    3、通過JSONP方法可确準斷判檔案是否是加載功成、失敗

 寫在前面:

    本文參考了多篇外站術技部落格的文章,若有引用外站容内,但未聲明的情況,敬請指處!

     文中示例若有漏錯,請指出;如認為文章對您有效,可點選“推薦” :)

    參考接鍊:

    http://www.phpied.com/when-is-a-stylesheet-really-loaded/

    https://github.com/seajs/seajs/blob/master/src/util-request.js

    https://github.com/rgrove/lazyload/commit/6caf58525532ee8046c78a1b026f066bad46d32d

    http://www.zachleat.com/web/load-css-dynamically/

文章結束給大家分享下程式員的一些笑話語錄: 很多所謂的牛人也不過如此,離開了你,微軟還是微軟,Google還是Google,蘋果還是蘋果,暴雪還是暴雪,而這些牛人離開了公司,自己什麼都不是。