CSS 및 웹 성능에 대해 자세히 알아보기
이 문서에서는 CSS와 네트워크 성능에 대해 설명합니다. 도움이 필요한 친구들이 모두 참고할 수 있기를 바랍니다.
이 글은 CSS 로딩 관련 지식을 비교적 포괄적으로 소개하는 글입니다. 번역가의 수준이 제한되어 있으므로 능숙한 학생들은 원문을 직접 읽어보시길 바랍니다. 번역이 도움이 되실 겁니다 감사합니다~ 다음은 전문입니다.
감사합니다. CSS 마술사로 불린 지 10년이 넘었지만 최근에는 블로그에 CSS 관련 글이 많지 않습니다. 그런 다음 CSS와 성능이라는 두 가지 주요 주제를 결합한 기사를 가져오겠습니다.
CSS는 페이지 렌더링의 핵심 요소 중 하나입니다(페이지에 외부 CSS가 있는 경우). 브라우저는 페이지를 렌더링하기 전에 모든 CSS가 다운로드되고 구문 분석될 때까지 기다립니다. 중요한 경로의 지연은 첫 번째 화면 표시 시간에 영향을 미치므로 가능한 한 빨리 CSS를 사용자 장치로 전송해야 합니다. 그렇지 않으면 페이지가 렌더링될 때까지 사용자에게 빈 화면만 표시됩니다.
가장 큰 질문은 무엇인가요?
넓게 말하면 CSS는 (렌더링) 성능의 핵심입니다. 그 이유는 다음과 같습니다.
렌더링 트리가 구축될 때까지 브라우저는 페이지를 렌더링하지 않습니다.
렌더링 트리는 DOM과 CSSOM ;
DOM은 DOM 이후의 JavaScript 작업을 차단하는 HTML 플러스(동기식)의 결과입니다.
CSSOM은 DOM에 적용된 CSS 규칙의 결과입니다.
JavaScript를 비차단으로 만드는 것은 매우 쉽습니다. 간단합니다.
상대적으로 CSS를 비동기식으로 로드하는 것이 더 어렵습니다.
다음 경험 법칙을 기억하세요. (이상적으로는) 가장 느린 스타일 테이블은 페이지 렌더링 시간을 결정합니다.
위 고려 사항을 바탕으로 가능한 한 빨리 DOM과 CSSOM을 구축해야 합니다. 일반적으로 DOM은 비교적 빠르게 구성되며 서버가 응답하는 첫 번째 요청(페이지 요청 시)은 HTML 문서입니다. 그러나 일반적으로 CSS는 HTML의 하위 리소스로 존재하므로 CSSOM 구성에는 일반적으로 시간이 더 오래 걸립니다.
이 기사에서는 CSS가 네트워크 병목 현상(자체 또는 다른 리소스에 대해)인 이유와 이를 극복하여 주요 경로를 단축하고 첫 번째 렌더링 전 대기 시간을 줄이는 방법에 대해 설명합니다.
중요 CSS 사용
조건이 허락한다면 렌더링 전 대기 시간을 줄이는 가장 효과적인 방법은 Critical CSS(중요 CSS) 모드를 사용하는 것입니다. 첫 번째 렌더링에 필요한 스타일(일반적으로 다음과 관련된 스타일) 첫 번째 화면) <head>
태그에 인라인하고 다른 스타일을 비동기적으로 로드합니다. <head>
标签中,其他样式则通过异步的方式进行加载。
虽然这十分有效,但实施起来却并不容易,比如:高度动态化的网站(译者注:如 SPA)通常难以提取首屏相关的样式、提取的过程需要自动化、需要对首屏不同元素显示或隐藏的状态作出假设、某些边界情况难以处理以及相关工具仍未成熟等问题。如果你的项目相当庞大或是有历史包袱,这将变得更为复杂。
根据媒体类型拆分代码
如果在项目组难以执行关键 CSS 策略,可以尝试根据媒体查询拆分 CSS 文件,这也是一种可靠的策略。执行此策略后,浏览器表现如下:
- 以非常高的优先级下载符合当前上下文(设备、屏幕尺寸、分辨率、方向等)的 CSS 文件,阻塞关键路径;
- 以非常低的优先级下载不符合当前上下文的 CSS 文件,不会阻塞关键路径。
浏览器基本上能将未命中媒体查询的 CSS 文件延迟下载。
<link rel="stylesheet" href="all.css" />
如果我们把全部的 CSS 代码都放在一个文件中,请求的表现如下:

我们可以观察到,这个单独的 CSS 文件会以 最高 的优先级下载。
根据媒体查询拆分成若干个 CSS 文件后:
<link rel="stylesheet" href="all.css" media="all" /> <link rel="stylesheet" href="small.css" media="(min-width: 20em)" /> <link rel="stylesheet" href="medium.css" media="(min-width: 64em)" /> <link rel="stylesheet" href="large.css" media="(min-width: 90em)" /> <link rel="stylesheet" href="extra-large.css" media="(min-width: 120em)" /> <link rel="stylesheet" href="print.css" media="print" />
浏览器会以不同的优先级下载 CSS 文件:

浏览器仍然会下载全部的 CSS 文件,但只有符合当前上下文的 CSS 文件会阻塞渲染。
避免在 CSS 文件中使用 @import
为缩短渲染等待时间而努力的下一项任务非常简单:避免在 CSS 文件中使用 @import
이 방법은 매우 효과적이지만 구현하기가 쉽지 않습니다. 예를 들어 매우 동적인 웹사이트(역자 주: SPA 등)는 일반적으로 첫 번째 화면과 관련된 스타일을 추출하는 데 어려움이 있습니다. 첫 번째 화면은 달라야 합니다. 요소의 표시 또는 숨겨진 상태에 대한 가정, 처리하기 어려운 일부 극단적인 경우, 아직 미성숙한 관련 도구 등이 문제입니다. 프로젝트 규모가 상당히 크거나 역사적인 수하물이 있는 경우 이는 더욱 복잡해집니다.
🎜미디어 유형에 따라 코드 분할🎜🎜🎜프로젝트 팀에서 주요 CSS 전략을 구현하기 어려운 경우 미디어 쿼리를 기반으로 CSS 파일을 분할해 볼 수 있습니다. 또한 신뢰할 수 있는 전략 입니다. 이 전략을 실행한 후 브라우저는 다음과 같이 작동합니다. 🎜🎜현재 컨텍스트(장치, 화면 크기, 해상도, 방향 등)와 일치하는 CSS 파일을 매우 높은 우선순위로 다운로드하여 중요한 경로를 차단합니다. 매우 낮은 우선순위 현재 컨텍스트에 맞지 않는 CSS 파일을 다운로드하는 우선순위는 중요 경로를 차단하지 않습니다. 🎜🎜🎜브라우저는 기본적으로 미디어 쿼리가 누락된 CSS 파일 다운로드를 지연시킬 수 있습니다. 🎜<link rel="stylesheet" href="all.css" />
로그인 후 복사로그인 후 복사로그인 후 복사🎜모든 CSS 코드를 하나의 파일에 넣으면 요청은 다음과 같이 동작합니다: 🎜
그림 >🎜이 단일 CSS 파일이 가장 높은 우선순위로 다운로드되는 것을 볼 수 있습니다. 🎜🎜미디어 쿼리를 기반으로 여러 CSS 파일로 분할한 후: 🎜@import url(imported.css);
로그인 후 복사로그인 후 복사🎜브라우저는 서로 다른 우선순위로 CSS 파일을 다운로드합니다. 🎜![현재 컨텍스트에 맞지 않는 CSS 파일은 _최저_ 다운로드로 다운로드됩니다. /1]()
🎜 브라우저는 여전히 모든 CSS 파일을 다운로드하지만 일치하는 CSS 파일만 다운로드합니다. 현재 컨텍스트는 렌더링을 차단합니다. 🎜🎜CSS 파일에서 @import
사용을 피하세요🎜🎜🎜렌더링 대기 시간을 줄이기 위한 다음 작업은 매우 간단합니다. 🎜Use <를 사용하지 마세요 CSS 파일의 code>@import🎜🎜
如果了解 @import
的原理,那应该清楚它的性能并不高,使用它会阻塞渲染更长时间。这是因为我们在关键路径上创造了更多(队列式)的网络请求:
下载 HTML;
请求并下载依赖的 CSS;
- (下载及解析完成后,本该是构造渲染树,然而;)
CSS 依赖了其他的 CSS,继续请求并下载 CSS 文件;
构造渲染树。
以下是相关的案例:
<link rel="stylesheet" href="all.css" />
로그인 후 복사로그인 후 복사로그인 후 복사all.css 的内容:
@import url(imported.css);
로그인 후 복사로그인 후 복사最终,浏览器的请求瀑布图呈现为:

关键路径上的 CSS 文件并没有并行下载。
通过将 @imports
请求的文件改为 <link rel="stylesheet" />
:
<link rel="stylesheet" href="all.css" />
로그인 후 복사可以提高网络性能:

关键路径上的 CSS 文件是并行下载的。
注意,有一个特殊的情况值得讨论。如果你没有包含 @import
的 CSS 文件的修改权限,为了让浏览器并行下载 CSS 文件,可以往 HTML 中补充相应的 <link rel="stylesheet" src="@import的地址" />
。浏览器会并行下载相应的 CSS 文件且不会重复下载 @import
引用的文件。
在 HTML 中谨慎地使用 @import
本节的内容比较奇怪。各大浏览器的相关实现上似乎都有问题,我以前提交了相关的bugs(译者注:简单说,当页面中存在:<style>@import url(xxx.url);</style>
,浏览器不会并行下载,但加上引号后:<style>@import url("xxx.url");</style>
,浏览器会并行下载)。
为了透彻地理解本节的内容,首先我们需要了解浏览器的预加载扫描器:各大浏览器都实现了一个名为预加载扫描器的辅助解析器。浏览器的核心解析器主要用于构建 DOM、CSSOM、运行 JavaScript 等。HTML 文档中某些标签与状态会阻塞核心解析器,因而核心解析器的运行是断断续续的。而预加载扫描器可以跳到核心解析器尚未解析的部分,用以发现其他待引用的子资源(如 CSS、JS 文件、图片等)。一旦发现此类子资源,预加载扫描器会开始下载它们,以便核心解析器在解析到对应内容时就能使用它们(,而不是直到那一刻才开始下载该资源)。预加载扫描器的出现,使网页的加载性能提高了19%,这是一项了不起的成就,可以极大地优化用户体验。
作为开发者,需要警惕预加载扫描器背后隐藏的问题,这在后文会进行阐述。
在 HTML 中使用 @import
,在以 WebKit 与 Blink 为内核的浏览器中,可能会触发它们预加载扫描器的 bug,在 Firefox 与 IE/Edge 中,则表现低效。
Firefox 与 IE / Edge:在 HTML 中将 @import
放在 JS 和 CSS 之前
在 Firefox 与 IE/Edge 中,预加载扫描器不会并行下载 <script src="">
和 <link rel="stylesheet" />
后 @imports
引用的资源。
这意味着如下的 HTML:
<script src="app.js"></script>
<style>
@import url(app.css);
</style>
로그인 후 복사会出现这样的请求瀑布图:

由于预加载扫描器失效,导致资源在 Firefox 中无法并行下载(IE/Edge 中有着同样的问题)。
通过上图,可以清晰地观察到:直到 JavaScript 文件下载完成之后,@import
引用的 CSS 文件才开始下载。
不单 <script>
标签会触发此问题,<link>
标签也会:
<link rel="stylesheet" href="style.css" />
<style>
@import url(app.css);
</style>
로그인 후 복사로그인 후 복사
与 <script>
标签一样,子资源无法并行下载。
此问题最简单的解决方案是调换 <script>
或 <link rel="stylesheet" />
标签与(包含 @import
的)<style>
标签的位置。然而,当我们改变顺序时,可能会对页面造成影响。
最佳解决方案是完全不使用 @import
,再往 HTML 文档中加入另一个 <link rel="stylesheet" />
取而代之:
<link rel="stylesheet" href="style.css" />
<link rel="stylesheet" href="app.css" />
로그인 후 복사修改后,浏览器表现更好:

浏览器并行下载资源,IE/Edge 表现相同。
以 Blink 或 WebKit 内核的浏览器:在 HTML 文档中使用 @import
时,要用引号包裹 url。
对于以 Blink 或 WebKit 为内核的浏览器而言,当 @import
引用的 url 未被引号包裹时,表现与 Firefox 和 IE/Edge 一致(无法并行下载)。这意味着上述两个内核的预加载扫描器存在 bug。
因此,无需调整代码的顺序,只需要添加引号即可解决问题。但我还是建议使用另一个 <link rel="stylesheet" />
取代 @import
。
未添加引号时的代码:
<link rel="stylesheet" href="style.css" />
<style>
@import url(app.css);
</style>
로그인 후 복사로그인 후 복사瀑布图:

可以看到,缺失引号会破坏 Chrome 的预加载(Opera 与 Safari 表现也是如此。)
添加引号后的代码:
<link rel="stylesheet" href="style.css" />
<style>
@import url("app.css");
</style>
로그인 후 복사
添加引号后,Chrome、Opera 和 Safari 的预加载扫描器表现恢复正常,
这绝对是 WebKit 与 Blink 内核的一个 bug,是否添加引号不应成为影响预加载扫描器的因素。
感谢 Yoav 帮我追踪这个问题。
现在这个 bug 现已在 Chromium 的待修复列表中。
不要将动态插入 JavaScript 的代码放在 <link rel="stylesheet" />
之后
在上一节中,我们了解到某些引用 CSS 文件路径 的方法,会对其他资源的下载造成负面影响。在本节中,我们将探究为何稍有不慎,CSS 将延迟其他资源的下载。该问题主要出现在动态创建的 <script>
标签中:
<script>
var script = document.createElement('script');
script.src = "analytics.js";
document.getElementsByTagName('head')[0].appendChild(script);
</script>
로그인 후 복사所有浏览器都存在一个鲜为人知,但符合逻辑的现象,它会对性能造成很大的影响:
在浏览器下载完该 CSS 文件之前,不会执行下面的 JS
<link rel="stylesheet" href="slow-loading-stylesheet.css" />
<script>
console.log("I will not run until slow-loading-stylesheet.css is downloaded.");
</script>
로그인 후 복사这是合理的。当 CSS 文件尚未下载完成时,HTML 文档中任何同步的 JavaScript 代码,均不会执行。考虑以下场景: <script>
中的代码会访问当前的页面样式,为确保结果正确,需要等待( <script>
标签前)所有 CSS 文件下载并解析完毕后再获取,否则无法保证正确性。因此,在 CSSOM 构建完成之前,<script>
中的代码不会执行。
根据这现象,CSS 文件的下载时间会对后续 <script>
的执行时间造成影响。下面的例子能较好地说明问题。
如果我们将一个 <link rel="stylesheet" />
放在 <script>
之前,<script>
中动态创建新 <script>
的代码只会在 CSS 文件下载完之后才会执行,这意味着 CSS 推迟了资源的下载与执行:
<script>
var script = document.createElement('script');
script.src = "analytics.js";
document.getElementsByTagName('head')[0].appendChild(script);
</script>
로그인 후 복사从下面的瀑布图可以看到,JavaScript 文件在 CSSOM 构建完成之后才开始下载,完全失去了并行下载的优势:

尽管预加载扫描器希望能预下载 analytics.js
,但对 analytics.js
的引用并非一开始就存在于 HTML 的文档之中,它是由 <link>
后面 <script>
的代码动态创建的,在创建之前,它只是一些字符串,而不是预加载扫描器可识别的资源,无形中它被隐藏起来了。
为了更安全地加载脚本,第三方服务商经常提供这样的代码片段。然而,开发者通常不信任第三方的代码,因而会把该片段放在页面的最后,但这可能会导致不良的后果。事实上,Google Analytics (在文档中)对此的建议是:
将代码复制后,作为第一项粘贴到待追踪页面的 中。
综上,我的建议是:
如果 <script>
中的代码并不依赖 CSS,把它们放在样式表之前。
调整一下代码:
<script>
var script = document.createElement('script');
script.src = "analytics.js";
document.getElementsByTagName('head')[0].appendChild(script);
</script>
로그인 후 복사
交换位置之后,子资源可以并行下载,页面的整体性能提高了两倍以上。(译者注:本节的内容只同意一半,<head>
中的代码,确实是建议先放 <script>
,再放 <link>
,后文也会有相关的内容,但第三方代码放在 <head>
中的第一项,取决于相关代码的用途。如非必要,放在页面末尾或空闲时下载及执行也未尝不可)
将无需查询 CSSOM 的 JavaScript 代码放在 CSS 文件之前,需要查询的放在 CSS 文件之后
这条建议远比你想象中的有用。
上文讨论了插入新 <script>
的代码应放在 <link>
之前,那是否能推广到其他的 CSS 与 JavaScript 呢?为了弄明白这个问题,先提出以下假设:
假设:
- CSSOM 的构建会阻塞 CSS 后面同步 JS 的执行;
- 同步的 JS 会阻塞 DOM 的构建...
那如果 JS 并不依赖 CSSOM,以下那种情况会更快?
- script 在前 style 在后;
- style 在前 script 在后?
答案是:
如果 JS 文件没有依赖 CSS,你应该将 JS 代码放在样式表之前。 既然没有依赖,那就没有任何理由阻塞 JavaScript 代码的执行。
(尽管执行 JavaScript 代码时会停止解析 DOM, 但预加载扫描器会提前下载之后的 CSS)
如果你一部分 JavaScript 需要依赖 CSS 而另一部分却不用,最佳的实践是将 JavaScript 分为两部分,分别置于 CSS 的两侧:
<!-- 这部分 JavaScript 代码下载完后会立即执行 -->
<script src="i-need-to-block-dom-but-DONT-need-to-query-cssom.js"></script>
<link rel="stylesheet" href="app.css" />
<!-- 这部分 JavaScript 代码在 CSSOM 构建完成后才会执行 -->
<script src="i-need-to-block-dom-but-DO-need-to-query-cssom.js"></script>
로그인 후 복사根据这种组织方式,我们的页面会按最佳的方式下载与执行相关代码。下面的截图中,粉色代表 JS 的执行,但它们都比较“纤细”了,希望你能看得清楚。(第一栏的(下同))第一行是整个页面的时间轴,留意该行粉色的部分,代表 JS 正在执行。第二行是首个 JS 文件的时间轴,可以看到下载完后并立即执行。第三行是 CSS 的时间轴,因而没有任何 JS 执行。最后一行是第二个 JS 文件的时间轴,可以清晰地看到,直到 CSS 下载完成后才执行。

注意,你应该根据页面的实际情况测试这种代码组织方式,取决于 CSS 与 JavaScript 文件大小与 JavaScript 文件执行所需的时间,可能会出现不同的结果。记得多测试!(译者注:根据实践经验,<head>
中的代码组织基本可以按照这种方式,即 JS 在 CSS 之前,因为 <head>
中的 JS 代码基本不依赖 CSS,唯一的反例是 JS 代码体积非常大或执行时间很长。)
将 <link rel="stylesheet" />
放在 <body>
中。
最后一条优化策略比较新颖,它对页面性能有很大帮助,并使页面达到逐步渲染的效果,同时易于执行。
在 HTTP/1.1 中,我们习惯于将全部的 css 打成一个文件,如 app.css:
<html>
<head>
<link rel="stylesheet" href="app.css" />
</head>
<body>
<header class="site-header">
<nav class="site-nav">...</nav>
</header>
<main class="content">
<section class="content-primary">
<h1>...</h1>
<div class="date-picker">...</div>
</section>
<aside class="content-secondary">
<div class="ads">...</div>
</aside>
</main>
<footer class="site-footer">
</footer>
</body>
로그인 후 복사然而,从三方面而言,渲染性能降低了:
每个页面只用到 app.css 中的部分样式: 用户会下载多余的 CSS。
难以制定缓存策略: 例如,某个页面使用的日期选择器更改了背景颜色,重新生成 app.css 后,旧的 app.css 缓存将失效。
整个 app.css 在解析构建完 CSSOM 之前,页面渲染被阻塞: 尽管当前页面可能只用到了 17% 的 CSS代码,但(浏览器)仍需等待其他 83% 的代码下载并解析完后,才能开始渲染。
使用 HTTP/2,可以解决第一与第二点:
<html>
<head>
<link rel="stylesheet" href="core.css" />
<link rel="stylesheet" href="site-header.css" />
<link rel="stylesheet" href="site-nav.css" />
<link rel="stylesheet" href="content.css" />
<link rel="stylesheet" href="content-primary.css" />
<link rel="stylesheet" href="date-picker.css" />
<link rel="stylesheet" href="content-secondary.css" />
<link rel="stylesheet" href="ads.css" />
<link rel="stylesheet" href="site-footer.css" />
</head>
<body>
<header class="site-header">
<nav class="site-nav">...</nav>
</header>
<main class="content">
<section class="content-primary">
<h1>...</h1>
<div class="date-picker">...</div>
</section>
<aside class="content-secondary">
<div class="ads">...</div>
</aside>
</main>
<footer class="site-footer">
</footer>
</body>
로그인 후 복사根据页面的不同组件下载不同的 CSS,能有效地解决冗余问题。这减少了对关键路径造成阻塞的 CSS 文件总大小。
同时,我们可以制定更有效的缓存策略,(当代码产生变化之后,)只会影响对应文件的缓存,其他的文件保持不变。
但仍有解决的问题:下载并解析全部 CSS 文件之前,页面的渲染仍然是阻塞的。页面的渲染时间仍然取决于最慢的 CSS 文件下载与解析的时间。假设由于某种原因,页脚的 CSS 下载需要很长时间,(即使页头的 CSSOM 已经构建完成,)浏览器也只能等待而无法渲染页头。
然而,这现象在 Chrome (v69)中得到缓解,Firefox 与 IE/Edge 也已经进行了相关的优化。<link rel="stylesheet" />
只会阻塞后续内容,而不是整个页面的渲染。这意味着我们可以用以下方式组织代码:
<html>
<head>
<link rel="stylesheet" href="core.css" />
</head>
<body>
<link rel="stylesheet" href="site-header.css" />
<header class="site-header">
<link rel="stylesheet" href="site-nav.css" />
<nav class="site-nav">...</nav>
</header>
<link rel="stylesheet" href="content.css" />
<main class="content">
<link rel="stylesheet" href="content-primary.css" />
<section class="content-primary">
<h1>...</h1>
<link rel="stylesheet" href="date-picker.css" />
<div class="date-picker">...</div>
</section>
<link rel="stylesheet" href="content-secondary.css" />
<aside class="content-secondary">
<link rel="stylesheet" href="ads.css" />
<div class="ads">...</div>
</aside>
</main>
<link rel="stylesheet" href="site-footer.css" />
<footer class="site-footer">
</footer>
</body>
로그인 후 복사这样的结果是我们能逐步渲染页面,当前面的 CSS 可用时,页面将呈现对应的内容(,而不需等待全部 CSS 下载并解析完毕)。
I如果浏览器不支持这种特性,也不会损害页面的性能。整个页面将回退为原来的模式,只有在最慢的 CSS 下载并解析完成后,才能渲染页面。
有关这种特性的更多细节,建议阅读这篇文章。
总结
本文内容比较 繁杂,成文后超出了本来的预期,尝试总结了 CSS 加载相关的一系列的最佳实践,值得仔细体会:
- 懒加载非关键 CSS:
- 优先加载关键 CSS,懒加载其他 CSS;
- 或根据媒体类型拆分 CSS 文件。
- 避免使用
@import
:
- 在 HTML 文档中应该避免;
- 在 CSS 文件之中更应避免;
- 以及警惕预加载扫描器的怪异行为。
- 关注 CSS 与 JavaScript 的顺序:
- 在 CSS 文件后的 JavaScript 仅在 CSSOM 构建完成后才会执行;
- 如果你的 JavaScript 不依赖 CSS;
- 将它放置于 CSS 之前;
- 如果 JavaScript 依赖 CSS:
- 将它放置于 CSS 之后。
- 仅加载 DOM 依赖的 CSS:
- 这将提高初次渲染的速度使让页面逐步渲染。
注意
本文叙述的内容都遵循规范或根据浏览器的行为推导得出,然而,你应该亲自进行测试。尽管理论上是正确的,但在实践中可能会有所不同。记得好好测试!
英文原文地址:https://csswizardry.com/2018/11/css-and-network-performance/
更多编程相关知识,请访问:编程视频!!
위 내용은 CSS 및 웹 성능에 대해 자세히 알아보기의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!
<link rel="stylesheet" href="all.css" />
@import url(imported.css);
🎜CSS 파일에서 @import
사용을 피하세요🎜🎜🎜렌더링 대기 시간을 줄이기 위한 다음 작업은 매우 간단합니다. 🎜Use <를 사용하지 마세요 CSS 파일의 code>@import🎜🎜
如果了解 @import
的原理,那应该清楚它的性能并不高,使用它会阻塞渲染更长时间。这是因为我们在关键路径上创造了更多(队列式)的网络请求:
下载 HTML;
请求并下载依赖的 CSS;
- (下载及解析完成后,本该是构造渲染树,然而;)
CSS 依赖了其他的 CSS,继续请求并下载 CSS 文件;
构造渲染树。
以下是相关的案例:
<link rel="stylesheet" href="all.css" />
all.css 的内容:
@import url(imported.css);
最终,浏览器的请求瀑布图呈现为:
关键路径上的 CSS 文件并没有并行下载。
通过将 @imports
请求的文件改为 <link rel="stylesheet" />
:
<link rel="stylesheet" href="all.css" />
可以提高网络性能:

关键路径上的 CSS 文件是并行下载的。
注意,有一个特殊的情况值得讨论。如果你没有包含 @import
的 CSS 文件的修改权限,为了让浏览器并行下载 CSS 文件,可以往 HTML 中补充相应的 <link rel="stylesheet" src="@import的地址" />
。浏览器会并行下载相应的 CSS 文件且不会重复下载 @import
引用的文件。
在 HTML 中谨慎地使用 @import
本节的内容比较奇怪。各大浏览器的相关实现上似乎都有问题,我以前提交了相关的bugs(译者注:简单说,当页面中存在:<style>@import url(xxx.url);</style>
,浏览器不会并行下载,但加上引号后:<style>@import url("xxx.url");</style>
,浏览器会并行下载)。
为了透彻地理解本节的内容,首先我们需要了解浏览器的预加载扫描器:各大浏览器都实现了一个名为预加载扫描器的辅助解析器。浏览器的核心解析器主要用于构建 DOM、CSSOM、运行 JavaScript 等。HTML 文档中某些标签与状态会阻塞核心解析器,因而核心解析器的运行是断断续续的。而预加载扫描器可以跳到核心解析器尚未解析的部分,用以发现其他待引用的子资源(如 CSS、JS 文件、图片等)。一旦发现此类子资源,预加载扫描器会开始下载它们,以便核心解析器在解析到对应内容时就能使用它们(,而不是直到那一刻才开始下载该资源)。预加载扫描器的出现,使网页的加载性能提高了19%,这是一项了不起的成就,可以极大地优化用户体验。
作为开发者,需要警惕预加载扫描器背后隐藏的问题,这在后文会进行阐述。
在 HTML 中使用 @import
,在以 WebKit 与 Blink 为内核的浏览器中,可能会触发它们预加载扫描器的 bug,在 Firefox 与 IE/Edge 中,则表现低效。
Firefox 与 IE / Edge:在 HTML 中将 @import
放在 JS 和 CSS 之前
在 Firefox 与 IE/Edge 中,预加载扫描器不会并行下载 <script src="">
和 <link rel="stylesheet" />
后 @imports
引用的资源。
这意味着如下的 HTML:
<script src="app.js"></script> <style> @import url(app.css); </style>
会出现这样的请求瀑布图:

由于预加载扫描器失效,导致资源在 Firefox 中无法并行下载(IE/Edge 中有着同样的问题)。
通过上图,可以清晰地观察到:直到 JavaScript 文件下载完成之后,@import
引用的 CSS 文件才开始下载。
不单 <script>
标签会触发此问题,<link>
标签也会:
<link rel="stylesheet" href="style.css" /> <style> @import url(app.css); </style>

与 <script>
标签一样,子资源无法并行下载。
此问题最简单的解决方案是调换 <script>
或 <link rel="stylesheet" />
标签与(包含 @import
的)<style>
标签的位置。然而,当我们改变顺序时,可能会对页面造成影响。
最佳解决方案是完全不使用 @import
,再往 HTML 文档中加入另一个 <link rel="stylesheet" />
取而代之:
<link rel="stylesheet" href="style.css" /> <link rel="stylesheet" href="app.css" />
修改后,浏览器表现更好:

浏览器并行下载资源,IE/Edge 表现相同。
以 Blink 或 WebKit 内核的浏览器:在 HTML 文档中使用 @import
时,要用引号包裹 url。
对于以 Blink 或 WebKit 为内核的浏览器而言,当 @import
引用的 url 未被引号包裹时,表现与 Firefox 和 IE/Edge 一致(无法并行下载)。这意味着上述两个内核的预加载扫描器存在 bug。
因此,无需调整代码的顺序,只需要添加引号即可解决问题。但我还是建议使用另一个 <link rel="stylesheet" />
取代 @import
。
未添加引号时的代码:
<link rel="stylesheet" href="style.css" /> <style> @import url(app.css); </style>
瀑布图:

可以看到,缺失引号会破坏 Chrome 的预加载(Opera 与 Safari 表现也是如此。)
添加引号后的代码:
<link rel="stylesheet" href="style.css" /> <style> @import url("app.css"); </style>

添加引号后,Chrome、Opera 和 Safari 的预加载扫描器表现恢复正常,
这绝对是 WebKit 与 Blink 内核的一个 bug,是否添加引号不应成为影响预加载扫描器的因素。
感谢 Yoav 帮我追踪这个问题。
现在这个 bug 现已在 Chromium 的待修复列表中。
不要将动态插入 JavaScript 的代码放在 <link rel="stylesheet" />
之后
在上一节中,我们了解到某些引用 CSS 文件路径 的方法,会对其他资源的下载造成负面影响。在本节中,我们将探究为何稍有不慎,CSS 将延迟其他资源的下载。该问题主要出现在动态创建的 <script>
标签中:
<script> var script = document.createElement('script'); script.src = "analytics.js"; document.getElementsByTagName('head')[0].appendChild(script); </script>
所有浏览器都存在一个鲜为人知,但符合逻辑的现象,它会对性能造成很大的影响:
在浏览器下载完该 CSS 文件之前,不会执行下面的 JS
<link rel="stylesheet" href="slow-loading-stylesheet.css" /> <script> console.log("I will not run until slow-loading-stylesheet.css is downloaded."); </script>
这是合理的。当 CSS 文件尚未下载完成时,HTML 文档中任何同步的 JavaScript 代码,均不会执行。考虑以下场景: <script>
中的代码会访问当前的页面样式,为确保结果正确,需要等待( <script>
标签前)所有 CSS 文件下载并解析完毕后再获取,否则无法保证正确性。因此,在 CSSOM 构建完成之前,<script>
中的代码不会执行。
根据这现象,CSS 文件的下载时间会对后续 <script>
的执行时间造成影响。下面的例子能较好地说明问题。
如果我们将一个 <link rel="stylesheet" />
放在 <script>
之前,<script>
中动态创建新 <script>
的代码只会在 CSS 文件下载完之后才会执行,这意味着 CSS 推迟了资源的下载与执行:
<script> var script = document.createElement('script'); script.src = "analytics.js"; document.getElementsByTagName('head')[0].appendChild(script); </script>
从下面的瀑布图可以看到,JavaScript 文件在 CSSOM 构建完成之后才开始下载,完全失去了并行下载的优势:

尽管预加载扫描器希望能预下载 analytics.js
,但对 analytics.js
的引用并非一开始就存在于 HTML 的文档之中,它是由 <link>
后面 <script>
的代码动态创建的,在创建之前,它只是一些字符串,而不是预加载扫描器可识别的资源,无形中它被隐藏起来了。
为了更安全地加载脚本,第三方服务商经常提供这样的代码片段。然而,开发者通常不信任第三方的代码,因而会把该片段放在页面的最后,但这可能会导致不良的后果。事实上,Google Analytics (在文档中)对此的建议是:
将代码复制后,作为第一项粘贴到待追踪页面的 中。
综上,我的建议是:
如果 <script>
中的代码并不依赖 CSS,把它们放在样式表之前。
调整一下代码:
<script> var script = document.createElement('script'); script.src = "analytics.js"; document.getElementsByTagName('head')[0].appendChild(script); </script>

交换位置之后,子资源可以并行下载,页面的整体性能提高了两倍以上。(译者注:本节的内容只同意一半,<head>
中的代码,确实是建议先放 <script>
,再放 <link>
,后文也会有相关的内容,但第三方代码放在 <head>
中的第一项,取决于相关代码的用途。如非必要,放在页面末尾或空闲时下载及执行也未尝不可)
将无需查询 CSSOM 的 JavaScript 代码放在 CSS 文件之前,需要查询的放在 CSS 文件之后
这条建议远比你想象中的有用。
上文讨论了插入新 <script>
的代码应放在 <link>
之前,那是否能推广到其他的 CSS 与 JavaScript 呢?为了弄明白这个问题,先提出以下假设:
假设:
- CSSOM 的构建会阻塞 CSS 后面同步 JS 的执行;
- 同步的 JS 会阻塞 DOM 的构建...
那如果 JS 并不依赖 CSSOM,以下那种情况会更快?
- script 在前 style 在后;
- style 在前 script 在后?
答案是:
如果 JS 文件没有依赖 CSS,你应该将 JS 代码放在样式表之前。 既然没有依赖,那就没有任何理由阻塞 JavaScript 代码的执行。
(尽管执行 JavaScript 代码时会停止解析 DOM, 但预加载扫描器会提前下载之后的 CSS)
如果你一部分 JavaScript 需要依赖 CSS 而另一部分却不用,最佳的实践是将 JavaScript 分为两部分,分别置于 CSS 的两侧:
<!-- 这部分 JavaScript 代码下载完后会立即执行 --> <script src="i-need-to-block-dom-but-DONT-need-to-query-cssom.js"></script> <link rel="stylesheet" href="app.css" /> <!-- 这部分 JavaScript 代码在 CSSOM 构建完成后才会执行 --> <script src="i-need-to-block-dom-but-DO-need-to-query-cssom.js"></script>
根据这种组织方式,我们的页面会按最佳的方式下载与执行相关代码。下面的截图中,粉色代表 JS 的执行,但它们都比较“纤细”了,希望你能看得清楚。(第一栏的(下同))第一行是整个页面的时间轴,留意该行粉色的部分,代表 JS 正在执行。第二行是首个 JS 文件的时间轴,可以看到下载完后并立即执行。第三行是 CSS 的时间轴,因而没有任何 JS 执行。最后一行是第二个 JS 文件的时间轴,可以清晰地看到,直到 CSS 下载完成后才执行。

注意,你应该根据页面的实际情况测试这种代码组织方式,取决于 CSS 与 JavaScript 文件大小与 JavaScript 文件执行所需的时间,可能会出现不同的结果。记得多测试!(译者注:根据实践经验,<head>
中的代码组织基本可以按照这种方式,即 JS 在 CSS 之前,因为 <head>
中的 JS 代码基本不依赖 CSS,唯一的反例是 JS 代码体积非常大或执行时间很长。)
将 <link rel="stylesheet" />
放在 <body>
中。
最后一条优化策略比较新颖,它对页面性能有很大帮助,并使页面达到逐步渲染的效果,同时易于执行。
在 HTTP/1.1 中,我们习惯于将全部的 css 打成一个文件,如 app.css:
<html> <head> <link rel="stylesheet" href="app.css" /> </head> <body> <header class="site-header"> <nav class="site-nav">...</nav> </header> <main class="content"> <section class="content-primary"> <h1>...</h1> <div class="date-picker">...</div> </section> <aside class="content-secondary"> <div class="ads">...</div> </aside> </main> <footer class="site-footer"> </footer> </body>
然而,从三方面而言,渲染性能降低了:
每个页面只用到 app.css 中的部分样式: 用户会下载多余的 CSS。
难以制定缓存策略: 例如,某个页面使用的日期选择器更改了背景颜色,重新生成 app.css 后,旧的 app.css 缓存将失效。
整个 app.css 在解析构建完 CSSOM 之前,页面渲染被阻塞: 尽管当前页面可能只用到了 17% 的 CSS代码,但(浏览器)仍需等待其他 83% 的代码下载并解析完后,才能开始渲染。
使用 HTTP/2,可以解决第一与第二点:
<html> <head> <link rel="stylesheet" href="core.css" /> <link rel="stylesheet" href="site-header.css" /> <link rel="stylesheet" href="site-nav.css" /> <link rel="stylesheet" href="content.css" /> <link rel="stylesheet" href="content-primary.css" /> <link rel="stylesheet" href="date-picker.css" /> <link rel="stylesheet" href="content-secondary.css" /> <link rel="stylesheet" href="ads.css" /> <link rel="stylesheet" href="site-footer.css" /> </head> <body> <header class="site-header"> <nav class="site-nav">...</nav> </header> <main class="content"> <section class="content-primary"> <h1>...</h1> <div class="date-picker">...</div> </section> <aside class="content-secondary"> <div class="ads">...</div> </aside> </main> <footer class="site-footer"> </footer> </body>
根据页面的不同组件下载不同的 CSS,能有效地解决冗余问题。这减少了对关键路径造成阻塞的 CSS 文件总大小。
同时,我们可以制定更有效的缓存策略,(当代码产生变化之后,)只会影响对应文件的缓存,其他的文件保持不变。
但仍有解决的问题:下载并解析全部 CSS 文件之前,页面的渲染仍然是阻塞的。页面的渲染时间仍然取决于最慢的 CSS 文件下载与解析的时间。假设由于某种原因,页脚的 CSS 下载需要很长时间,(即使页头的 CSSOM 已经构建完成,)浏览器也只能等待而无法渲染页头。
然而,这现象在 Chrome (v69)中得到缓解,Firefox 与 IE/Edge 也已经进行了相关的优化。<link rel="stylesheet" />
只会阻塞后续内容,而不是整个页面的渲染。这意味着我们可以用以下方式组织代码:
<html> <head> <link rel="stylesheet" href="core.css" /> </head> <body> <link rel="stylesheet" href="site-header.css" /> <header class="site-header"> <link rel="stylesheet" href="site-nav.css" /> <nav class="site-nav">...</nav> </header> <link rel="stylesheet" href="content.css" /> <main class="content"> <link rel="stylesheet" href="content-primary.css" /> <section class="content-primary"> <h1>...</h1> <link rel="stylesheet" href="date-picker.css" /> <div class="date-picker">...</div> </section> <link rel="stylesheet" href="content-secondary.css" /> <aside class="content-secondary"> <link rel="stylesheet" href="ads.css" /> <div class="ads">...</div> </aside> </main> <link rel="stylesheet" href="site-footer.css" /> <footer class="site-footer"> </footer> </body>
这样的结果是我们能逐步渲染页面,当前面的 CSS 可用时,页面将呈现对应的内容(,而不需等待全部 CSS 下载并解析完毕)。
I如果浏览器不支持这种特性,也不会损害页面的性能。整个页面将回退为原来的模式,只有在最慢的 CSS 下载并解析完成后,才能渲染页面。
有关这种特性的更多细节,建议阅读这篇文章。
总结
本文内容比较 繁杂,成文后超出了本来的预期,尝试总结了 CSS 加载相关的一系列的最佳实践,值得仔细体会:
- 懒加载非关键 CSS:
- 优先加载关键 CSS,懒加载其他 CSS;
- 或根据媒体类型拆分 CSS 文件。
- 避免使用
@import
:- 在 HTML 文档中应该避免;
- 在 CSS 文件之中更应避免;
- 以及警惕预加载扫描器的怪异行为。
- 关注 CSS 与 JavaScript 的顺序:
- 在 CSS 文件后的 JavaScript 仅在 CSSOM 构建完成后才会执行;
- 如果你的 JavaScript 不依赖 CSS;
- 将它放置于 CSS 之前;
- 如果 JavaScript 依赖 CSS:
- 将它放置于 CSS 之后。
- 仅加载 DOM 依赖的 CSS:
- 这将提高初次渲染的速度使让页面逐步渲染。
注意
本文叙述的内容都遵循规范或根据浏览器的行为推导得出,然而,你应该亲自进行测试。尽管理论上是正确的,但在实践中可能会有所不同。记得好好测试!
英文原文地址:https://csswizardry.com/2018/11/css-and-network-performance/
更多编程相关知识,请访问:编程视频!!
위 내용은 CSS 및 웹 성능에 대해 자세히 알아보기의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

vue.js에서 bootstrap 사용은 5 단계로 나뉩니다 : Bootstrap 설치. main.js.의 부트 스트랩 가져 오기 부트 스트랩 구성 요소를 템플릿에서 직접 사용하십시오. 선택 사항 : 사용자 정의 스타일. 선택 사항 : 플러그인을 사용하십시오.

WebDevelopmentReliesonHtml, CSS 및 JavaScript : 1) HtmlStructuresContent, 2) CSSSTYLESIT, 및 3) JAVASCRIPTADDSINGINTERACTIVITY, BASISOFMODERNWEBEXPERIENCES를 형성합니다.

HTML은 웹 구조를 정의하고 CSS는 스타일과 레이아웃을 담당하며 JavaScript는 동적 상호 작용을 제공합니다. 세 사람은 웹 개발에서 의무를 수행하고 화려한 웹 사이트를 공동으로 구축합니다.

Bootstrap에 이미지를 삽입하는 방법에는 여러 가지가 있습니다. HTML IMG 태그를 사용하여 이미지를 직접 삽입하십시오. 부트 스트랩 이미지 구성 요소를 사용하면 반응 형 이미지와 더 많은 스타일을 제공 할 수 있습니다. 이미지 크기를 설정하고 IMG-Fluid 클래스를 사용하여 이미지를 적응할 수 있도록하십시오. IMG 통과 클래스를 사용하여 테두리를 설정하십시오. 둥근 모서리를 설정하고 IMG 라운드 클래스를 사용하십시오. 그림자를 설정하고 그림자 클래스를 사용하십시오. CSS 스타일을 사용하여 이미지를 조정하고 배치하십시오. 배경 이미지를 사용하여 배경 이미지 CSS 속성을 사용하십시오.

부트 스트랩 분할 라인을 만드는 두 가지 방법이 있습니다 : 태그를 사용하여 수평 분할 라인이 생성됩니다. CSS 테두리 속성을 사용하여 사용자 정의 스타일 분할 라인을 만듭니다.

부트 스트랩 프레임 워크를 설정하려면 다음 단계를 따라야합니다. 1. CDN을 통해 부트 스트랩 파일 참조; 2. 자신의 서버에서 파일을 다운로드하여 호스팅하십시오. 3. HTML에 부트 스트랩 파일을 포함; 4. 필요에 따라 Sass/Less를 컴파일하십시오. 5. 사용자 정의 파일을 가져옵니다 (선택 사항). 설정이 완료되면 Bootstrap의 그리드 시스템, 구성 요소 및 스타일을 사용하여 반응 형 웹 사이트 및 응용 프로그램을 만들 수 있습니다.

부트 스트랩 버튼을 사용하는 방법? 부트 스트랩 CSS를 소개하여 버튼 요소를 만들고 부트 스트랩 버튼 클래스를 추가하여 버튼 텍스트를 추가하십시오.
