- 前端
【免费下载链接】loadCSS
Load CSS asynchronously
本文以 Filament Group 开源的 loadCSS 项目(当前仓库版本 3.1.0)为对象,系统讲解如何让非关键 CSS 以异步方式加载、避免阻塞页面渲染:既包括推荐优先使用的纯 HTMLmedia切换标记模式,也包括可编程动态加载的loadCSS()函数与onloadCSS()回调函数。读者读完后,将掌握一套可直接落地的异步 CSS 加载方案,并理解其底层 DOM 注入、styleSheets轮询等核心实现原理。需要提醒的是,项目 README 已注明该仓库已归档、不再维护(README.md),但其模式与源码对现代前端性能优化仍有很好的参考价值。
为什么需要异步加载 CSS
通过link[rel=stylesheet]或@import引用样式表时,浏览器会在样式表加载完成前延迟页面渲染——这是浏览器为了保证"样式就绪后再绘制"而采取的渲染阻塞机制。对于首屏渲染必需的关键 CSS,这种阻塞是合理的;但对于非首屏关键、按需加载的样式(如弹层、主题皮肤、第三方组件样式),这种阻塞行为就变得不可接受。
loadCSS 的目标正是:在不阻塞页面渲染的前提下,尽可能早地发起样式表的获取与应用。项目同时提供了两条路径:
- 一个纯 HTML 的推荐标记模式(无需 JavaScript);
- 一个可选的小型 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):
| 参数 | 类型 | 默认行为 | 说明 |
|---|---|---|---|
href | string | 必填 | 要加载的 CSS 文件的 URL |
before | Element | 注入到最后一个样式表/脚本之后 | 指定插入参考元素,样式表link将插入到该元素之前 |
media | string | all | 设置样式表的media属性值 |
attributes | Object | 无 | 需要设置到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)做了多层兼容:
- 优先通过
ss.addEventListener("load", newcb)绑定; - 兼容旧 IE 的
ss.attachEvent("onload", newcb); - 针对不支持
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。原因有二:
- 文档顶部推荐的
media="print"+onload标记模式更简单、性能更好; - 此前的 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 加载链路:
- 首选:纯 HTML 的
media="print"+onload切换模式,配合<noscript>降级,零依赖、性能最佳; - 需要脚本控制时:
loadCSS(href, before, media, attributes)动态注入link,支持自定义插入位置、媒体类型与任意属性(如 SRI); - 需要加载完成回调时:配合
onloadCSS(link, callback)获得跨浏览器的可靠回调。
其核心思路——"先以不匹配的 media 发起非阻塞下载,再在加载完成后切换 media 生效"——至今仍是前端性能优化的经典手法。源码中以insertBefore安全注入、递归轮询document.styleSheets模拟加载完成等实现细节,也为自研加载器提供了可借鉴的范本。由于项目已归档,生产环境若仍需使用,建议按自身维护策略评估引入方式。
- 前端
【免费下载链接】loadCSS
Load CSS asynchronously
相关推荐
Flatstrap常见问题解答:开发者必知的8个解决方案
Flatstrap常见问题解答:开发者必知的8个解决方案 Flatstrap作为精简版的Bootstrap框架,以"无多余内容"为核心优势,帮助开发者快速构建轻
前端UI库/组件解决FloatMenu权限问题:SYSTEM_ALERT_WINDOW申请与处理最佳实践
解决FloatMenu权限问题:SYSTEM_ALERT_WINDOW申请与处理最佳实践 在Android应用开发中,悬浮窗功能是提升用户体验的重要方式,但实现
一文读懂Skywork-Reward-V2-Qwen3-8B:核心技术与创新点揭秘
一文读懂Skywork Reward V2 Qwen3 8B:核心技术与创新点揭秘 Skywork Reward V2 Qwen3 8B是基于Qwen3 8B模
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考