浏览器之CSS如何影响首次加载时间

本文先站在渲染流水线的视角来介绍CSS是如何工作的,然后通过CSS工作流程来分析性能瓶颈,最后再来讨论如何减少首次加载的白屏事件。

渲染流水线视角下的CSS

//theme.css
div{ 
 color : coral;
 background-color:black
}


<html>
<head>
 <link href="theme.css" rel="stylesheet">
</head>
<body>
 <div>geekbang com</div>
</body>
</html>

这两段代码分别由CSS和HTML文件构成,我们来分析下打开这段HTML文件时渲染流水线,可以参考下示意图:

css_render_pipeline_01

结合图分析:

首先是发起主页面的请求,这个发起请求方可能是渲染进程,也有可能是浏览器进程,发起请求被送到网络进程中去执行。网络进程接收到返回的HTML数据后,将其发送给渲染进程,渲染进程会解析HTML数据并构建DOM。这里需要特别注意下,请求HTML数据和构建DOM中间有一段空闲时间,这个空闲时间可能成为页面渲染的瓶颈。

之前的文章提到过当渲染进程接受HTML文件字节流,会开启一个预解析线程,如果遇到JS文件或者CSS文件,预解析线程会提前下载这些数据。杜宇上面代码,预解析线程会解析出来一个外部的theme.css文件并发起下载,这里也有一个空闲时间需要注意一下,就是再DOM构建结束之后,theme.css还未下载完成这段时间里,渲染流水线无事可做,因为下一步是合成布局树,而合成布局树需要CSSOM和DOM,所以这里需要等待CSS加载结束并解析成CSSOM。

那渲染流水线为什么需要CSSOM?

和HTML一样,渲染引擎无法直接理解CSS内容。需要转换其微CSSOM可以理解的内部结构。

CSSOM也有两个作用

  • 给JS操作样式表的能力

  • 给布局树合成提供基础的样式信息

理解了这些之后,来看稍微复杂的场景

//theme.css
div{ 
 color : coral;
 background-color:black
}



<html>
<head>
 <link href="theme.css" rel="stylesheet">
</head>
<body>
 <div>geekbang com</div>
 <script>
 console.log('time.geekbang.org')
 </script>
 <div>geekbang com</div>
</body>
</html>

这段代码再开头的基础上做了一点小修改,再body内部加了一个JS。有了JS渲染流水线就不一样了,参考下面:

css_render_pipeline_02

结合这个图来分析含有外部CSS文件和JS代码的页面的渲染流水线。

我们知道在解析DOM的过程中如果遇到了JS脚本,那么需要先暂停DOM解析先去执行JS,因为JS有可能会修改当前状态的DOM。

不过在执行JS脚本之前,如果页面中包含了外部的CSS文件的引用,或者通过Style标签内置了CSS内容,那么渲染引擎还需要将这些内容转换为CSSOM,因为JS有修改CSSOM的能力,所以在执行JS之前,还依赖CSSOM。

再复杂一点的情景

//theme.css
div{ 
 color : coral;
 background-color:black
}
//foo.js
console.log('time.geekbang.org')
<html>
<head>
 <link href="theme.css" rel="stylesheet">
</head>
<body>
 <div>geekbang com</div>
 <script src='foo.js'></script>
 <div>geekbang com</div>
</body>
</html>

HTML包含了CSS外部引用和JS外部文件,它的流水线是什么样子呢

css_render_pipeline_03

图中可以看出,在接受到HTML数据之后预解析的过程中,HTML识别出由CSS文件、JS文件需要下载,于是就同时发起这两个文件的下载请求,需要注意的是,这两个文件下载的过程是重叠的,所以下载的事件按照最久的那个文件来计算。

最后流水线就和前面一样了,不管CSS和JS文件谁先到达,都要等到CSS文件下载完成并生成CSSOM,然后在执行JS脚本,最后在继续构建DOM,构建布局树,绘制页面。

影响页面展示的因素以及优化策略

前面花那么多文字来分析流水线,主要的原因是——渲染流水线影响到了首次页面展示的速度,而首次页面展示的速度有直接影响到用户体验。

从URL发起开始,到首次页面展示,视觉上经历了三个阶段

  1. 第一阶段:请求发出去到提交数据阶段,页面展示出来的还是之前的页面

  2. 第二阶段,提交数据之后渲染进程会创建一个空白页面,我们通常把这个阶段称为 预解析白屏,并且等待CSS文件和JS文件加载完成,生成CSSOM和DOM,然后合成布局树,最后还要经历一系列步骤准备首次渲染

  3. 等首次渲染完成,就开始进入完整页面的生成阶段,页面会一点点被绘制出来

影响第一阶段的因素主要是网络或者服务器处理这边。

第三阶段会在后面文章中分析。

终端关注第二阶段,这个阶段主要问题是白屏时间,如果白屏时间过久就会影响到用户体验。为了缩短白屏时间,我们挨个分析这个阶段的主要任务,包括:

  • 解析HTM
  • 下载CSS
  • 下载JS
  • 生成CSSOM
  • 执行JS
  • 生成布局树
  • 绘制页面 ……

这种情况下主要的瓶颈在于 下载CSS文件,下载JS文件和执行JS

所以想要白屏时间短可以有一下策略:

  • 通过内联JS、内敛CSS来移除这两种文件的下载,获取HTML可以直接开始渲染
  • 并不是所有场景都适合内敛,那么经量减少文件大小,通过webpack等压缩,减少不必要注释
  • 将一些不在解析HTML阶段使用的JS标记上sync或者defer
  • 对于大的CSS文件,可以通过媒体查询将其拆分成不同用途的CSS文件,这样只有在特定的场景下才回家再特定的CSS文件。

Mark24

Everything can Mix.