动态加载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 timers计时器简单应用说明
Oct 28 Javascript
JavaScript中的字符串操作详解
Nov 12 Javascript
为开发者准备的10款最好的jQuery日历插件
Feb 04 Javascript
jQuery中offsetParent()方法用法实例
Jan 19 Javascript
jQuery实现的超简单点赞效果实例分析
Dec 31 Javascript
浅析JavaScript回调函数应用
May 22 Javascript
JavaScript使用键盘输入控制实现数字验证功能
Aug 19 Javascript
Bootstrap基本样式学习笔记之表单(3)
Dec 07 Javascript
javascript实现QQ空间相册展示源码
Dec 12 Javascript
浅谈Vue响应式(数组变异方法)
May 07 Javascript
基于elementUI使用v-model实现经纬度输入的vue组件
May 12 Javascript
基于原生js实现判断元素是否有指定class名
Jul 11 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
Windows下的PHP安装pear教程
2014/10/24 PHP
ECSHOP在PHP5.5及高版本上报错的解决方法
2015/08/31 PHP
谈谈 PHP7新增功能
2015/12/16 PHP
简单PHP会话(session)说明介绍
2016/08/21 PHP
PHP实现的文件操作类及文件下载功能示例
2016/12/24 PHP
老生常谈PHP数组函数array_merge(必看篇)
2017/05/25 PHP
调试php程序的简单步骤
2019/10/04 PHP
Jsonp 跨域的原理以及Jquery的解决方案
2010/05/18 Javascript
基于jquery的direction图片渐变动画效果
2010/05/24 Javascript
JavaScript中也使用$美元符号来代替document.getElementById
2010/06/19 Javascript
javascript 广告后加载,加载完页面再加载广告
2010/11/25 Javascript
JS实现div内部的文字或图片自动循环滚动代码
2013/04/19 Javascript
jQuery实现响应浏览器缩放大小并改变背景颜色
2014/10/31 Javascript
JS实现很酷的水波文字特效实例
2015/02/26 Javascript
javascript实现的右下角弹窗实例
2015/04/24 Javascript
Bootstrap弹出带合法性检查的登录框实例代码【推荐】
2016/06/23 Javascript
浅谈Angular的$q, defer, promise
2016/12/20 Javascript
Iscrool下拉刷新功能实现方法(推荐)
2017/06/26 Javascript
详解tween.js的使用教程
2017/09/14 Javascript
vuex中使用对象展开运算符的示例
2017/09/25 Javascript
bootstrap表格内容过长时用省略号表示的解决方法
2017/11/21 Javascript
使用vue如何构建一个自动建站项目
2018/02/05 Javascript
深入理解Vue nextTick 机制
2018/04/28 Javascript
JQuery事件冒泡和默认行为代码实例
2020/05/13 jQuery
vue+Element中table表格实现可编辑(select下拉框)
2020/05/21 Javascript
在Python的Django框架中创建和使用模版
2015/07/15 Python
fastcgi文件读取漏洞之python扫描脚本
2017/04/23 Python
Django2.1集成xadmin管理后台所遇到的错误集锦(填坑)
2018/12/20 Python
python百行代码自制电脑端网速悬浮窗的实现
2020/05/12 Python
Python 通过爬虫实现GitHub网页的模拟登录的示例代码
2020/08/17 Python
python 使用三引号时容易犯的小错误
2020/10/21 Python
全面解析CSS Media媒体查询使用操作(推荐)
2017/08/15 HTML / CSS
CSS3实现多样的边框效果
2018/05/04 HTML / CSS
丝芙兰美国官网:SEPHORA美国
2016/08/03 全球购物
导游词之西江千户苗寨
2019/12/24 职场文书
深入详解JS函数的柯里化
2021/06/09 Javascript