动态加载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 相关文章推荐
jquery 可排列的表实现代码
Nov 13 Javascript
Jquery乱码的一次解决过程 图解教程
Feb 20 Javascript
JQuery事件e参数的方法preventDefault()取消默认行为
Sep 26 Javascript
JavaScript定时显示广告代码分享
Mar 02 Javascript
Javascript优化技巧之短路表达式详细介绍
Mar 27 Javascript
javascript中不易分清的slice,splice和split三个函数
Mar 29 Javascript
javascript高仿热血传奇游戏实现代码
Feb 22 Javascript
vue使用iframe嵌入网页的示例代码
Jun 09 Javascript
基于React+Redux的SSR实现方法
Jul 03 Javascript
Vue项目引发的「过滤器」使用教程
Mar 12 Javascript
jQuery实现动态生成年月日级联下拉列表示例
May 11 jQuery
jQuery实现鼠标滑动切换图片
May 27 jQuery
用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
在DC的漫画和电影中,蝙蝠侠的宿敌,小丑的真名是什么?
2020/04/09 欧美动漫
山进SANGEAN ATS-909X电路分析
2021/03/02 无线电
php中替换字符串中的空格为逗号','的方法
2014/06/09 PHP
php获取客户端电脑屏幕参数的方法
2015/01/09 PHP
php制作简单模版引擎
2016/04/07 PHP
javascript 放大镜效果js组件 qsoft.PopBigImage.v0.35 加入了chrome支持
2009/04/07 Javascript
浅析用prototype定义自己的方法
2013/11/14 Javascript
JS冒泡事件的快速解决方法
2013/12/16 Javascript
js实现基于正则表达式的轻量提示插件
2015/08/29 Javascript
JavaScript对象数组排序函数及六个用法
2015/12/23 Javascript
JavaScript数组去重的几种方法效率测试
2016/10/23 Javascript
Node.js读取文件内容示例
2017/03/07 Javascript
jQuery实现简单的手风琴效果
2020/04/17 jQuery
Vue Ajax跨域请求实例详解
2017/06/20 Javascript
浅析vue-router jquery和params传参(接收参数)$router $route的区别
2018/08/03 jQuery
vue 组件的封装之基于axios的ajax请求方法
2018/08/11 Javascript
layui监听工具栏的实例(操作列表按钮)
2019/09/10 Javascript
javascript利用键盘控制小方块的移动
2020/04/20 Javascript
[59:36]2018DOTA2亚洲邀请赛 4.3 突围赛 Secret vs VG 第二场
2018/04/04 DOTA
用python代码做configure文件
2014/07/20 Python
Django使用Celery异步任务队列的使用
2018/03/13 Python
详解python分布式进程
2018/10/08 Python
基于树莓派的语音对话机器人
2019/06/17 Python
Python虚拟环境的原理及使用详解
2019/07/02 Python
Django框架视图函数设计示例
2019/07/29 Python
Python爬取视频(其实是一篇福利)过程解析
2019/08/01 Python
Django中的用户身份验证示例详解
2019/08/07 Python
教师自荐书
2013/10/08 职场文书
市场营销职业生涯规划书范文
2014/01/12 职场文书
幼儿园中秋节活动方案2013
2014/01/29 职场文书
教学评估实施方案
2014/03/16 职场文书
企业整改报告范文
2014/11/08 职场文书
趣味运动会加油词
2015/07/18 职场文书
《三国志》赏析
2019/08/27 职场文书
Python实现socket库网络通信套接字
2021/06/04 Python