浏览器渲染:从 HTML 到屏幕上的像素

举报
yd_232225224 发表于 2026/10/23 14:46:16 2026/10/23
【摘要】 打开一个网页,浏览器在一瞬间就把文字、图片、按钮、动画呈现在屏幕上。用户可以滚动页面、点击链接、在输入框中打字,页面随之流畅地响应。这一切看似理所当然,但在背后,浏览器需要完成一系列复杂的工作:解析 HTML 和 CSS,执行 JavaScript,计算每个元素的大小和位置,将它们绘制成像素,并在用户交互和动画中以每秒数十次的频率不断更新画面。理解浏览器的渲染过程,是编写高性能网页的基础。许...

打开一个网页,浏览器在一瞬间就把文字、图片、按钮、动画呈现在屏幕上。用户可以滚动页面、点击链接、在输入框中打字,页面随之流畅地响应。这一切看似理所当然,但在背后,浏览器需要完成一系列复杂的工作:解析 HTML 和 CSS,执行 JavaScript,计算每个元素的大小和位置,将它们绘制成像素,并在用户交互和动画中以每秒数十次的频率不断更新画面。

理解浏览器的渲染过程,是编写高性能网页的基础。许多常见的性能问题,例如页面加载缓慢、白屏时间过长、滚动卡顿、动画掉帧、交互响应迟缓,其根源都可以在渲染流水线的某个环节中找到。

本文介绍现代浏览器的整体架构、渲染流水线的各个阶段、JavaScript 与渲染的关系、事件循环与帧的调度、合成与图层,以及常见的性能问题和优化方法。

一、浏览器的整体架构

多进程架构

现代浏览器普遍采用多进程架构,将不同的职责分配给不同的进程:

  • 浏览器进程:负责浏览器的界面,例如地址栏、标签页、书签,以及协调其他进程、管理存储和网络请求的权限;
  • 渲染进程:负责解析和渲染网页内容、执行 JavaScript。通常每个标签页,或者每个站点,对应一个独立的渲染进程;
  • GPU 进程:负责与图形硬件通信,执行实际的图形绘制和合成;
  • 网络进程:负责处理网络请求;
  • 其他辅助进程:例如处理音频、视频解码、插件等。

多进程架构带来了几方面的好处:

  • 稳定性:一个标签页中的网页崩溃,只会影响它自己的渲染进程,不会导致整个浏览器崩溃;
  • 安全性:渲染进程运行在一个权限受到严格限制的沙箱中,无法直接访问文件系统、网络或其他系统资源,必须通过浏览器进程代为执行。即使网页中的恶意代码利用了渲染引擎的漏洞,也很难突破沙箱危害整个系统。将不同站点隔离在不同的进程中,还能防止一个恶意站点通过侧信道攻击窃取另一个站点的数据;
  • 性能:多个进程可以利用多核处理器并行工作。

本文主要关注的是渲染进程内部的工作。

渲染进程中的线程

在渲染进程中,有几个重要的线程:

  • 主线程:负责解析 HTML、计算样式、布局、绘制指令的生成,以及执行 JavaScript。网页中的绝大部分工作都在主线程上进行;
  • 合成线程:负责将页面的各个图层组合成最终的画面,处理滚动等不需要主线程参与的操作;
  • 光栅化线程:负责将绘制指令转换为实际的像素;
  • 工作线程:网页可以创建的后台线程,用于执行不影响页面渲染的 JavaScript 任务。

主线程承担了如此多的职责,这一点是理解浏览器性能问题的关键:任何在主线程上长时间运行的任务,都会阻塞其他所有工作,包括响应用户输入和更新画面。

二、渲染流水线概览

浏览器将网页内容转换为屏幕上的像素,需要经过一系列阶段,这一过程通常被称为渲染流水线(Rendering Pipeline):

  1. 解析:将 HTML 解析为文档对象模型树,将 CSS 解析为样式规则;
  2. 样式计算:确定每个元素最终应用的样式;
  3. 布局:计算每个元素的大小和在页面中的位置;
  4. 分层:将页面划分为多个图层;
  5. 绘制:为每个图层生成绘制指令列表;
  6. 光栅化:将绘制指令转换为位图,即实际的像素;
  7. 合成:将各个图层的位图按照正确的顺序和位置组合在一起,显示到屏幕上。

下面分别介绍各个阶段。

三、解析

HTML 解析与文档对象模型

浏览器从网络接收到的 HTML 是一串字节。解析的过程包括:

  1. 解码:根据指定的字符编码,将字节转换为字符;
  2. 词法分析:将字符流切分为一个个记号,例如开始标签、结束标签、属性、文本;
  3. 构建树:根据记号,创建对应的节点对象,并按照它们的嵌套关系组织成一棵树,这就是文档对象模型(Document Object Model,DOM)。

DOM 既是浏览器内部表示页面结构的数据结构,也是 JavaScript 用来访问和修改页面内容的接口。

HTML 的解析有一个特点:它具有很强的容错性。即使 HTML 中存在标签未闭合、嵌套错误等问题,解析器也会按照标准中详细规定的规则进行修正,构建出一棵合理的树,而不会像许多编程语言的解析器那样直接报错。这种容错性让网页更加健壮,但也使得 HTML 的解析规则非常复杂。

HTML 的解析是增量进行的:浏览器不需要等待整个 HTML 文档下载完成,而是一边接收,一边解析,一边构建 DOM。这使得页面可以尽早开始渲染。

脚本对解析的阻塞

当 HTML 解析器遇到一个脚本标签时,默认情况下会暂停解析:

  • 如果是外部脚本,需要先下载脚本文件;
  • 然后执行脚本;
  • 执行完成后,才继续解析后面的 HTML。

之所以这样设计,是因为脚本可能会修改文档的内容,例如在当前位置写入新的 HTML。解析器必须等待脚本执行完毕,才能确定后续应该如何解析。

这意味着,位于页面头部的同步脚本,会阻塞整个页面的解析和渲染,直到它被下载和执行完成。如果脚本文件很大或者网络很慢,用户会长时间看到一个空白的页面。

为了避免这一问题,可以为脚本标签添加特定的属性:

  • 延迟执行:脚本在后台下载,不阻塞 HTML 解析,在整个文档解析完成后、文档就绪事件触发之前,按照它们在文档中出现的顺序依次执行;
  • 异步执行:脚本在后台下载,不阻塞 HTML 解析,下载完成后立即执行,执行时会暂停解析。多个异步脚本之间的执行顺序不确定,取决于谁先下载完成;
  • 模块脚本:默认具有延迟执行的行为。

一般的建议是:对于不需要在页面解析过程中执行的脚本,使用延迟或异步的方式加载;或者将脚本放在文档的末尾。

预加载扫描器

为了缓解脚本阻塞带来的影响,浏览器有一个预加载扫描器(Preload Scanner)。当主解析器因为脚本而暂停时,预加载扫描器会继续向前扫描剩余的 HTML,找出其中引用的外部资源,例如脚本、样式表、图片、字体,并提前发起下载请求。这样,当主解析器恢复工作时,这些资源可能已经下载完成了。

预加载扫描器只能发现直接写在 HTML 标记中的资源。如果资源是由 JavaScript 动态插入的,或者是在 CSS 中引用的,预加载扫描器就无法提前发现,这些资源的加载会被推迟。

CSS 解析

CSS 同样需要被解析,浏览器将其转换为一组样式规则的内部表示,有时被称为 CSS 对象模型(CSSOM)。

与 HTML 不同,CSS 不能被增量地应用:因为后面的规则可能会覆盖前面的规则,在样式表被完全下载和解析之前,浏览器无法确定元素的最终样式。因此,CSS 是渲染阻塞资源:在页面所需的样式表加载完成之前,浏览器不会渲染页面内容,以避免先显示无样式的内容,然后突然变化,造成闪烁。

此外,CSS 还会阻塞脚本的执行:如果一个脚本位于样式表之后,浏览器会等待样式表加载和解析完成后,再执行这个脚本。因为脚本可能会查询元素的样式信息,必须确保此时的样式是正确的。

因此,样式表的加载速度对页面的首次渲染时间有很大影响。常见的优化方法包括:将首屏渲染所需的关键样式内联到 HTML 中,其余样式异步加载;减小样式表的体积;通过媒体查询,让只在特定条件下才需要的样式表不阻塞渲染。

四、样式计算

DOM 树和样式规则准备好之后,浏览器需要为每个元素确定其最终的样式,这一过程称为样式计算(Style Calculation),也称样式解析或样式重算。

选择器匹配

对于每个元素,浏览器需要找出所有能够匹配它的 CSS 规则。一个有趣的细节是:浏览器在匹配选择器时,通常是从右向左进行的。

例如,对于一个由多级后代选择器组成的规则,浏览器首先检查元素本身是否匹配最右边的部分,如果不匹配就立即排除;如果匹配,再向上检查它的祖先元素是否满足左边的条件。由于页面中大部分元素都无法匹配最右边的部分,这种方式能够快速排除绝大多数不相关的规则。

浏览器还会使用多种优化手段加速匹配,例如根据规则最右边部分的标识、类名、标签名,将规则预先分组,只检查可能相关的规则;以及在具有相同特征的兄弟元素之间共享样式计算的结果。

层叠与继承

一个元素往往会匹配多条规则,这些规则可能为同一个属性设置了不同的值。浏览器按照层叠(Cascade)规则决定最终使用哪个值,依次考虑:

  • 来源与重要性:浏览器默认样式、用户样式、作者样式,以及是否被标记为重要;
  • 层叠层:作者可以将样式组织到不同的层中,后声明的层优先级更高;
  • 特异性:选择器越具体,优先级越高。例如,标识选择器比类选择器优先,类选择器比标签选择器优先;
  • 出现顺序:在其他条件相同时,后出现的规则覆盖先出现的规则。

对于某些属性,如果元素本身没有指定值,会从父元素继承,例如字体和文字颜色;而另一些属性则不继承,使用其初始值,例如边框和外边距。

计算值

样式计算的结果,是每个元素的每个 CSS 属性的计算值。在这一过程中,许多相对的值会被转换为绝对的值,例如将相对于字体大小的长度单位转换为像素,将颜色关键字转换为具体的颜色值。

五、布局

布局树

知道了每个元素的样式后,浏览器还需要确定每个元素在页面上的几何信息:它的大小和位置。这一过程称为布局(Layout),在一些浏览器中也称为重排(Reflow)。

布局并不是直接在 DOM 树上进行的。浏览器会根据 DOM 树和计算后的样式,构建一棵布局树:

  • 不可见的元素不会出现在布局树中。例如,设置了不显示的元素,以及文档头部中的元素;
  • 一些在 DOM 中不存在的内容会出现在布局树中,例如通过伪元素生成的内容;
  • 一个 DOM 元素可能对应多个布局对象,例如一段跨越多行的文本。

布局的计算

布局的计算基于 CSS 定义的各种布局模型:

  • 常规流:块级元素从上到下依次排列,每个占据一整行;行内元素在一行中从左到右排列,一行放不下时自动换行;
  • 弹性布局:在一个方向上排列子元素,并按照规则分配剩余空间或收缩超出的部分;
  • 网格布局:在二维的网格中放置子元素;
  • 定位:将元素从常规流中脱离出来,相对于某个参照物进行定位;
  • 浮动、多列布局、表格布局等。

布局的计算通常从根元素开始,递归地向下进行。一般来说,父元素的宽度决定了子元素可用的宽度,而子元素的高度又决定了父元素的高度,因此布局需要在自顶向下和自底向上两个方向上传递信息。

文本的布局尤其复杂:需要根据字体的度量信息计算每个字符的宽度,在合适的位置断行,处理双向文本、复杂文字的字形组合、连字等问题。

布局的代价

布局是一个相对昂贵的操作。并且由于元素之间在几何上相互影响,一个元素的变化可能会影响到许多其他元素的布局。例如,修改一个元素的宽度,可能改变其中文本的换行,从而改变它的高度,进而影响其后所有兄弟元素的位置,以及父元素的高度。

浏览器会尽量减少需要重新计算的范围:当某个元素发生变化时,只将它和受影响的祖先元素标记为需要重新布局,下一次布局时只重新计算这些"脏"的部分,这称为增量布局。

但在某些情况下,一个小的变化仍然可能导致大范围甚至整个页面的重新布局。在复杂的页面中,布局可能需要几十甚至上百毫秒。

为了限制布局变化的影响范围,CSS 提供了一个包含属性,开发者可以声明某个元素的内部布局不会影响外部,或者其大小不依赖于内容。浏览器可以据此将布局的计算限制在该元素内部,避免向上传播。

六、分层与绘制

图层

页面中的内容并不是全部绘制在一个平面上的。浏览器会将页面划分为多个图层(Layer),每个图层独立地进行绘制和光栅化,最后再将它们合成在一起。

某些元素会被提升为单独的图层,常见的情况包括:

  • 使用了三维变换或透视的元素;
  • 正在进行透明度或变换动画的元素;
  • 设置了提示浏览器即将发生变化的属性的元素;
  • 视频、画布等特殊元素;
  • 固定定位的元素;
  • 需要独立滚动的区域;
  • 与已有的图层重叠,并且需要绘制在其上方的元素。

将元素放在单独的图层中,其好处在于:当这个元素的某些属性发生变化时,例如移动位置或改变透明度,只需重新合成,而不需要重新绘制其他图层的内容。

但图层并非越多越好。每个图层都需要占用额外的内存来存储其光栅化后的位图,图层过多会导致内存占用大幅增加,并且管理和合成大量的图层本身也有开销。

层叠上下文与绘制顺序

元素的绘制顺序由 CSS 的层叠上下文(Stacking Context)规则决定。层叠上下文是一个三维的概念,决定了元素在垂直于屏幕的方向上的前后顺序。

在一个层叠上下文中,元素大致按照以下顺序绘制:背景和边框、层叠顺序为负的子元素、常规流中的块级元素、浮动元素、行内元素、层叠顺序为零或自动的定位元素、层叠顺序为正的子元素。

许多属性会创建新的层叠上下文,例如设置了层叠顺序的定位元素、透明度小于一的元素、使用了变换或滤镜的元素。理解层叠上下文,有助于解释为什么有时给一个元素设置很高的层叠顺序值,它仍然被另一个元素遮挡。

绘制

绘制(Paint)阶段并不直接产生像素。浏览器为每个图层生成一个绘制记录列表,记录下需要执行的绘制操作,例如"在某个位置绘制一个某种颜色的矩形""在某个位置使用某种字体绘制一段文本""在某个位置绘制一张图片"。

绘制同样支持增量更新:当页面的某部分发生变化时,只需为受影响的区域重新生成绘制指令,这些区域称为失效区域。

七、光栅化与合成

光栅化

光栅化(Rasterization)是将绘制指令转换为实际像素位图的过程。

为了提高效率,浏览器会将每个图层划分为许多较小的图块(Tile),例如 256 像素或 512 像素见方。光栅化以图块为单位进行,并且会优先处理视口内及其附近的图块,用户看不到的区域可以推迟光栅化,甚至完全不进行。

现代浏览器通常使用 GPU 光栅化:将绘制指令发送给 GPU 进程,由图形处理器执行实际的像素绘制。对于大多数绘制操作,GPU 的并行处理能力使其比 CPU 快得多。

合成

合成(Compositing)是将各个图层的位图,按照正确的顺序、位置、变换和透明度组合在一起,生成最终画面的过程。

合成的一个关键特点是:它可以在合成线程上进行,不需要主线程的参与。合成线程掌握着各个图层的位置、变换、透明度等信息,以及已经光栅化好的图块。当这些合成属性发生变化时,合成线程可以直接计算出新的画面,交由 GPU 进程显示到屏幕上。

这意味着,即使主线程正在忙于执行 JavaScript 或进行布局,只要所需的变化只涉及合成属性,画面仍然可以流畅地更新。

合成线程处理滚动

滚动是合成线程最重要的应用之一。在大多数情况下,页面的滚动只需要改变滚动图层的位置,并显示原本在视口之外、已经被光栅化好的图块。这些操作都可以在合成线程上完成,不需要主线程参与,因此即使主线程很繁忙,页面也能流畅地滚动。

但有一个例外:如果页面为滚动或触摸事件注册了监听器,并且这些监听器有可能阻止默认的滚动行为,合成线程就必须等待主线程执行完这些监听器,确认它们没有阻止滚动之后,才能进行滚动。如果主线程此时正忙,滚动就会出现明显的延迟。

为了避免这一问题,可以在注册事件监听器时,声明这个监听器是被动的,即承诺它不会阻止默认行为。这样合成线程就无需等待,可以立即进行滚动。

八、JavaScript 与渲染

事件循环

浏览器的主线程运行着一个事件循环(Event Loop)。事件循环不断地从任务队列中取出任务执行。任务的来源包括:

  • 脚本的执行;
  • 用户的交互事件,例如点击、键盘输入;
  • 定时器回调;
  • 网络请求完成后的回调;
  • 消息传递等。

除了普通的任务,还有微任务,例如承诺对象的回调。在每个任务执行完毕后,事件循环会立即执行微任务队列中的所有微任务,直到队列清空,然后才进入下一个阶段。

渲染时机

渲染并不会在每个任务之后都进行。浏览器会在适当的时机执行一次渲染更新,这一时机通常与显示器的刷新频率同步。对于每秒刷新 60 次的显示器,大约每 16.7 毫秒更新一次画面;对于更高刷新率的显示器,间隔更短。

一次渲染更新大致包括以下步骤:

  1. 处理在这一帧中积累的输入事件;
  2. 执行通过请求动画帧接口注册的回调;
  3. 进行样式计算;
  4. 进行布局;
  5. 进行绘制;
  6. 将结果交给合成线程。

请求动画帧是执行与视觉更新相关的 JavaScript 的最佳时机:它保证回调在下一次渲染之前执行,并且与显示器的刷新同步。相比之下,使用定时器来驱动动画,回调的执行时机与画面的刷新不同步,可能在一帧中执行多次,或者在某些帧中没有执行,导致动画不够平滑。

帧预算

要实现流畅的动画和滚动,浏览器需要在每一帧的时间预算内完成所有工作。以每秒 60 帧计算,每帧只有大约 16.7 毫秒,扣除浏览器自身的开销,留给网页的时间大约只有 10 毫秒左右。

如果某一帧中的工作没有在预算内完成,这一帧的画面更新就会被推迟,用户会感觉到画面的停顿,这称为掉帧。

长任务

长任务是指在主线程上运行时间超过 50 毫秒的任务。在长任务执行期间,主线程无法处理其他任何工作:用户的点击、输入得不到响应,画面无法更新,页面会显得卡顿或无响应。

长任务的常见来源包括:

  • 执行大量的 JavaScript 代码,例如框架的初始化、复杂的数据处理;
  • 大范围的样式计算和布局;
  • 一次性处理大量的 DOM 修改。

应对长任务的主要方法是拆分任务:将一个耗时的任务拆分成多个较小的片段,每个片段执行完毕后,主动将控制权交还给事件循环,让浏览器有机会处理用户输入和渲染画面,然后再继续执行下一个片段。浏览器也提供了专门的接口,用于在适当的时机让出主线程,并根据任务的优先级进行调度。

对于不需要访问 DOM 的计算密集型任务,例如数据处理、图像处理、复杂的计算,可以将其移到工作线程中执行,完全避免阻塞主线程。

强制同步布局

浏览器通常会将布局推迟到渲染更新阶段进行,在一帧中多次修改 DOM 和样式,只需要在最后进行一次布局。

但如果 JavaScript 在修改了样式之后,立即读取某些需要布局信息才能确定的属性,例如元素的宽度、高度、偏移位置、滚动位置,浏览器为了返回正确的值,就不得不立即同步地进行样式计算和布局。这称为强制同步布局。

更糟糕的情况是在循环中交替地进行读取和写入:

对于每个元素:
    读取某个元素的宽度    → 触发布局
    修改这个元素的宽度    → 布局失效

每次循环都会触发一次完整的布局,这称为布局抖动(Layout Thrashing),可能导致性能急剧下降。

避免的方法是将读取和写入分开:先批量读取所有需要的布局信息,然后再批量进行所有的修改。这样在整个过程中只需进行一次布局。

九、渲染的不同路径

当页面发生变化时,根据变化的性质不同,需要重新执行渲染流水线中的不同阶段,代价差别很大。

修改影响几何的属性:例如宽度、高度、外边距、内边距、位置、字体大小、文本内容。这需要重新进行样式计算、布局、绘制和合成,代价最高。

修改只影响外观的属性:例如颜色、背景颜色、阴影、边框的颜色。元素的几何信息没有改变,可以跳过布局,只需重新进行样式计算、绘制和合成。

修改合成属性:主要是变换和透明度。如果相关的元素已经在独立的图层中,浏览器可以跳过布局和绘制,只需重新进行合成。并且这一过程可以完全在合成线程上进行,不受主线程繁忙程度的影响。

因此,实现动画的最佳实践是:尽可能只对变换和透明度进行动画。例如,要移动一个元素,使用平移变换,而不是修改它的位置属性;要缩放一个元素,使用缩放变换,而不是修改它的宽度和高度。这样的动画不仅代价低,而且即使主线程正在执行其他任务,动画依然能够保持流畅。

对于使用 CSS 声明的动画和过渡,如果只涉及变换和透明度,浏览器可以将其完全交给合成线程处理,主线程在动画开始后几乎不需要参与。

十、加载性能

关键渲染路径

关键渲染路径(Critical Rendering Path)是指浏览器从接收到 HTML 到首次在屏幕上渲染出内容所需经历的步骤。优化关键渲染路径的目标,是让用户尽早看到有意义的内容。

影响关键渲染路径的主要因素包括:

  • 关键资源的数量:阻塞首次渲染的资源,主要是同步脚本和样式表;
  • 关键资源的大小:需要下载的字节数;
  • 关键路径的长度:资源之间的依赖关系导致的网络往返次数。例如,HTML 引用了一个样式表,样式表又通过导入语句引用了另一个样式表,就需要多次串行的网络请求。

优化加载性能的方法

减少阻塞渲染的资源。 将非关键的脚本设置为延迟或异步加载;将首屏所需的关键样式内联,其余样式异步加载;移除未使用的样式和脚本。

减小资源体积。 压缩和精简代码;对文本资源使用压缩传输;按需拆分 JavaScript 代码,只加载当前页面需要的部分;使用现代的图片格式,并根据设备的屏幕尺寸和像素密度提供合适大小的图片。

尽早发现和加载关键资源。 对于预加载扫描器无法发现的关键资源,例如在 CSS 中引用的字体、由脚本加载的首屏图片,可以使用资源提示,告诉浏览器提前加载它们;对于将要访问的其他域名,可以提前进行域名解析和连接建立。

合理设置资源的优先级。 浏览器会为不同类型的资源分配不同的加载优先级。对于首屏中最重要的图片,可以提高其优先级;对于视口之外的图片和内嵌框架,使用延迟加载,在它们即将进入视口时才开始加载。

优化字体加载。 网页字体在下载完成之前,浏览器可能会隐藏使用该字体的文本,导致用户长时间看不到文字。可以通过设置字体显示策略,让浏览器先使用后备字体显示文本,在网页字体加载完成后再替换。对于中文等字符集很大的字体,可以对字体进行子集化,只包含页面实际使用的字符,大幅减小字体文件的体积。

利用缓存。 为静态资源设置合理的缓存策略,使回访的用户无需重新下载未变化的资源。对于内容不变的资源,可以在文件名中包含内容的哈希值,并设置很长的缓存时间。

服务端渲染与客户端渲染

现代网页应用的渲染方式,大致可以分为几类:

  • 客户端渲染:服务器只返回一个几乎为空的 HTML 骨架和 JavaScript 代码,由浏览器下载并执行 JavaScript 后,在客户端生成页面内容。其问题在于首次渲染需要等待大量 JavaScript 的下载和执行,白屏时间较长,并且对搜索引擎不够友好;
  • 服务端渲染:服务器直接生成完整的 HTML 返回给浏览器,浏览器可以立即显示内容。之后,客户端的 JavaScript 再接管页面,为其绑定事件处理,使其具备交互能力,这一过程称为水合(Hydration)。服务端渲染缩短了首次显示内容的时间,但在水合完成之前,页面虽然看起来已经就绪,却无法响应用户的交互;
  • 静态生成:在构建阶段预先生成所有页面的 HTML,直接以静态文件的形式提供服务,适合内容不经常变化的页面;
  • 渐进式和部分水合:只对页面中需要交互的部分进行水合,或者按照优先级逐步水合,减少需要在客户端执行的 JavaScript 量。另一类思路是尽量让大部分组件只在服务端渲染,只将真正需要交互的部分发送到客户端。

选择哪种方式,需要在首次显示速度、交互就绪时间、服务器负载和开发复杂度之间进行权衡。

十一、性能指标

为了衡量网页的用户体验,业界定义了一系列以用户为中心的性能指标。其中,以下几个被认为是最核心的:

最大内容绘制(Largest Contentful Paint,LCP):视口中最大的图片或文本块完成渲染的时间。它近似地反映了用户感知到页面主要内容加载完成的时刻。

交互到下一次绘制(Interaction to Next Paint,INP):衡量页面对用户交互的响应速度。它观察用户在页面整个生命周期中的所有点击、触摸和键盘交互,记录从交互发生到下一帧画面呈现的延迟,取其中较差的值。这个延迟包括三部分:输入延迟,即交互发生时主线程正在忙于其他任务而无法立即处理的等待时间;事件处理时间,即执行事件监听器的时间;呈现延迟,即事件处理完成后,进行样式计算、布局、绘制直到画面更新的时间。

累积布局偏移(Cumulative Layout Shift,CLS):衡量页面在加载过程中的视觉稳定性。当页面中已经显示的元素因为其他内容的加载而突然移动位置时,例如图片加载后将下方的文字向下推,或者广告突然插入,就会产生布局偏移。这不仅令人困扰,还可能导致用户点击到错误的位置。

改善这些指标的常见方法包括:

  • 改善最大内容绘制:优化服务器响应时间,减少阻塞渲染的资源,优先加载首屏的主要图片,避免将首屏图片设置为延迟加载;
  • 改善交互响应:拆分长任务,减少主线程上的 JavaScript 执行时间,避免在事件处理中进行昂贵的同步操作,将非紧急的工作推迟到画面更新之后进行;
  • 改善布局稳定性:为图片和视频等元素预先指定尺寸或宽高比,为动态加载的内容预留空间,避免在已有内容的上方插入新内容,使用不会引起布局变化的方式实现动画。

十二、调试与分析

浏览器的开发者工具提供了强大的性能分析能力:

  • 性能面板:录制一段时间内页面的运行情况,以时间轴的形式展示主线程上执行的每一项任务,包括脚本执行、样式计算、布局、绘制,以及每一帧的时间。可以从中找出长任务、强制同步布局、频繁的重新布局等问题;
  • 渲染工具:可以高亮显示页面中被重新绘制的区域、显示布局偏移的位置、显示图层的边界、实时显示帧率,帮助直观地发现不必要的重绘和布局偏移;
  • 图层面板:以三维的方式展示页面的图层结构,以及每个图层被创建的原因和占用的内存;
  • 网络面板:展示所有资源的加载顺序、时间和优先级,帮助分析关键渲染路径;
  • 自动化审计工具:对页面进行全面的检查,给出性能、可访问性、最佳实践等方面的评分和改进建议。

需要注意的是,开发者通常使用高性能的电脑和快速的网络,而许多真实用户使用的是性能较弱的手机和不稳定的移动网络。在开发者工具中模拟较慢的处理器和网络,以及收集真实用户的性能数据,对于了解实际的用户体验非常重要。

结语

浏览器将一份 HTML 文档变成屏幕上可交互的画面,需要经过一条精心设计的流水线:解析构建出文档的结构,样式计算确定每个元素的外观,布局确定每个元素的几何位置,绘制生成绘制指令,光栅化将指令转换为像素,合成将多个图层组合成最终的画面。

在这条流水线中,主线程承担了大部分的工作,它是网页性能的关键瓶颈。理解了这一点,许多性能优化的原则就变得自然而然:减少阻塞渲染的资源,让内容尽早显示;避免长任务,让主线程能够及时响应用户;分离布局信息的读取和写入,避免强制同步布局;使用变换和透明度实现动画,让工作交给合成线程;为内容预留空间,保持页面的视觉稳定。

浏览器的渲染引擎在不断演进,许多过去需要开发者手动处理的优化,如今已经由浏览器自动完成。但理解渲染的基本原理,仍然是诊断和解决网页性能问题的基础。当页面出现卡顿或缓慢时,能够判断问题出在流水线的哪一个环节,往往就已经找到了一半的答案。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。