• CSS文件动态加载


    前段时间研究了下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 })();

     >>>我是附件,赶紧猛点击

  • 相关阅读:
    Gradle with Kotlin (二) 项目、Java项目、父子、同级共享代码
    Gradle with Kotlin (一) 安装、DSL、任务、插件
    辛弃疾
    Remote Method Invoke
    Webpack (一) 选项和配置
    《芙蓉女兒誄》
    js 原型链之我见
    js 库
    Gradle
    Spring Boot 入门
  • 原文地址:https://www.cnblogs.com/chyingp/p/load_css.html
Copyright © 2020-2023  润新知