浏览器渲染:从文本到图片

画面的绘制并不是魔法

当我第一次开始前端开发(Vue.js)时,我认为浏览器只是一个“显示代码的框架”。很自然,如果您使用

,就会出现一个正方形,如果您使用 color: red,它就会变成红色。

但是,当我在实践中遇到动画卡顿或滚动时屏幕抖动(卡顿)时,我的想法发生了变化。 “为什么相同的代码在 Chrome 中运行顺利,但在 Internet Explorer (IE) 中崩溃?” “为什么当我用 JavaScript 稍微改变一下样式时,我的 CPU 就会飙升?”

事实证明,浏览器不仅仅是一个图片框架。浏览器是一个巨大的“画家”和“建筑师”,它解释我们发送的文本(HTML/CSS),以数学方式计算每个像素,并将其打印出来。

上次我们通过崎岖的网络道路安全地传递了数据。这次我们打开到达的数据盒(数据包),看看浏览器是如何绘制出一张称为屏幕的图片的。这是回复:启动 CS 基础知识系列的最后一个故事。

浏览器是一个构建站点,代码在这里被读取、构建和绘制。

货物到达,组装开始

在我们的“数字物流中心”世界观中,产品(HTML 文件)现已到达客户家中(浏览器)。不过,送来的并不是成品,而是一本‘组装手册’。称为浏览器引擎(渲染引擎)的工作团队阅读本手册并立即开始组装。这个过程称为渲染管道。

第 1 步:蓝图分析(解析和 DOM 树)

读取首先到达的 HTML 文本并创建“骨架”。

  • <html>是根(Root),<body>是树干,<div>是树枝……
  • 像这样以树(Tree)结构构建出来的东西,就叫做 DOM(Document Object Model)。它同时也是一套 API,让 JavaScript 之后能够操作这副骨架。

第 2 步:室内设计(CSSOM 树)

然后,读取 CSS 文件并创建“样式规则”。

  • “所有 div 都是红色”,“类名为 box 的元素尺寸为 100px”
  • 这就叫做 CSSOM(CSS Object Model)。

第 3 步:创建工单(渲染树)

现在我们将骨架(DOM)和设计(CSSOM)结合起来。这里重要的一点是,仅选择“实际出现在屏幕上的内容”。

  • 带有 display: none 属性的元素呢?它反正不会显示在屏幕上,所以会从渲染树中排除。(也不占用空间)
  • 相反,visibility: hidden 呢?虽然看不见,但仍然占用空间,所以会被包含在渲染树中。
渲染树只是“可以真实绘制的树”的最终列表。

[代码验证]回流和重绘

现在是非常重要的一步。性能优化的关键就在这里。创建渲染树后,浏览器将执行两项繁重的任务。

  1. 布局(Layout)/ 回流(Reflow):以数学方式计算每个元素要“放在哪里、以多大尺寸”摆放。(建筑设计)
  2. 绘制(Paint)/ 重绘(Repaint):在计算好的位置上真正地“上色”。(粉刷作业)

让我们感受一下代码的成本差异。

const box = document.getElementById('box');

// Case 1: 觸發 Reflow 的程式碼(昂貴!)
// 改變大小或位置時,連帶周圍元素的位置也必須重新計算。
box.style.width = '200px'; 
box.style.height = '200px';
box.style.margin = '20px';

// Case 2: 只觸發 Repaint 的程式碼(相對便宜)
// 位置不變,只改顏色。不需重新計算,只要重新繪製即可。
box.style.backgroundColor = 'blue';
box.style.color = 'white';
box.style.visibility = 'hidden';

分析:

  • 修改 width:这是改变建筑大小的工程。隔壁的建筑会被挤开,再隔壁的建筑也会被挤开。浏览器必须把整台计算器重新敲一遍。(触发 Reflow -> 性能下降的罪魁祸首)
  • 修改 color:建筑大小保持不变,只是重新刷一遍漆,不需要任何计算。(触发 Repaint -> 很快)

在实践中,大多数“动画卡顿”的情况都是由于实时触摸 width、left 和 top 等布局属性而发生的。在这种情况下,需要一种技术来通过使用诸如 transform 之类的属性来跳过布局计算。

实用建议:不要欺负你的浏览器

我在大学的时候并不知道这一点,所以我随机地用 JavaScript 操作了 DOM。通过执行 for 语句,我更改了每个元素的大小和样式。从浏览器的角度来看,这就像每秒数十次摧毁和重建建筑物的折磨。

确保性能的提示:

  1. DOM 操作要集中一次完成:不要一个一个地修改样式,而是预先定义好 class,只修改一次 className。
  2. 动画交给 GPU:不用 left、top,改用 transform: translate(),就能跳过布局阶段,交由 GPU 处理。
  3. 隐藏时的区别:如果要彻底去掉,就用 display: none(排除在渲染树之外);如果只想空出位置,就用 visibility: hidden(保留在渲染树中)。两者要区分使用。
更改位置(回流)是比更改颜色(重绘)昂贵得多的操作。

系列 1 总结:我的 CS 重新启动

通过本文,[回复:启动]CS 基础知识系列结束了。

我们一起踏上了漫长的旅程。

  1. 语言(Language):人写的代码被翻译成机器语言(构建),
  2. 内存(Memory):入住名为堆栈和堆的房间,
  3. 进程(OS):借助名为线程的分身术分担 CPU,
  4. 网络(Network):被拆分成数据包,在崎岖的道路上奔驰之后,
  5. 渲染(Rendering):遇到了名为浏览器的画家,终于出现在我们眼前。

一开始,我只是为了找工作而背一些知识。然而,在实践中遇到错误并重新审视之后,CS(计算机工程)并不是一纸空文,而是支撑我的代码的坚实的基础。

基础工作现已完成。是时候在坚实的土地上建造自己的城堡了。在下一个系列【Monolith Builder】中,我将介绍使用我在实践中经历过的 Spring Boot 和 Vue.js 构建 Web 应用程序。请期待“理论”转化为“实践”的过程。

發佈留言