前段時間研究了下JS動態加載和執行順序依賴的東東,把LABJS的源碼從頭扒了下:LABJS淺析。對於JS加載執行以及下載監控這,項目組在這塊做的東西不少,但對於CSS加載這塊的質量監控,力度就小得多了。原因很簡單:JS下載失敗或出錯,這個頁面基本就廢了。CSS下載失敗,大部分情況下頁面還是可用的,雖然會比較臭。
但對於OPA來說,情況可能就完全不同了,CSS文件加載失敗的影響相對就比較大了。
本着生命不息折騰不已的精神,又倒騰了下CSS加載這塊的內容,成果如下,鑒於今天晚上11點才下班回家現在已經很困,就直接上代碼了,詳細分析后面補上~
刪掉注釋空行其實代碼很少,關於如何測試、API調用都在開頭聲明了,demo可下載 附件 :)
1 /** 2 * CSS文件加載器,主要功能:動態加載CSS文件,支持加載完成時候的回調(成功 and 失敗 情況下) 3 * 源碼實現借鑒:https://github.com/rgrove/lazyload/commit/6caf58525532ee8046c78a1b026f066bad46d32d 4 * 更多關於CSS加載的坑的討論,見:http://www.phpied.com/when-is-a-stylesheet-really-loaded/ 5 * 6 * 測試方法:1)將文件解壓到服務器上(或用fiddler等本地文件替換) 2)訪問demo.html即可 7 * 8 * @example 9 * loadCSS.load('style.css'); 10 * loadCSS.load('style.css', function(){ alert('style.css loaded'); }); 11 * loadCSS.load('style.css', function(obj){ alert('age is '+obj.age); }, {age: 24}); 12 * loadCSS.load(['a.css', 'b.css'], function(){ alert('a.css and b.css are all loaded'); }); 13 * 14 * 更多說明:目前只能判斷CSS文件加載事件是否完成,至於是否出現404、5XX等,還判斷不了 15 * 曲線救國:回調里判斷CSS里定義的某個樣式是否存在/生效,借此判斷CSS是否下載成功,如下 16 * loadCSS.load('sytle.css', function(){ 17 * var div = document.createElement('div'); 18 * div.className = 'pre_defined_class'; //pre_defined_class 為測試用的預定義類,假設為 .pre_defined_class{display:none;} 19 * var value = getStyle(div, 'display'); 20 * if(value=='none'){ 21 * //成功 22 * }else{ 23 * //失敗 24 * } 25 * }) 26 * 27 * @version 1.0 28 * @TODO: 1)靜態加載的CSS文件的檢測(是否成功加載)2)加載配置項 29 * @author casper chyingp@gmail.com 30 * http://www.cnblogs.com/chyingp 31 * http://www.zcool.com.cn/u/346408 32 * 33 */ 34 var LoadCSS = (function () { 35 36 //配置項,未實現 37 var CFG = { 38 POLL_INTERVAL: 50, 39 MAX_TIME: 10 40 }; 41 42 var head = document.head || document.getElementsByTagName('head')[0]; 43 var styleSheets = document.styleSheets 44 var env = getEnv(); //獲取用戶代理信息,為瀏覽器差異化加載提供判斷依據 45 var queue = []; //CSS加載隊列 46 /* 47 @格式1 queue隊列內元素格式 48 { 49 urls: ['a.css', 'b.css', 'd.css'], 50 callback: function(param){}, //urls里面所有CSS文件加載完成后的回調方法,可選 51 obj: {age:24} //callback回調方法傳入的實參 52 } 53 */ 54 55 56 function indexOf(arr, ele){ 57 var ret = -1; 58 for(var i=0,len=arr.length; i<len; i++){ 59 if(arr[i]==ele) ret = i; 60 } 61 return ret; 62 } 63 64 /** 65 * @private 66 * @description 返回用戶瀏覽器代理信息,為判斷不同瀏覽器提供依據 67 * @return {Object} 格式見內部代碼 68 */ 69 function getEnv() { 70 var ua = navigator.userAgent; 71 var env = {}; 72 73 (env.webkit = /AppleWebKit\//.test(ua)) 74 || (env.ie = /MSIE/.test(ua)) 75 || (env.opera = /Opera/.test(ua)) 76 || (env.gecko = /Gecko\//.test(ua)) 77 || (env.unknown = true); 78 79 return env; 80 } 81 82 /** 83 * @private 84 * @description gecko內核的瀏覽器輪詢檢測方法 85 * 參考:http://www.zachleat.com/web/2010/07/29/load-css-dynamically/ 86 * @param {HTMLElement} node style節點,node.nodeName == 'STYLE' 87 * @param {Object} queueObj 見@格式1 88 */ 89 function pollGecko(node, queueObj) { 90 try { 91 92 node.sheet.cssRules; 93 94 } catch (ex) { 95 96 node.pollCount++; 97 98 if (node.pollCount < 200) { 99 100 setTimeout(function () { 101 pollGecko(node, queueObj); 102 }, 50); 103 104 } else { 105 106 finishLoading(node.href, queueObj); //用不用略做些延遲,防止神一樣的渲染問題?? 107 108 } 109 110 return; 111 } 112 113 finishLoading(node.href, queueObj); 114 } 115 116 117 /** 118 * @private 119 * @description webkit內核的瀏覽器輪詢檢測方法 120 * @param {HTMLElement} node link節點,node.nodeName == 'LINK' 121 * @param {Object} queueObj 見@格式1 122 */ 123 function pollWebKit(node, queueObj) { 124 125 for(var i=styleSheets.length; i>0; i--){ 126 127 if(styleSheets[i-1].href===node.href){ 128 finishLoading(node.href, queueObj); 129 return; 130 } 131 } 132 133 node.pollCount++; //輪詢次數加1 134 135 if (node.pollCount < 200) { 136 setTimeout(function(){ 137 pollWebKit(node, queueObj); 138 }, 50); 139 } else { 140 finishLoading(node.href, queueObj); 141 } 142 } 143 144 function checkSucc(className, attr, value){ 145 var div = document.createElement('div'); 146 div.style.cssText += 'height:0; line-height:0; visibility:hidden;'; 147 div.className = className; 148 document.body.appendChild(div); 149 150 return getComputedStyle(div, attr)==value; 151 } 152 153 /** 154 * @description 獲取節點樣式值——只能獲取比較簡單的樣式的值,一些兼容性問題不是重點,在這里不做處理,有興趣可以看下jquery源碼 155 * @param {HTMLElement} node dom節點 156 * @param {String} attr 樣式名字,如display、visibility等 157 */ 158 function getComputedStyle(node, attr){ 159 var getComputedStyle = window.getComputedStyle; 160 if(getComputedStyle){ 161 return getComputedStyle(node, null)[attr]; 162 }else if(node.currentStyle){ 163 return node.currentStyle[attr]; 164 }else{ 165 return node.style[attr]; 166 } 167 } 168 169 /** 170 * @private 171 * @description url對應的CSS文件加載完成時的回調(404也包括在內) 172 * @param {String} url CSS文件的url 173 * @param {Object} queueObj 見@格式1 174 */ 175 function finishLoading(url, queueObj){ 176 var index = indexOf(queueObj.urls, url); 177 queueObj.urls.splice(index, 1); 178 179 if(!queueObj.urls.length){ 180 queueObj.callback(queueObj.obj); 181 182 index = indexOf(queue, queueObj); 183 queue.splice(index, 1); 184 } 185 } 186 187 /** 188 * @description 加載CSS的方法 189 * @param {Array} urls 加載的CSS文件名隊列 190 * @param {Function} [callback] CSS文件隊列全部加載完的回調 191 * @param {Object} obj callback的參數 192 * @param {Object} context 193 * @return {Undefined} 194 */ 195 function loadCSS(urls, callback, obj) { 196 var queueObj = { 197 urls: urls, 198 callback: callback, 199 obj: obj 200 } 201 queue.push(queueObj); 202 203 var pendingUrls = queueObj.urls; 204 for (var i = 0, len = pendingUrls.length; i < len; ++i) { 205 206 var url = pendingUrls[i]; 207 var node ; 208 if(env.gecko){ 209 node = document.createElement('style'); 210 }else{ 211 node = document.createElement('link'); 212 node.rel = 'stylesheet'; 213 node.href = url; 214 } 215 //node.setAttribute('charset', 'utf-8'); //設不設置有木有影響,持保留態度 216 217 if (env.gecko || env.webkit) { //老版本webkit、gecko不支持onload 218 219 node.pollCount = 0; 220 queueObj.urls[i] = node.href; //輪詢判斷的時候用到,因為不同瀏覽器里面取到的node.href值會不一樣,有的只有文件名,有的是完整文件名?(相對路徑、絕對路徑) 221 222 if (env.webkit) { //之所以要用輪詢,后面討論,@TODO: 新版本的webkit已經支持onload、onerror,優化下? 223 224 pollWebKit(node, queueObj); 225 226 } else { 227 228 node.innerHTML = '@import "' + url + '";'; //為什么這樣做,猛點擊這里:http://www.phpied.com/when-is-a-stylesheet-really-loaded/ 229 pollGecko(node, queueObj); 230 } 231 232 } else { 233 234 node.onload = node.onerror = function(){ 235 finishLoading(this.href, queueObj); 236 }; 237 } 238 239 head.appendChild(node); 240 } 241 } 242 243 //---------------------- 對外接口!--------------------------- 244 return { 245 246 /** 247 * @description 加載CSS文件 248 * 考慮:成功回調,錯誤回調分開? 249 * @param {Array|String} urls 要加載的CSS文件的文件名(相對路徑,或絕對路徑),比如:'style.css', ['style.css', 'test.css'] 250 * @param {Function} [callback] 可選:文件加載完成后的回調(成功;或失敗,如404、500等) 251 * @param {Object} [obj] 可選:回調執行時傳入的參數 252 */ 253 load: function (urls, callback, obj) { 254 loadCSS([].concat(urls), callback || function(){}, obj || {}); 255 } 256 257 }; 258 })();