前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • HTTP

    • 基础篇

      • HTTP的前世今生
      • HTTP是什么
      • HTTP世界全览
      • HTTP分层
      • 键入网址到回车发生什么
      • HTTP报文是什么样子的
      • 理解请求方法
      • URI
      • 响应状态码
      • HTTP有哪些特点
      • HTTP优缺点
      • HTTP的实体数据
      • HTTP传输大文件
      • HTTP的连接管理
      • HTTP的重定向
      • HTTP的Cookie机制
      • HTTP的缓存控制
      • HTTP的代理服务
      • HTTP的缓存代理
    • 高级篇

    • 扩展篇

  • 浏览器相关

  • 计算机基础

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

渲染流水线:CSS如何影响首次加载时的白屏时间|浏览器篇

# 渲染流水线:CSS如何影响首次加载时的白屏时间?

30 秒速记

  • 主文档返回后,网络进程把 HTML 字节流交给渲染进程,渲染进程解析并逐步构建 DOM。
  • 渲染进程接收 HTML 时会通过预解析提前发现外部 CSS 和 JavaScript,并尽早发起相应资源下载。
  • 外部样式下载完成后还要解析为 CSSOM;布局树依赖 DOM 与 CSSOM,缺少任一方都不能进入下一步。
  • 如果 DOM 已构建完成而外部 CSS 尚未就绪,渲染流水线只能等待,这段空档会延长首次显示前的白屏。
  • 优化白屏应关注关键样式从被发现、下载到生成 CSSOM 的完整路径,而不只是 HTML 的解析速度。

外部 CSS 会影响首次白屏时间,因为布局树必须等 DOM 和 CSSOM 都准备好后才能生成。 主文档返回后,渲染进程一边解析 HTML、构建 DOM,一边通过预解析提前发现并下载样式和脚本。即使 DOM 已经完成,只要样式还没下载并解析成 CSSOM,渲染流水线就只能等待。简单来说,优化白屏不能只盯着 HTML 解析,还要关注关键样式从发现、下载到解析完成的整条路径。

在上一篇文章中我们详细介绍了 DOM 的生成过程,并结合具体例子分析了 JavaScript 是如何阻塞 DOM 生成的。那本文我们就继续深入聊聊渲染流水线中的 CSS。因为 CSS 是页面中非常重要的资源,它决定了页面最终显示出来的效果,并影响着用户对整个网站的第一体验。所以,搞清楚浏览器中的 CSS 是怎么工作的很有必要,只有理解了 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 文件时的渲染流水线,你可以先参考下面这张渲染流水线示意图:

下面我们结合上图来分析这个页面文件的渲染流水线。

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

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

面试官追问

追问 1商品详情页的 DOM 已经构建完成,但 300 KB 的 theme.css 仍在下载,产品认为正文节点既然存在就应立刻显示,你怎么解释这段白屏?
参考回答

正文节点存在并不代表已经具备首次展示条件,布局树还同时依赖 DOM 和由样式解析得到的 CSSOM。在 theme.css 下载并解析完成前,渲染流水线需要等待;节点数量不是判断页面能否绘制的充分依据。

追问 2代码评审中有人说预解析线程已经提前发现 theme.css,所以外链样式不会再影响首屏,你会如何反驳?
参考回答

预解析只会让样式更早被发现并发起下载,不能消除布局树对 CSSOM 的依赖。若 DOM 已完成而样式仍未返回,流水线依然会出现等待;是否形成明显白屏取决于资源到达与解析的实际时序。

追问 3营销页从单个内联样式改为外部 theme.css 后,DOM 构建速度没有变化却出现空档,你会在工程上记录哪两个关键时间点?
参考回答

应分别记录 DOM 构建结束和 theme.css 下载、解析为 CSSOM 完成的时间,以确认空档是否来自样式等待。还要保留主文档到达及开始构建 DOM 的时点;否则容易把另一段空闲时间误算成样式阻塞。

追问 4主文档响应很快,但性能记录显示收到 HTML 字节流后,渲染进程迟迟没有开始构建 DOM,负责人仍坚持只优化 CSS,你会怎么处理?
参考回答

不会把全部精力放在样式上,因为主文档请求与 DOM 构建之间本身也可能存在渲染瓶颈。应先区分这段空闲与 DOM 完成后等待 CSSOM 的空闲;若延迟发生在发现样式之前,缩小 theme.css 未必有效。

追问 5一个页面的外链样式下载很快,但解析完成后首屏仍未出现,你能否仅凭“CSS 会影响白屏”断定样式仍是根因?
参考回答

不能,CSSOM 就绪只满足构造布局树的一项前提,后续仍有样式计算、布局和绘制等流程。应沿时间线确认等待究竟发生在哪个阶段;如果样式阶段已结束,继续压缩样式可能掩盖真正的渲染瓶颈。

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

30 秒速记

  • CSSOM 是浏览器解析 CSS 后得到的结构化模型,使渲染引擎和 JavaScript 能够读取、计算及修改样式。
  • CSSOM 一方面通过 document.styleSheets 等接口提供样式表操作能力,另一方面为布局树提供元素的最终样式信息。
  • 布局树由 DOM 与 CSSOM 共同生成:不可见或不参与展示的节点会被过滤,随后完成样式计算和几何布局。
  • 解析器遇到可能修改页面的同步脚本时会暂停构建 DOM;如果脚本可能访问样式,执行前还必须等待相关 CSSOM 就绪。
  • 外部 CSS 与 JavaScript 可由预解析器并行下载,但脚本执行仍可能受 CSSOM 阻塞,最终代价取决于资源下载和依赖顺序。

渲染流水线需要 CSSOM,因为浏览器不能直接使用 CSS 文本,必须先把它解析成可计算、可操作的结构。 它既给 JavaScript 提供 document.styleSheets 等样式操作能力,也为布局树提供最终样式。构建布局树时,浏览器会结合 DOM 和 CSSOM,过滤不展示的节点,再完成样式计算与布局。若同步脚本可能读取样式,即使样式和脚本能并行下载,脚本执行仍要等待相关 CSSOM 就绪。

和 HTML 一样,渲染引擎也是无法直接理解 CSS 文件内容的,所以需要将其解析成渲染引擎能够理解的结构,这个结构就是 CSSOM。和 DOM 一样,CSSOM 也具有两个作用,第一个是提供给 JavaScript 操作样式表的能力,第二个是为布局树的合成提供基础的样式信息。这个 CSSOM 体现在 DOM 中就是document.styleSheets。具体结构你可以去查阅相关资料,这里我就不过多介绍了,你知道 CSSOM 的两个作用是怎样的就行了。

有了 DOM 和 CSSOM,接下来就可以合成布局树了,我们在前面《05 | 渲染流程(上):HTML、CSS 和 JavaScript 文件,是如何变成页面的?》这篇文章中讲解过布局树的构造过程,这里咱们再简单回顾下。等 DOM 和 CSSOM 都构建好之后,渲染引擎就会构造布局树。布局树的结构基本上就是复制 DOM 树的结构,不同之处在于 DOM 树中那些不需要显示的元素会被过滤掉,如 display:none 属性的元素、head 标签、script 标签等。复制好基本的布局树结构之后,渲染引擎会为对应的 DOM 元素选择对应的样式信息,这个过程就是样式计算。样式计算完成之后,渲染引擎还需要计算布局树中每个元素对应的几何位置,这个过程就是计算布局。通过样式计算和计算布局就完成了最终布局树的构建。再之后,就该进行后续的绘制操作了。

这就是在渲染过程中涉及到 CSS 的一些主要流程。

了解了这些之后,我们再来看看稍微复杂一点的场景,还是看下面这段 HTML 代码:

//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 标签内部加了一个简单的 JavaScript。有了 JavaScript,渲染流水线就有点不一样了,可以参考下面这张渲染流水线图:

那我们就结合这张图来分析含有外部 CSS 文件和 JavaScript 代码的页面渲染流水线,上一篇文章中我们提到过在解析 DOM 的过程中,如果遇到了 JavaScript 脚本,那么需要先暂停 DOM 解析去执行 JavaScript,因为 JavaScript 有可能会修改当前状态下的 DOM。

不过在执行 JavaScript 脚本之前,如果页面中包含了外部 CSS 文件的引用,或者通过 style 标签内置了 CSS 内容,那么渲染引擎还需要将这些内容转换为 CSSOM,因为 JavaScript 有修改 CSSOM 的能力,所以在执行 JavaScript 之前,还需要依赖 CSSOM。也就是说 CSS 在部分情况下也会阻塞 DOM 的生成。

我们再来看看更加复杂一点的情况,如果在 body 中被包含的是 JavaScript 外部引用文件,Demo 代码如下所示:

//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 的外部引用和 JavaScript 外部文件,那它们的渲染流水线是怎样的呢?可参考下图

从图中可以看出来,在接收到 HTML 数据之后的预解析过程中,HTML 预解析器识别出来了有 CSS 文件和 JavaScript 文件需要下载,然后就同时发起这两个文件的下载请求,需要注意的是,这两个文件的下载过程是重叠的,所以下载时间按照最久的那个文件来算。

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

← DOM树:JavaScript是如何影响DOM树构建的分层和合成机制:为什么css动画比JavaScript高效 →

fe
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • HTTP

    • 基础篇

      • HTTP的前世今生
      • HTTP是什么
      • HTTP世界全览
      • HTTP分层
      • 键入网址到回车发生什么
      • HTTP报文是什么样子的
      • 理解请求方法
      • URI
      • 响应状态码
      • HTTP有哪些特点
      • HTTP优缺点
      • HTTP的实体数据
      • HTTP传输大文件
      • HTTP的连接管理
      • HTTP的重定向
      • HTTP的Cookie机制
      • HTTP的缓存控制
      • HTTP的代理服务
      • HTTP的缓存代理
    • 高级篇

    • 扩展篇

  • 浏览器相关

  • 计算机基础