我的个人博客

消除渲染阻塞 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 极大地影响页面加载时间

开始加载页面时,浏览器会自动加载所有 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 文件就可以立即向用户显示页面内容,而无需等待其他 2 个文件加载。

对于使用 media 属性的链接:

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

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

media 的值必须是 media typemedia query,这在加载外部样式表时非常有用 - 它帮助浏览器为首次渲染选择必要的 CSS。

合并 CSS 或内联 CSS

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

或者你可以限制加载过多的 CSS 文件。通常在编码时,开发人员倾向于按组件、模块等分离不同的 CSS 文件以便于管理。然而,加载许多 CSS 文件比只加载一个文件需要更长的时间。

inline-css
帮助 Gatsby 网站加载极快的技术之一是内联 CSS

Performance API

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

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

在这个例子中,我加载了两个 CSS 文件,并比较了在 device-width 大于和小于 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 {
    // 创建 PerformanceObserver 实例
    let perfObsever = new PerformanceObserver((perf) => {
      let perfEntries = perf.getEntriesByType('paint')
      perfEntries.forEach(({ name, startTime }) => {
        // 在这个回调中获取结果
        console.log(`The time to ${name} was ${startTime} milliseconds.`)
      })
    })
 
    // 观察 "paint" 事件
    perfObsever.observe({ entryTypes: ['paint'] })
  } catch (err) {
    console.error(err)
  }
} else {
  // 记得在使用此 API 之前检查浏览器兼容性
  console.log("Performance API isn't supported!")
}

有许多不同的 Performance 指标,在这种情况下,我使用了 PerformancePaintTiming

为了更清楚地看到结果,你可以打开 Chrome DevTools,限制网络和 CPU 速度以模拟用户设备的条件。

对于 >800px 的屏幕(在首次页面渲染之前加载所有 CSS),

css-block-render

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

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

css-not-block-render

用户提前 2 秒看到内容,并且即使其他 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,例如使用 media typesmedia queries合并 CSS内联 CSS。 这些变化看似微小,但对性能有显著影响。

参考资料