动态加载css方法实现和深入解析


Posted in Javascript onJanuary 18, 2017

一、方法引用来源和应用

此动态加载css方法 loadCss,剥离自Sea.js,并做了进一步的优化(优化代码后续会进行分析)。

因为公司项目需要用到懒加载来提高网站加载速度,所以将非首屏渲染必需的css文件进行动态加载操作。

二、优化后的完整代码

/*
* @function 动态加载css文件
* @param {string} options.url -- css资源路径
* @param {function} options.callback -- 加载后回调函数
* @param {string} options.id -- link标签id
*/
function loadCss(options){
 var url = options.url,
 callback = typeof options.callback == "function" ? options.callback : function(){},
 id = options.id,
 node = document.createElement("link"),
 supportOnload = "onload" in node,
 isOldWebKit = +navigator.userAgent.replace(/.*(?:AppleWebKit|AndroidWebKit)\/?(\d+).*/i, "$1") < 536, // webkit旧内核做特殊处理
 protectNum = 300000; // 阈值10分钟,一秒钟执行pollCss 500次
 node.rel = "stylesheet";
 node.type = "text/css";
 node.href = url;
 if( typeof id !== "undefined" ){
 node.id = id;
 }
 document.getElementsByTagName("head")[0].appendChild(node);
 // for Old WebKit and Old Firefox
 if (isOldWebKit || !supportOnload) {
 // Begin after node insertion
 setTimeout(function() {
  pollCss(node, callback, 0);
 }, 1);
 return;
 }
 if(supportOnload){
 node.onload = onload;
 node.onerror = function() {
  // 加载失败(404)
  onload();
 }
 }else{
 node.onreadystatechange = function() {
  if (/loaded|complete/.test(node.readyState)) {
  onload();
  }
 }
 }
 function onload() {
 // 确保只跑一次下载操作
 node.onload = node.onerror = node.onreadystatechange = null;
 // 清空node引用,在低版本IE,不清除会造成内存泄露
 node = null;
 callback();
 }
 // 循环判断css是否已加载成功
 /*
 * @param node -- link节点
 * @param callback -- 回调函数
 * @param step -- 计步器,避免无限循环
 */
 function pollCss(node, callback, step){
 var sheet = node.sheet,
  isLoaded; 
 step += 1;
 // 保护,大于10分钟,则不再轮询
 if(step > protectNum){
  isLoaded = true;
  
  // 清空node引用
  node = null;
  callback();
  return;
 }
 if(isOldWebKit){
  // for WebKit < 536
  if(sheet){
  isLoaded = true;
  }
 }else if(sheet){
  // for Firefox < 9.0
  try{
  if(sheet.cssRules){
   isLoaded = true;
  }
  }catch(ex){
  // 火狐特殊版本,通过特定值获知是否下载成功
  // The value of `ex.name` is changed from "NS_ERROR_DOM_SECURITY_ERR"
  // to "SecurityError" since Firefox 13.0. But Firefox is less than 9.0
  // in here, So it is ok to just rely on "NS_ERROR_DOM_SECURITY_ERR"
  if(ex.name === "NS_ERROR_DOM_SECURITY_ERR"){
   isLoaded = true;
  }
  }
 }
 setTimeout(function() {
  if(isLoaded){
  // 延迟20ms是为了给下载的样式留够渲染的时间
  callback();
  }else{
  pollCss(node, callback, step);
  }
 }, 20);
 }
}

三、解析代码

一、参数

本方法支持三个参数,可进行扩展。

1.1 opations.url

url是需要引入的css资源路径,也即标签的href属性内容。

1.2 options.id

id是标签的id属性。这个参数为非必要参数,可不传。主要作用是标记当前标签,方便js进行查找,以确定是否已加载某个css文件。

1.3 options.callback

callback是css文件加载完成后会调用的回调函数。也存在特殊场景下,文件加载失败,回调函数仍旧执行的情况。

二、生成标签,并插入头部head,进行加载资源

var url = options.url,
 callback = typeof options.callback == "function" ? options.callback : function(){},
 id = options.id,
 node = document.createElement("link");
node.rel = "stylesheet";
node.type = "text/css";
node.href = url;
if( typeof id !== "undefined" ){
 node.id = id;
}
document.getElementsByTagName("head")[0].appendChild(node);

生成一个dom节点,然后配置好rel、type、href等必需的属性值,以便浏览器能正常解析链接的资源。

接着,查找到head节点,将节点插入。

三、实现css资源下载状态监控的pollCss方法

pollCss方法的职责是判断插入的link节点,也即node变量反馈资源是否已加载完成。

3.1 判断的主要依据

浏览器加载css资源,会给该link节点生成sheet属性,可以根据浏览器不同,读取sheet属性相关内容,来判断是否已经加载完成。所以第一句语句var sheet = node.sheet首先要做的就是获取sheet属性值。

3.2 普通浏览器判断

try{
 if(sheet.cssRules){
 isLoaded = true;
 }
}catch(ex){
 // 火狐特殊版本,通过特定值获知是否下载成功
 // The value of `ex.name` is changed from "NS_ERROR_DOM_SECURITY_ERR"
 // to "SecurityError" since Firefox 13.0. But Firefox is less than 9.0
 // in here, So it is ok to just rely on "NS_ERROR_DOM_SECURITY_ERR"
 if(ex.name === "NS_ERROR_DOM_SECURITY_ERR"){
 isLoaded = true;
 }
}

如果读取sheet.cssRules有值,证明css资源已经链接进页面,并开始解析。此时可以判断资源加载成功。

如果读取失败,则根据抛错内容,判断是否有特定name属性ex.name === "NS_ERROR_DOM_SECURITY_ERR"。存在,则代表是低版本火狐(9.0以前),且资源已经加载成功。

3.3 旧webkit内核浏览器判断

var isOldWebKit = +navigator.userAgent.replace(/.*(?:AppleWebKit|AndroidWebKit)\/?(\d+).*/i, "$1") < 536; // webkit旧内核做特殊处理
if(isOldWebKit){
 // for WebKit < 536
 if(sheet){
 isLoaded = true;
 }
}

如果是webkit旧内核浏览器,则只需要判断sheet属性值存在,则代表资源加载完成。

3.4 增加多次循环检测

setTimeout(function() {
 if(isLoaded){
 // 延迟20ms是为了给下载的样式留够渲染的时间
 callback();
 }else{
 pollCss(node, callback, step);
 }
}, 20);

触发pollCss方法后,可能第一次检测sheet值,会检测不到。也就代表还没加载完成。所以需要进行轮询。这里是隔20ms进行一次问询,直到资源加载完成为止。

3.5 轮询容错(针对Sea.js源码的优化)

css资源加载也有可能出错的时机存在,而且存在不触发onerror方法的可能性。如果不加一个保护,则轮询可能一直持续下去,所以需要有一个极限阈值。

var protectNum = 300000, // 阈值10分钟,一秒钟执行pollCss 500次
 step = 0;
// 很多代码....
step += 1;
// 保护,大于10分钟,则不再轮询
if(step > protectNum){
 isLoaded = true;
 // 清空node引用
 node = null;
 callback();
 return;
}

这里的阈值是轮询10分钟,如果10分钟后,仍然不符合条件,则默认资源已下载完成,执行callback方法,并清空node引用。

四、确定触发pollCss检查的时机

4.1 pollCss轮询的应用场景

当浏览器内核是旧的webkit内核时,或者不支持节点触发onload方法时,才使用pollCss进行轮询。

// for Old WebKit and Old Firefox
if (isOldWebKit || !supportOnload) {
 // Begin after node insertion
 setTimeout(function() {
 pollCss(node, callback, 0);
 }, 1);
 return;
}

五、现代浏览器直接用onload和onreadystatechange做判断

现代浏览器用这种方式判断,可以避免轮询的弊端。判断也更加准确及时。

5.1 onload方法

function onload() {
 // 确保只跑一次下载操作
 node.onload = node.onerror = node.onreadystatechange = null;
 // 清空node引用,在低版本IE,不清除会造成内存泄露
 node = null;
 callback();
}

onload方法触发执行后,应立即将多个相关方法进行重置,以避免callback多次触发。

node = null;将node重置为null,是为了避免低版本的IE出现内存溢出问题,及时清除没用的dom节点。

最后,执行callback方法。

5.2 支持onload方法浏览器的处理

if(supportOnload){
 node.onload = onload;
 node.onerror = function() {
 // 加载失败(404)
 onload();
 }
}

5.3 不支持onload方法浏览器的处理

if(supportOnload){
 // 代码...
}else{
 node.onreadystatechange = function() {
 if (/loaded|complete/.test(node.readyState)) {
  onload();
 }
 }
}

四、后记

选择剥离Sea.js方法进行改造的原因:因为该js库使用人群很广泛,如果出问题,作者也会及时修复。所以,以此代码为蓝本进行改造契合公司的用户群,避免大面积出现问题。

在产品上应用该方法后,到目前为止,未有客户反馈样式异常问题。所以,看本文章的程序猿们,可以放心使用。

ps:公司用户群有1千多万的用户量,涉及大大小小繁杂的浏览器,从IE6到chrome都有。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持三水点靠木!

Javascript 相关文章推荐
在页面中js获取光标/鼠标的坐标及光标的像素坐标
Nov 11 Javascript
JS OffsetParent属性深入解析
Jan 13 Javascript
js定义类的几种方法(推荐)
Jun 08 Javascript
AngularJS 入门教程之HTML DOM实例详解
Jul 28 Javascript
AngularJS ng-bind-html 指令详解及实例代码
Jul 30 Javascript
BootStrap入门教程(二)之固定的内置样式
Sep 19 Javascript
详解Vue.js——60分钟组件快速入门(上篇)
Dec 05 Javascript
jQuery Mobile漏洞会有跨站脚本攻击风险
Feb 12 Javascript
Vue.js事件处理器与表单控件绑定详解
Mar 20 Javascript
微信小程序 图片绝对定位(背景图片)
Apr 05 Javascript
jQuery菜单实例(全选,反选,取消)
Aug 28 jQuery
VUE实现Studio管理后台之鼠标拖放改变窗口大小
Mar 04 Javascript
用jQuery实现可输入多选下拉组合框实例代码
Jan 18 #Javascript
JS实现旋转木马式图片轮播效果
Jan 18 #Javascript
微信小程序实现图片预加载组件
Jan 18 #Javascript
JavaScript原生节点操作小结
Jan 17 #Javascript
Javascript 两种刷新方法以及区别和适用范围
Jan 17 #Javascript
easyUI combobox实现联动效果
Jan 17 #Javascript
Angularjs实现搜索关键字高亮显示效果
Jan 17 #Javascript
You might like
高亮度显示php源代码
2006/10/09 PHP
php XPath对XML文件查找及修改实现代码
2011/07/27 PHP
php class类的用法详细总结
2013/10/17 PHP
PHP 使用pcntl和libevent 实现Timer功能
2013/10/27 PHP
PHP pear安装配置教程
2016/05/14 PHP
浅谈Laravel队列实现原理解决问题记录
2017/08/19 PHP
用js正确判断用户名cookie是否存在的方法
2014/01/28 Javascript
JavaScript中的比较操作符&gt;、=、
2014/12/31 Javascript
初识Node.js
2015/03/20 Javascript
JS+DIV+CSS实现的经典标签切换效果代码
2015/09/14 Javascript
jQuery基于扩展实现的倒计时效果
2016/05/14 Javascript
JS数字千分位格式化实现方法总结
2016/12/16 Javascript
简单的渐变轮播插件
2017/01/12 Javascript
jQuery实现QQ空间汉字转拼音功能示例
2017/07/10 jQuery
使用socket.io实现简单聊天室案例
2018/01/02 Javascript
使用vue-router与v-if实现tab切换遇到的问题及解决方法
2018/09/07 Javascript
JavaScript实现小球沿正弦曲线运动
2020/09/07 Javascript
Vue 解决路由过渡动画抖动问题(实例详解)
2020/01/05 Javascript
JavaScript图片旋转效果实现方法详解
2020/06/28 Javascript
Python中的CURL PycURL使用例子
2014/06/01 Python
python模块之re正则表达式详解
2017/02/03 Python
pygame游戏之旅 添加游戏暂停功能
2018/11/21 Python
利用arcgis的python读取要素的X,Y方法
2018/12/22 Python
使用Python控制摄像头拍照并发邮件
2019/04/23 Python
CSS3用@font-face实现自定义英文字体
2013/09/23 HTML / CSS
中国医药集团国药在线:国药网
2017/02/06 全球购物
学生自我鉴定模板
2013/12/30 职场文书
法务专员岗位职责
2014/01/02 职场文书
大学生演讲稿范文
2014/01/11 职场文书
活动总结报告格式
2014/05/09 职场文书
贷款委托书怎么写
2014/08/02 职场文书
先进班集体事迹材料
2014/12/25 职场文书
2015年上半年物业工作总结
2015/03/30 职场文书
大学三好学生主要事迹范文
2015/11/03 职场文书
演讲开头怎么书写?
2019/08/06 职场文书
Python代码风格与编程习惯重要吗?
2021/06/03 Python