☰
loadCSS:异步加载 CSS 的轻量级模式与实现原理全解析
2026/10/15 15:58:26 网站建设 项目流程
  • 前端

【免费下载链接】loadCSS

Load CSS asynchronously

项目地址:https://gitcode.com/gh_mirrors/lo/loadCSS
点击查看免费下载

本文以 Filament Group 开源的 loadCSS 项目(当前仓库版本 3.1.0)为对象,系统讲解如何让非关键 CSS 以异步方式加载、避免阻塞页面渲染:既包括推荐优先使用的纯 HTMLmedia切换标记模式,也包括可编程动态加载的loadCSS()函数与onloadCSS()回调函数。读者读完后,将掌握一套可直接落地的异步 CSS 加载方案,并理解其底层 DOM 注入、styleSheets轮询等核心实现原理。需要提醒的是,项目 README 已注明该仓库已归档、不再维护(README.md),但其模式与源码对现代前端性能优化仍有很好的参考价值。

为什么需要异步加载 CSS

通过link[rel=stylesheet]或@import引用样式表时,浏览器会在样式表加载完成前延迟页面渲染——这是浏览器为了保证"样式就绪后再绘制"而采取的渲染阻塞机制。对于首屏渲染必需的关键 CSS,这种阻塞是合理的;但对于非首屏关键、按需加载的样式(如弹层、主题皮肤、第三方组件样式),这种阻塞行为就变得不可接受。

loadCSS 的目标正是:在不阻塞页面渲染的前提下,尽可能早地发起样式表的获取与应用。项目同时提供了两条路径:

  1. 一个纯 HTML 的推荐标记模式(无需 JavaScript);
  2. 一个可选的小型 JavaScript 函数loadCSS(),用于在脚本中动态加载样式表。

推荐首选:纯 HTML 的异步加载标记模式

作为首选模式,官方强烈推荐直接在 HTML 中这样写:

<link rel="stylesheet" href="/path/to/my.css" media="print" onload="this.media='all'; this.onload=null;">

这一行标记的工作原理如下:

  • media="print"使样式表对当前屏幕媒体类型"不适用"。浏览器仍会立即发起资源下载,但由于媒体不匹配,样式不会参与当前页面的渲染,因此不会阻塞绘制;
  • 下载完成后触发onload,此时把media改为all,让样式立即生效;
  • this.onload=null防止旧浏览器在修改media后再次触发onload造成重复执行。

仓库中的 test/recommended.html 即该模式的直接演示:它引用一个被 server.js 故意延迟 5 秒响应的 test/slow.css,页面文字在样式生效前即可被阅读,证明加载过程未阻塞渲染。

为无 JavaScript 环境提供降级

该模式依赖onload内联脚本,若用户禁用 JavaScript 则样式永远不会应用。可在同标记旁补充<noscript>降级:

<link rel="stylesheet" href="slow.css" media="print" onload="this.media='all'; this.onload=null;"> <noscript><link rel="stylesheet" href="slow.css"></noscript>

test/mediatoggle.html 展示了这一完整写法。同时,test/control.html 作为对照组,用普通<link rel="stylesheet" href="slow.css">验证了"传统引用会阻塞渲染约 5 秒"的前提假设,从反面对比印证该异步模式的收益。

loadCSS 函数:动态加载样式表的 JavaScript API

若你需要在脚本中程序化地加载 CSS 文件,可以使用项目核心文件 src/loadCSS.js 暴露的全局loadCSS函数。最基本调用方式:

loadCSS( "path/to/mystylesheet.css" );

执行后,函数会在页面最后一个样式表或脚本元素之后插入一个新的link[rel=stylesheet]元素,并返回该link元素的引用,方便后续脚本继续操作它。多次调用loadCSS会按照调用顺序依次引用各 CSS 文件;但需要注意,资源可能以与调用顺序不同的顺序完成加载。

函数签名与三个可选参数

loadCSS共接收 4 个参数,其中后 3 个均为可选(源码注释见 src/loadCSS.js):

参数类型默认行为说明
hrefstring必填要加载的 CSS 文件的 URL
beforeElement注入到最后一个样式表/脚本之后指定插入参考元素,样式表link将插入到该元素之前
mediastringall设置样式表的media属性值
attributesObject无需要设置到link元素上的属性名/属性值键值对
before:控制注入位置

默认情况下loadCSS试图将样式表插入到页面所有 CSS 与 JS 之后。若需要更精确的位置(例如插入到某个特定样式表之前),可以传入before参数指定插入参考点,新样式表会被插入到该元素之前。官方示例通过给script加id实现"紧邻本脚本之前"注入:

<head> ... <script id="loadcss"> // load a CSS file just before the script element containing this code loadCSS( "path/to/mystylesheet.css", document.getElementById("loadcss") ); </script> ... </head>

源码中对应逻辑为 src/loadCSS.js:有before时直接以它为参考节点;否则取document.body(或head)的最后一个子节点作为参考。实际插入调用位于 src/loadCSS.js:

ref.parentNode.insertBefore( ss, ( before ? ref : ref.nextSibling ) );

即:传了before时插到参考元素之前,未传时插到参考元素之后(即页面末尾)。源码注释还指出,这里刻意使用insertBefore而非appendChild,是为了规避某些旧式 DOM 插入方式的安全隐患。

media:指定媒体类型

可传入字符串设置样式表的media属性,默认值为all:

loadCSS( "path/to/mystylesheet.css", null, "print" );
attributes:设置自定义属性(以 SRI 为例)

可传入属性名/属性值键值对对象,用于给样式表元素附加任意属性,最常见的用途是设置Subresource Integrity(SRI)完整性校验属性:

loadCSS( "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css", null, null, { "crossorigin": "anonymous", "integrity": "sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" } );

源码中属性设置发生在创建link元素之后、设置rel/href之前,通过ss.setAttribute()逐个写入(src/loadCSS.js)。仓库还提供了 test/attributes.html 诊断页,分别用"正确的 integrity 摘要""错误的 integrity 摘要""错误的 crossorigin 属性"三组对照,验证浏览器是否正确执行 SRI 校验——正确的摘要应加载成功,错误摘要应触发onerror而不加载。

onloadCSS:为样式表提供可靠的 onload 回调

部分浏览器对link元素的onload事件支持并不稳定(尤其是较老的移动端 WebKit),因此若需要可靠的加载完成回调,官方建议同时引入 src/onloadCSS.js 并使用onloadCSS函数:

var stylesheet = loadCSS( "path/to/mystylesheet.css" ); onloadCSS( stylesheet, function() { console.log( "Stylesheet has loaded." ); });

onloadCSS接收两个参数:loadCSS返回的link元素引用,以及加载完成后要执行的回调函数。其实现(src/onloadCSS.js)做了多层兼容:

  1. 优先通过ss.addEventListener("load", newcb)绑定;
  2. 兼容旧 IE 的ss.attachEvent("onload", newcb);
  3. 针对不支持onload的浏览器(源码注释点名的 Android 4.3 Galaxy S4、Android 4.2、Android 2.3 等),利用"isApplicationInstalled" in navigator这一弱推断特征识别 Android < 4.4 环境,转而调用loadCSS在元素上暴露的onloadcssdefined轮询机制。

回调通过called标志保证只触发一次,并以callback.call(ss)的形式调用,使回调内的this指向样式表元素。

浏览器兼容性

按 README 的说明,loadCSS 的模式尝试在任何支持 JavaScript 的浏览器中异步加载 CSS。但对于 IE8 及更早的旧浏览器,样式表加载期间仍会阻塞渲染——这种情况下样式表会表现得与普通link引用一样(即退化为同步加载,功能不受影响,只是失去异步收益)。也就是说,该方案对旧浏览器是"优雅降级"而非"失效"。

版本 3.0 的破坏性变更:移除 preload polyfill

README 专门说明了 3.0 版本的重要变化:不再支持也不再包含针对rel=preload标记模式的 polyfill。原因有二:

  1. 文档顶部推荐的media="print"+onload标记模式更简单、性能更好;
  2. 此前的 preload 模式有时会与浏览器资源优先级机制产生冲突,反而不利于 CSS 的非阻塞加载。

因此从 3.0 起,升级方式为:将原先的 preload 标记改写成上文推荐的 HTML 模式,并从构建中删除相关 JS。由于这是一次破坏 API 的变更,项目因此做了主版本号提升;若仍需使用已废弃的 preload 模式,可将代码固定指向仍留在 NPM 上的旧版本(如 2.1.0)。当前仓库 package.json 记录的版本号为 3.1.0,main入口指向src/loadCSS.js。

源码级原理剖析

理解了 API 之后,深入 src/loadCSS.js 可以看到异步加载的关键实现细节。

关键技巧一:先用不匹配的 media 发起"非阻塞下载"

源码在设置rel与href之后,特意先把media临时设为字符串"only x"(src/loadCSS.js):

ss.rel = "stylesheet"; ss.href = href; // temporarily set media to something inapplicable to ensure it'll fetch without blocking render ss.media = "only x";

"only x"是一个对任何现有媒体类型都不匹配的伪媒体查询,浏览器照常下载资源,但不会因样式就绪而阻塞渲染。加载完成后,loadCB再把media改回media || "all"(src/loadCSS.js),使样式立即生效。QUnit 测试 test/qunit/tests.js 专门断言了"加载前 media 为only x、加载后为all(或自定义值)"这一行为。

关键技巧二:等待 body 就绪再注入

link注入前需要等待document.body存在,以保证在 IE11 等浏览器下实现非阻塞加载。源码用递归setTimeout轮询实现(src/loadCSS.js):

function ready( cb ){ if( doc.body ){ return cb(); } setTimeout(function(){ ready( cb ); }); }

关键技巧三:onloadcssdefined 轮询 styleSheets

对于不触发onload的浏览器,loadCSS返回的元素上暴露了一个onloadcssdefined方法:它通过轮询document.styleSheets,直到发现某个样式表的href与目标resolvedHref一致,即认为加载完成(src/loadCSS.js):

var onloadcssdefined = function( cb ){ var resolvedHref = ss.href; var i = sheets.length; while( i-- ){ if( sheets[ i ].href === resolvedHref ){ return cb(); } } setTimeout(function() { onloadcssdefined( cb ); }); };

随后代码同时注册原生load事件监听与onloadcssdefined轮询(src/loadCSS.js),双保险确保loadCB(恢复 media)最终被执行。onloadCSS的 Android 分支正是复用了这一机制。

模块导出与使用方式

文件末尾兼容了两种使用环境(src/loadCSS.js):在 CommonJS 环境(如 Node)下通过exports.loadCSS导出,否则挂载为全局w.loadCSS。直接引入后即可像前文示例一样使用。

测试与验证

QUnit 单元测试

仓库在 test/qunit/index.html 组织了一套 QUnit 测试,测试逻辑见 test/qunit/tests.js,覆盖了:

  • window.loadCSS存在且为函数;
  • 能加载相对路径/绝对路径的 CSS 文件(files/test.css,内容见 test/qunit/files/test.css);
  • attributes参数能正确写入title、type等属性;
  • media 在加载前后由only x变为all(或自定义的print);
  • before参数能精确控制注入位置(断言新元素紧邻指定元素之前);
  • onloadCSS回调触发后样式真实生效(通过getComputedStyle验证背景色变为绿色)。

诊断测试页面与延迟服务器

test/index.html 是诊断测试入口,整理了两组对比页面:

  • 标准加载测试:验证各种传统方式的阻塞行为——test/control.html(head 中的普通 link)、test/body.html(body 中的 link 是否阻塞其后内容)、test/import-head.html(head 中@import)、test/import.html(body 中@import)、test/dom-append.html(脚本 appendChild 注入 link)、test/attributes.html(SRI 属性);
  • LoadCSS 辅助加载测试:test/test.html(纯loadCSS()动态加载)、test/test-onload.html(动态加载 +onloadCSS回调,回调触发后向页面追加<h1>onload!!!</h1>)。

这些页面统一引用 test/slow.css,该文件被 server.js 刻意延迟 5 秒返回,以模拟真实网络延迟:若能先读到页面文字再看到样式生效,即证明加载是非阻塞的。server.js还会把测试页中的<!--#include virtual="../src/loadCSS.js" -->这类占位符替换为源码内容(server.js),方便直接双击/本地预览。

本地运行与构建

通过 package.json 可本地体验:

npm start # 等价于 npx ./server.js,启动 3000 端口静态服务器 npm test # 等价于 npx grunt

构建任务定义在 Gruntfile.js:默认任务依次执行jshint(代码检查)、qunit(在 phantom 环境下跑 test/qunit/**/*.html 中的测试)、concat与uglify(生成dist/loadCSS.js、dist/onloadCSS.js及对应的.min.js压缩版)。也就是说,日常发布版可由src下的两个源文件经构建产出。

小结

loadCSS 提供了一条从"零 JavaScript"到"可编程动态加载"的完整异步 CSS 加载链路:

  1. 首选:纯 HTML 的media="print"+onload切换模式,配合<noscript>降级,零依赖、性能最佳;
  2. 需要脚本控制时:loadCSS(href, before, media, attributes)动态注入link,支持自定义插入位置、媒体类型与任意属性(如 SRI);
  3. 需要加载完成回调时:配合onloadCSS(link, callback)获得跨浏览器的可靠回调。

其核心思路——"先以不匹配的 media 发起非阻塞下载,再在加载完成后切换 media 生效"——至今仍是前端性能优化的经典手法。源码中以insertBefore安全注入、递归轮询document.styleSheets模拟加载完成等实现细节,也为自研加载器提供了可借鉴的范本。由于项目已归档,生产环境若仍需使用,建议按自身维护策略评估引入方式。

  • 前端

【免费下载链接】loadCSS

Load CSS asynchronously

项目地址:https://gitcode.com/gh_mirrors/lo/loadCSS
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询