编写关键视窗以上 CSS

Avatar of Ben Edwards
Ben Edwards

DigitalOcean 为您的旅程的每个阶段提供云产品。从 200 美元的免费积分 开始!

以下是来自 Ben Edwards 的客座文章。我看到 Ben 在推特上发布了一条关于一个简单的 Sass @mixin 的推文,该 mixin 允许您将 CSS 的一部分指定为“关键”——其理念是先加载该关键 CSS,然后延迟加载其余的 CSS。这是一个聪明的想法,并且在网页性能人群中越来越受欢迎。我认为我应该让 Ben 为我们更详细地介绍这些想法。

Google PageSpeed Insights 和我的网页;这简直是天作之合,直到情况发生变化……PageSpeed 开始告诉我需要优化 CSS 传递,我的 CSS 文件是渲染阻塞的,我的页面上所有视窗以上的元素在那些文件加载之前都无法渲染,并且我应该将那些文件中的关键部分直接内联到我的 HTML 中。

回家去吧,PageSpeed,我哭喊着,谁会想要在自己的 HTML 中看到一堆 CSS?我是一个合法的专业人士,你知道我有自己的工作流程的! 我嘲笑着。

我一直坚持我的立场,直到我看到了以下这条推文

我想看到一个类似于 CSS Zen Garden 的网站,但是开发人员试图让
同一个响应式网站在 webpagetest.org 上获得更高的分数

Scott Jehl

我一直致力于让我的网页在 webpagetest.org 上获得尽可能高的分数,这需要改变工作流程,那么为什么我不应该为 PageSpeed 改变呢?现在,如果您已经在使用 Google 的 mod_pagespeed 模块,那么您可以放轻松,并给自己一个拥抱,因为 该模块已经为您搞定了。对于像我一样没有使用该模块的人来说,以下是我的方法。

以下是科学原理

为了解决这个问题,我首先需要了解 PageSpeed 想要告诉我什么。外部样式表(读取那些通过 link 标签包含的样式表)是渲染阻塞的。这意味着浏览器在所有 CSS 下载完毕之前不会将内容绘制到屏幕上。再加上这样一个事实,即渲染页面所需的數據量如果超过了初始拥塞窗口(通常为压缩后的 14.6kB),则需要在服务器和用户浏览器之间进行额外的往返。所有这一切都会导致额外的网络延迟,对于使用高延迟网络的用户(例如移动设备用户)来说,这会导致页面加载的重大延迟。

PageSpeed 的建议是将您的 CSS 分成两部分;一部分内联,负责为视窗以上的元素进行样式设置,另一部分可以延迟加载。现在,在我们纠结折疊是否存在之前,我们不妨先同意,任何能够更快地将数据传输给用户的方法都是一件好事,对吧?

确定什么是关键的

确定 CSS 中哪些部分是关键的需要检查我的网页在“移动设备”和“桌面”尺寸下的情况,然后对视窗中可见的元素应用的 CSS 规则进行截图。这似乎是一项艰巨的任务,但别担心,一些非常聪明的人来帮忙了

使用检查结果,我现在需要修改我的 HTML 以非渲染阻塞的方式加载我的 CSS。

异步加载,摆脱烦恼

假设我的一个 HTML 文档如下所示

<html>
  <head>
    <link rel="stylesheet" href="things.css">
  </head>
  <body>
    <div class="thing1">
      Hello world, how goes it?
    </div>
    ...
    <div class="thing2">
      Hey, I'm totally below-the-fold
    </div>
  </body>
</html>

假设 things.css 包含以下内容

.thing1 { color: red; }
.thing2 { background: green; }

使用检查结果,我现在可以在 head 中内联关键的视窗以上部分的 CSS,如下所示

<html>
  <head>
    <style>
      .thing1 { color: red; }
    </style>
  </head>
  <body>
    <div class="thing1">
      Hello world, how goes it?
    </div>
    ...

将它与 Filament Group 的 loadCSS 结合使用,我可以异步加载其余的视窗以下的 CSS,如下所示

    ...
    <div class="thing2">
      Hey, I'm totally below-the-fold
    </div>
    <script>
      /*!
      Modified for brevity from https://github.com/filamentgroup/loadCSS
      loadCSS: load a CSS file asynchronously.
      [c]2014 @scottjehl, Filament Group, Inc.
      Licensed MIT
      */
      function loadCSS(href){
        var ss = window.document.createElement('link'),
            ref = window.document.getElementsByTagName('head')[0];

        ss.rel = 'stylesheet';
        ss.href = href;

        // temporarily, set media to something non-matching to ensure it'll
        // fetch without blocking render
        ss.media = 'only x';

        ref.parentNode.insertBefore(ss, ref);

        setTimeout( function(){
          // set media back to `all` so that the stylesheet applies once it loads
          ss.media = 'all';
        },0);
      }
      loadCSS('things.css');
    </script>
    <noscript>
      <!-- Let's not assume anything -->
      <link rel="stylesheet" href="things.css">
    </noscript>
  </body>
</html>

未来的工作流程

好消息!PageSpeed 很高兴!它不再抱怨渲染阻塞的 CSS,并对视窗以上的内容得到应有的优先级感到满意,但在当今的 CSS 预处理器和前端工具的世界中,像上面这样的手动流程是行不通的。

自动化的方式

对于那些希望找到一种类似于 mod_pagespeed 的自动化方式,并且也熟悉 Node 的人(对于那些不熟悉的人表示歉意,但在 Clock,它是我们所做的一切的重要组成部分),您一定会想要了解 PenthouseAddy Osmani 的 实验性 Node 模块 Critical,它们都提供了一种方式来内联或操作关键 CSS,这些 CSS 是通过 PageSpeed API 确定的。虽然完全自动化的工作流程听起来很完美,但目前让我感到困扰的一点是,这些工具没有解决内联的 CSS 规则在视窗以下的 CSS 下载完毕后会被再次提供的问题。从尽可能少地向用户发送数据的角度来看,这感觉是一种不必要的重复。

CSS 预处理器来救援

在我看来,使用您最喜欢的 CSS 预处理器来编写视窗以上和视窗以下的 CSS 似乎是理所当然的,也是 Clock 的前端团队目前正在尝试的方法。

新的项目非常适合这种方法,关键 CSS 和非关键 CSS 可以通过一些结构良好的 @import 规则来编写

/* critical.scss - to be in-lined */
@import "header";
/* non-critical.scss - to be asynchronously loaded */
@import "web-fonts";
@import "footer";

如果您的部分不适合这种结构,那么 Team Sass 的条件样式 Compass 插件 Jacket 将非常有用。例如,如果您的部分 _shared.scss 包含了视窗以上和视窗以下元素的规则,则关键规则和非关键规则可以被 Jacket 这样包装起来

@include jacket(critical) {
  .header {
    color: red;
  }
}

@include jacket(non-critical) {
  @include font-face(...);
  ...

 .footer {
    color: blue;
  }
}

然后,critical.cssnon-critical.css 可以按如下方式进行编辑,以生成相同的 CSS

/* critical.scss - to be in-lined */
$jacket: critical;
@import "shared";
/* non-critical.scss - to be asynchronously loaded */
$jacket: non-critical;
@import "shared";

这种方法也与社区中许多人以组件级别而不是全局位置来编写媒体查询的方式相一致,并且可以用于在组件级别定义关键和非关键 CSS 规则。

我们仍在研究这些内容

虽然 PageSpeed Insights 网页版更新 已经快一年了,但我认为关键 CSS 和优先加载视窗以上内容的话题在过去几个月才开始得到广泛关注。

我希望通过向您提供一些关于我处理关键 CSS 编写方式的见解,能够激励您将其融入到您的工作流程中。并且请密切关注上面提到的工具,因为大多数工具都处于开发的早期阶段,我预计未来会有令人兴奋的变化。