消除阻塞渲染的 CSS 并使用 Performance API 测量页面渲染时间

Graphs of performance analytics on a laptop screen
Published on
/8 mins read/---

CSS 被浏览器视为阻塞渲染资源之一 - 这类资源必须在用户看到页面内容之前完成加载。

为什么要避免阻塞渲染的 CSS?

  • 阻塞渲染的 CSS 会降低网站向用户展示内容的速度。

  • 你的网站加载的每个 CSS 文件都会增加页面的首次绘制时间,这意味着如果页面需要加载大量 CSS,用户就需要等待更长时间才能看到内容。

render-blocking-css
CSS greatly affects page load time

当页面开始加载时,浏览器会自动加载所有的 CSS 文件,不管这些文件是否会阻塞渲染过程!

那么,如何限制阻塞渲染的 CSS 呢?

解决方案

如果你发现你的页面中有一些 CSS 只在特定情况下使用, 比如模态框中的样式(用户需要点击才能打开查看)、不是首先显示的标签页中的内容,或者只适用于大屏幕或移动设备的样式...

这里有一些方法可以帮助你的页面更快地加载。

使用 media 属性

当你想要在网页中加载 CSS 时,你会使用 link 标签,像这样:

<link href="style.css" rel="stylesheet" />
<link href="print.css" rel="stylesheet" />
<link href="style.mobile.css" rel="stylesheet" />

在加载 HTML 后,浏览器还会加载这 3 个 CSS 文件,并且只有在它们全部加载完成后才会显示内容。

然而,print.css 只在打印文档时使用(Ctrl/Cmd + P),而 style.mobile.css 仅用于移动设备上的样式。

在这种情况下,我们可以使用 media 属性

<link href="style.css" rel="stylesheet" />
<link href="print.css" media="print" rel="stylesheet" />
<link href="style.mobile.css" media="(max-width: 568px)" rel="stylesheet" />

现在浏览器明白它只需要加载 style.css 文件就可以立即向用户显示页面内容,而无需等待其他两个文件加载完成。

对于使用 media 属性的链接:

  • media="print": 这个文件中的样式只会在打印文档时应用,所以在加载页面时不需要渲染,也不会阻塞首次渲染。
  • media="(max-width: 568px)": 这些样式只会在设备宽度为 max-width=568px 时应用,在桌面/平板设备上加载页面时不会阻塞首次渲染。

使用 media 属性,我们可以在特定情况下调整页面显示,比如渲染后、调整屏幕大小、改变设备方向(横向/纵向)等。

media 的值必须是 媒体类型媒体查询,这在加载外部样式表时非常有用 - 它可以帮助浏览器选择首次渲染所需的 CSS。

合并 CSS 或内联 CSS

一个有效的方法是,如果 CSS 文件不是太大,可以直接将 CSS 放在文档头部的 style 标签中。这种方法能很好地提升性能,因为它只需要在 DOM 加载完成后就能立即显示。

或者你可以限制加载过多的 CSS 文件。通常在编码时,开发者倾向于按组件、模块等将不同的 CSS 文件分开,以便于管理。但是,加载多个 CSS 文件比只加载一个文件要花费更长的时间。
inline-css
One of the techniques to help Gatsby site load extremely fast is Inline CSS

Performance API

现在让我们使用 Chrome Performance API 来测量应用了避免阻塞渲染 CSS 技术后的页面渲染时间

我提供了一个简单的示例来帮助你理解阻塞渲染 CSShttps://hta218.github.io/render-blocking-css-example/

在这个示例中,我加载了两个 CSS 文件,并比较了在设备宽度大于和小于 800px 的屏幕上的页面渲染时间

<link rel="stylesheet" href="tailwind.css" media="(min-width: 800px)" />
<link rel="stylesheet" href="bootstrap.css" media="(min-width: 800px)" />

在使用 Performance API 之前,一定要先检查浏览器是否支持这个功能。

if ('PerformanceObserver' in window) {
  try {
    // Create PerformanceObserver instance
    let perfObsever = new PerformanceObserver((perf) => {
      let perfEntries = perf.getEntriesByType('paint')
      perfEntries.forEach(({ name, startTime }) => {
        // Get the result inside this callback
        console.log(`The time to ${name} was ${startTime} milliseconds.`)
      })
    })
 
    // observe "paint" event
    perfObsever.observe({ entryTypes: ['paint'] })
  } catch (err) {
    console.error(err)
  }
} else {
  // Remember to check the browser compatibility before using this API
  console.log("Performance API isn't supported!")
}

Performance API 有许多不同的性能指标,在这个例子中,我使用了 PerformancePaintTiming

为了更清楚地看到结果,你可以打开 Chrome DevTools,通过限制网络速度和 CPU 性能来模拟用户设备的实际情况。

对于屏幕宽度 >800px 的情况(在首次页面渲染前加载所有 CSS):

css-block-render

我们需要超过 6 秒才能完成首次绘制(在所有 CSS 加载完成后)- 这相当于用户需要等待 6 秒才能看到页面内容。

在只需要一个 CSS 文件进行首次绘制的情况下(其余文件仍在加载但不用于首次渲染):

css-not-block-render

用户可以提前 2 秒看到内容,即使其他两个 CSS 文件还没有加载完成,页面也能正常渲染。

以下是使用 Performance API 测量的结果总结:

render-result

另外,你也可以直接使用 PerformancePaintTiming API

if (window.performance) {
	let performance = window.performance;
	let perfEntries = performance.getEntriesByType('paint');
 
	perfEntries.forEach({ name, startTime } => {
		console.log(`The time to ${name} was ${startTime} milliseconds.`);
	});
} else {
	console.log("Performance timing isn't supported.");
}

总结

我们可以清楚地看到页面渲染时间的显著差异,当网页必须加载过多不必要的 CSS 时,这直接影响了用户体验。 因此,我们需要非常谨慎地处理这类资源。

有许多方法可以避免阻塞渲染的 CSS,比如使用媒体类型媒体查询合并 CSS 以及内联 CSS。 这些改变看似微小,但对性能有着重大影响。

参考资料