前端知库

link 和 @import 引入 CSS 有什么区别?

link 是 HTML 元素,浏览器解析 HTML 时就能发现并下载;@import 写在 CSS 里,要等外层样式表下载解析后才发现,形成串行的请求链,延迟 CSSOM 构建。link 能用 JavaScript 增删、切换 disabled,用 media 属性做条件加载;@import 必须写在样式表开头,支持媒体查询、supports() 条件和 layer() 放入层叠层,后者是 link 做不到的。

参考答案

简答

引入外部 CSS 有两种写法:在 HTML 的 <head> 里写 <link rel="stylesheet" href="a.css">,或者在 CSS 文件开头写 @import "a.css";。两者最终都让样式生效,区别在于浏览器什么时候知道要下载它。

link 写在 HTML 里,浏览器解析 HTML 时就发现了,可以和页面里其他资源一起尽早下载。@import 藏在 CSS 文件里,要等外层的 CSS 下载并解析后才发现,再去下载,这就形成了串行的请求链。Chrome 团队的性能建议明确说不要在 CSS 里用 @import,因为它会推迟 CSSOM 的构建,而 <head> 里的样式表本身就会阻塞渲染。

link 是 DOM 元素,可以用 JavaScript 创建、删除,或者切换 disabled 属性来启用、停用一份样式表,比如做主题切换;还能用 media 属性让打印样式之类的不阻塞渲染。@import 是 CSS 规则,只能通过 CSSOM 里的 CSSImportRule 读取。

@import 也有自己的优势:它可以带 layer() 把整份外部样式放进一个层叠层,link 没有对应的属性;还可以带 supports() 和媒体查询,条件不满足时浏览器可以不下载。要注意 @import 必须写在样式表最前面,只能排在 @charset 和 @layer 声明之后,否则会被忽略。

两种写法的加载时机

先看两种写法:

HTML
<!-- index.html -->
<link rel="stylesheet" href="base.css" />
<link rel="stylesheet" href="theme.css" />
CSS
/* base.css */
@import "theme.css";
body { margin: 0; }

第一种写法里,浏览器读到 HTML 就知道要下载 base.css 和 theme.css,两个请求可以同时发出去。

第二种写法里,HTML 里只有 base.css。浏览器要先把 base.css 下载下来、开始解析,读到 @import 才知道还有 theme.css,这时才发出第二个请求。也就是说,第二个文件至少要多等一个来回。Chrome 团队的性能指南把这叫做“串行的请求链”,并建议不要在 CSS 里用 @import。

为什么这个延迟会影响首屏?MDN 写明,<head> 里 rel="stylesheet" 的 link 默认会阻塞渲染,load 事件要等样式表连同它导入的内容都加载解析完才触发。所以 @import 进来的文件越晚被发现,页面越晚开始渲染。

用构建工具时情况不同。比如 Vite 文档写明它通过 postcss-import 预先内联 CSS 里的 @import,浏览器拿到的是合并后的文件,就没有这个问题。上面说的串行请求,指的是浏览器直接加载带 @import 的 CSS 文件的情况。

在 JavaScript 里能做什么

link 是普通的 DOM 元素,可以随时插入、删除。MDN 提到,link 上的 disabled 属性如果在 HTML 里就写上,页面加载时不会下载这份样式表,等属性被去掉再按需加载;在 DOM 上设置 disabled,这份样式表会从 document.styleSheets 里移除。所以主题切换常常是准备几个 link,切换它们的 disabled。

link 还有 load 和 error 事件,可以知道样式表是否加载成功。

@import 是 CSS 规则,不在 DOM 里。通过 document.styleSheets[0].cssRules 能拿到一个 CSSImportRule 对象,读它的 href、media、layerName、supportsText 和导入进来的 styleSheet,但没有办法像操作元素那样启用、停用它。

条件加载:media 和 supports()

两种写法都能按媒体条件引入:

HTML
<link rel="stylesheet" href="print.css" media="print" />
CSS
@import "print.css" print;
@import "grid.css" supports(display: grid) screen and (width <= 400px);

MDN 对 @import 的说法是,浏览器不支持其中任何一个媒体查询时不加载这个资源;supports() 条件不满足时可能不下载,即使通过其他途径下载了也不会应用。supports() 是 @import 独有的,link 没有对应的属性。

link 的 media 属性常被用来减少阻塞渲染。Chrome 团队的指南建议给打印样式、只在桌面用的样式这类当前用不到的样式表加上 media 属性,让它们下载时不阻塞渲染。

@import 和层叠层

@import 可以直接把一份外部样式放进层叠层:

CSS
@layer reset, vendor, app;
@import url("normalize.css") layer(reset);
@import url("vendor.css") layer(vendor);

MDN 说明,导入的规则在层叠中的表现,就像直接写在 @import 所在的位置一样;加上 layer() 以后,它们属于指定的层。link 元素没有 layer 属性,MDN 的属性列表里也没有,想把第三方库放进某个层,要么用 @import ... layer(),要么用构建工具包一层。层叠层的用法见《CSS Modules、层叠层和自定义属性怎样帮助维护样式?》。

这里也看到了 @import 位置的限制。MDN 写明,@import 必须在样式表最前面,只能排在 @charset 和用来声明层的 @layer 语句之后,放在其他规则后面会被忽略。它也不能写在 @media 这类条件规则里面,条件要直接写在 @import 后面。

常见追问

Sass 里的 @import 和 CSS 的 @import 是一回事吗?

不是。Sass 的 @import 在编译时把文件内容合并进来,浏览器根本看不到它,而且 Sass 已经推荐改用 @use,见《Sass、Less 这类 CSS 预处理器解决了什么问题?原生 CSS 现在能替代它们吗?》。原生 CSS 的 @import 是浏览器运行时去下载另一份样式表。

动态插入的 link 会阻塞渲染吗?

默认不会。MDN 写明只有 <head> 里的 link 可能阻塞渲染,解析 HTML 时发现的 rel="stylesheet" 默认阻塞;如果是用脚本动态加进去的,要另外设置 blocking="render" 才会阻塞渲染。