# 渲染面板

关于页面渲染方面的性能,我们可以在 Chorme 开发者工具中的渲染面板 Rendering 中观察到。

其中,FPS meter 是我们最需要关注的一个点。FPS 全称叫 Frames Per Second (每秒帧数)。帧数越高,动画显示的越流畅。一般的液晶显示器的刷新频率也就是 60 HZ。也就是说,要想页面上的交互效果和动画流畅,那么 FPS 稳定在 60 左右,是最佳的体验。

关于 Chorme 开发工具各个面板的具体使用可以参考这个:Chrome开发者工具使用指南 (opens new window)

performance

# 渲染性能测试

重排(回流)一定会引起重绘,但重绘不一定会引起重排(回流)。

(1)不同的动画实现方式会导致渲染性能不一样

<div class="container">
    <div class="ball" id="ball"></div>
</div>
<script>
    var balls = document.getElementById('ball');
    balls.classList.add('ball');
    balls.classList.add('ball-running');
</script>
1
2
3
4
5
6
7
8
  • 使用改变手动改变 top 和 left 的方式实现。
.container {
    position: relative;
    min-height: 400px;
}
.ball {
    position: absolute;
    top: 0;
    left: 0;
    width: 100px;
    height: 100px;
    border-radius: 50%;
    box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.75);
}
.ball-running {
    animation: run-around 4s infinite;
}
@keyframes run-around {
    0% {
        top: 0;
        left: 0;
    }
    25% {
        top: 0;
        left: 200px;
    }
    50% {
        top: 200px;
        left: 200px;
    }
    75% {
        top: 200px;
        left: 0;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

可以看到,小球每次在运动的时候,都会导致重绘和重排(绿色高亮部分是重绘,紫色高亮部分是重排),这样会导致页面的渲染性能比较差。

performance

  • 使用 translate 的方式来实现。
.container {
    position: relative;
    min-height: 400px;
}
.ball {
    position: absolute;
    top: 0;
    left: 0;
    width: 100px;
    height: 100px;
    border-radius: 50%;
    box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.75);
}
.ball-running {
    animation: run-around 4s infinite;
}
@keyframes run-around {
    0% {
        transform: translate(0, 0);
    }
    25% {
        transform: translate(200px, 0);
    }
    50% {
        transform: translate(200px, 200px);
    }
    75% {
        transform: translate(0, 200px);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

可以看到,现在小球每次运动时,不会有高亮显示了,说明不会触发重绘和重排,页面渲染性能得到了提升。

performance

# 网页渲染流程

# 浏览器解析 Html ⽂件的过程

浏览器解析 HTML ⽂件的过程是⼀个复杂且精细的操作,通常包括以下⼏个主要步骤:

1. 下载 HTML ⽂件

当你访问⼀个⽹⻚时,浏览器⾸先向服务器发送请求,下载 HTML ⽂件。

2. 解析 HTML 到 DOM

  • 解析标记(Lexical Analysis):浏览器开始解析 HTML ⽂件中的⽂本,并将其转换成有意义的标记(tokens)。这个过程类似于语法分析。

  • 构建 DOM 树:浏览器根据这些标记构建⽂档对象模型(DOM),它是⼀个树形结构,代表了⻚⾯的内容和结构。

3. 处理 CSS 和 JavaScript

  • CSS 解析:浏览器解析与 HTML ⽂件相关联的 CSS ⽂件,并根据这些样式信息创建 CSSOM(CSS 对象模型)。

  • JavaScript 执⾏:如果 HTML ⽂件中包含或引⽤了 JavaScript,浏览器也会解析并执⾏它。JavaScript 可以修改 DOM 和 CSSOM。

4. 构建渲染树

浏览器结合 DOM 和 CSSOM 构建渲染树(Render Tree)。渲染树是⻚⾯中所有可⻅元素的表示,包括它们的位置和样式信息。

5. 布局(Layout/Reflow)

浏览器计算渲染树中每个节点的确切位置和⼤⼩。这个过程也被称为回流(reflow)。

6. 绘制(Painting)

浏览器根据布局信息在屏幕上绘制所有的元素。这包括⽂本颜⾊、图像、边框等。

7. 合成(Compositing)

在某些情况下,⻚⾯的某些部分可能需要单独绘制,然后将它们合并到⼀起。这通常⽤于复杂的动画和视觉效果。

# 从输⼊ URL 到⻚⾯出现的全过程

1. DNS 解析

  • 查找 IP 地址:浏览器需要找到该 URL 的 IP 地址。⾸先在本地缓存中查找,如果未找到,会向 ISP 的 DNS 服务器发起查询。

  • 递归查询:如果 ISP 的 DNS 服务器也没有缓存,则进⾏递归查询,直到找到域名的授权 DNS 服务器,并获取到⽹站的 IP 地址。

2. 建⽴ TCP 连接

  • 三次握⼿:浏览器与服务器之间建⽴ TCP 连接,这通常涉及⼀个称为 “三次握⼿” 的过程,确保双⽅都准备好进⾏数据传输。

3. TLS 握⼿(如果是 HTTPS)

  • 建⽴安全连接:如果 URL 是 HTTPS,浏览器与服务器之间会进⾏ TLS 握⼿,以建⽴加密的数据传输通道。

4. 发送 HTTP 请求

  • 构建请求:浏览器构建⼀个 HTTP 请求,并通过建⽴的 TCP 连接发送给服务器。这个请求包括 URL、请求⽅法(如 GET)、头信息等。

5. 服务器处理请求

  • 接收和处理:服务器接收到请求后,会根据请求的 URL 处理请求,可能涉及查询数据库、运⾏后端代码等操作。

6. 服务器发送响应

  • 发送数据:服务器向浏览器发送⼀个 HTTP 响应,包括状态码(如 200 OK)、响应头信息、以及请求的⽂档(通常是 HTML)。

7. 浏览器解析 HTML

  • 构建 DOM 树:浏览器开始解析 HTML ⽂档,构建⽂档对象模型(DOM)。

  • CSS 解析:同时解析 CSS,构建 CSS 对象模型(CSSOM)。

  • JavaScript 处理:如果 HTML 引⽤了 JavaScript ⽂件,浏览器会下载并执⾏它们,这可能会修改 DOM 和 CSSOM。

8. 渲染⻚⾯

  • ⽣成渲染树:浏览器结合 DOM 和 CSSOM ⽣成渲染树。

  • 布局:计算出渲染树中每个节点的位置和⼤⼩。

  • 绘制:根据布局信息在屏幕上绘制出⻚⾯内容。

9. 加载额外资源

  • ⻚⾯中可能包含图⽚、CSS ⽂件、JavaScript ⽂件等额外资源,浏览器会发出额外的 HTTP 请求来加载这些资源。

10. ⻚⾯渲染完成

  • 当所有资源都被加载和渲染后,⻚⾯加载过程完成。

在整个过程中,浏览器和服务器之间可能会有多次往返通信。此外,现代⽹⻚可能包含⼤量的资源和复杂的脚本,这些都可能影响加载时间。每⼀步的性能和效率对于最终⽤户体验都⾄关重要。

# 浏览器的渲染流程

浏览器的渲染流程是⼀个包含多个步骤的复杂过程,主要涉及以下环节:

1. 解析 HTML 构建 DOM 树

浏览器解析 HTML ⽂件,创建 DOM 树。

2. 解析 CSS 构建 CSSOM 树

解析 CSS ⽂件和样式,构建 CSS 对象模型(CSSOM)树。

3. 合并 DOM 和 CSSOM 构建渲染树

将 DOM 和 CSSOM 合并,创建渲染树,这⼀步确定每个节点的样式。

4. 布局(Layout)

计算渲染树中每个节点的准确位置和⼤⼩。

5. 分层(Layering)

⼀些元素(如具有复杂效果的元素,使⽤ 3D 变换或 will-change 的元素)会被分到单独的合成层。

6. 绘制(Paint)

在各个层上填充像素,包括颜⾊、图⽚、⽂字等。

7. 合成(Compositing)

分层之后,在屏幕显示之前,浏览器会将所有层合并。这个过程通常由 GPU 加速,特别是当使⽤ CSS3D 变换等技术时。

# 重排和重绘

1. 重排(Reflow)

当 DOM 的变化影响元素的⼏何属性(如宽度、⾼度、位置等),浏览器需要重新计算元素的位置和⼤⼩。这可能导致整个⻚⾯或⻚⾯的⼀部分重新布局。

2. 重绘(Repaint)

当元素的外观被改变,但不影响其布局时(如更改颜⾊、阴影等),浏览器会重新绘制这些元素。

# 什么情况下会导致重绘重排?

  • 颜色、阴影、字体大小等的变化都会导致重绘。

  • 只要盒子动了,就会产生重排。除此之外,在读取一些属性的时候也会触发重排,这些属性有 offset、scroll、client、width 等。因此,平时在写 css 的时候,尽量把读操作放在一起,把写操作放在一起,不要读写读写,这样浏览器会进行优化。

  • requestAnimationFrame (opens new window) 是操作 DOM 元素的一种高效的方法。我们可以使用它设置读写分离。

requestAnimationFrame(function() {
    // 设置读写分离
})
1
2
3

# 如何避免重排和重绘

1. 最⼩化DOM操作

使⽤ DocumentFragment (opens new window) 或隐藏元素进⾏批量修改。

2. 批量修改样式

批量读取 CSS 样式让浏览器进⾏合并,然后使⽤ rerequestIdleCallback (opens new window) 批量修改,即读取和修改分离。

3. 使⽤ transform 和 opacity 实现动画

这些属性不会触发重排和重绘。

4. 避免触发同步布局事件

避免频繁读取会触发重排的属性。

5. 使⽤绝对定位

对于频繁重排的元素,使⽤绝对定位。

6. 优化 CSS 选择器

优化 CSS 选择器,避免过度复杂的选择器。浏览器解析 CSS 选择器是从右向左的,因此选择器的最右边是关键。

7. 避免使⽤表格布局

因为它可能导致更多的重排。

8. 使⽤ CSS 3D Transform

触发硬件加速,将元素提升到单独的合成层。

9. 利⽤ will-change 属性

will-change (opens new window) 可以提前通知浏览器元素可能的变化,以便优化。

# DOM 分层

DOM 是分层的。那么是如何分层的呢?

(1)浏览器获取到 DOM 元素。

(2)对 DOM 元素节点计算样式结果(Recalculate Style 样式重计算)。

(3)为每个节点生成图形位置(Layout 重排)。

(4)将每个节点绘制填充到图层位图中(Paint 重绘)。

(5)将图层作为纹理上传到 GPU 上去。

(6)GPU 把符合的图层合成生成到页面上(Composite Layers 合成层)。

performance

performance

以上就是整个网页的渲染流程,其中最重要三步就是:Layout -> Paint -> Composite Layers。

可以在 Chorme 开发者工具的性能面板中查看具体的过程和耗时。

performance

# 合成层做了什么?

(1)图层列表准备好之后,提交给主线程(单独的合成线程)。

(2)合成线程根据当前视口 viewport 来划分图块。

(3)将分成的图块生成位图,这个过程就叫光栅化栅格化(Raster)。

(4)所有图块都栅格化之后,合成线程就会生成 DrawQuad,提交给浏览器的渲染进程。

(5)浏览器有一个 viz 组件,接收到 DrawQuad 之后,把内容绘制到屏幕上。

# 哪些元素会分层?

  • 会分层的元素有:根元素,设置了 position、transform、半透明、css 滤镜、overflow 的元素,canvas 元素,video 元素

  • CSS3D、transform、video、WebGL、css 滤镜、will-change: transform (opens new window) 能够跳过重绘重排,让 GPU 直接参与进来,触发硬件加速。

# CPU 和 GPU 的区别

  • CPU 需要很强的通用性来处理各种不同的数据类型,同时又要逻辑判断又会引入大量的分支跳转和中断的处理。因此它不仅被 Cache 占据了大量空间,而且还有有复杂的控制逻辑和诸多优化电路,相比之下计算能力只是 CPU 很小的一部分。

  • GPU 面对的则是类型高度统一的、相互无依赖的大规模数据和不需要被打断的纯净的计算环境。它采用了数量众多的计算单元和超长的流水线,但只有非常简单的控制逻辑并省去了 Cache。

  • 适合在 GPU 上运行的程序

    • 计算密集型的程序。所谓计算密集型(Compute-intensive)的程序,就是其大部分运行时间花在了寄存器运算上,寄存器的速度和处理器的速度相当,从寄存器读写数据几乎没有延时。可以做一下对比,读内存的延迟大概是几百个时钟周期;读硬盘的速度就不说了,即便是 SSD, 也实在是太慢了。

    • 易于并行的程序。GPU 其实是一种 SIMD(Single Instruction Multiple Data)架构,它有成百上千个核,每一个核在同一时间最好能做同样的事情。

  • 有一个库叫 GPU.js (opens new window),可以用来加速 JavaScript。

  • 关于两者的详细区别可以参考这篇文章:CPU 和 GPU 的区别 (opens new window)

# JS 代码放到底部会阻塞 DOM 渲染吗?

测试代码:

<h1>深圳</h1>
<script>
    prompt('等待');
</script>
1
2
3
4

上面代码在浏览器中的效果如下:

performance

可以看到,即使我们把 js 代码放到了页面底部,页面还是会先弹出等待框,此时页面上也并没有显示出 h1 的内容。只有当我们输入内容点确定之后,页面上才会显示 h1 的内容。

因此,不管我们把 js 代码放到页面顶部还是底部,它都会影响 DOM 渲染。

但是,把 js 放页面底部还是有意义的,因为这样不会影响 DOM 解析,只会影响 DOM 渲染。

# CSS 会影响 DOM 的解析和渲染吗?

测试代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        h1 {
            color: red !important;
        }
    </style>
    <script>
        function h() {
            console.log(document.querySelectorAll('h1'));
        }
        setTimeout(h, 0);
    </script>
    <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/5.0.0-alpha1/css/bootstrap-grid.css">
</head>
<body>
    <h1>深圳</h1>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

然后需要在浏览器的 Network 那里新增一个网络,来模拟网速很慢的情况,才看得出加载效果。

performance

接着在浏览器中访问页面,

如果控制台中有打印出节点信息,说明 css 不会影响 DOM 解析;否则会影响。

如果页面中会马上显示出 h1 的内容,说明 css 不会影响 DOM 渲染;否则会影响。

可以看到效果如下,控制台输出节点信息了,但是页面上要等很久才会显示 h1 的内容。

performance

因此,css 不会影响 DOM 解析,但是会影响 DOM 渲染。

# CSS 会影响 JS 的解析吗?

测试代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        h1 {
            color: red !important;
        }
    </style>
    <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/5.0.0-alpha1/css/bootstrap-reboot.css">
</head>
<body>
    <h1>深圳</h1>
    <script>
        console.log(123);
    </script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

还是采用刚刚很慢的网络,可以在浏览器中看到,js 代码等了很久之后才执行。

performance

因此,css 会阻塞 js 代码的解析

# CSS 会影响 DOMContentLoaded 吗?

DOMContentLoaded (opens new window)

测试代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script>
        document.addEventListener('DOMContentLoaded', function() {
            console.log('DOMContentLoaded');
        })
    </script>
    <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/5.0.0-alpha1/css/bootstrap-utilities.css">
</head>
<body>
    <h1>深圳</h1>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

网速很慢的情况下执行结果如下:

performance

说明此时 css 不会影响 DOMContentLoaded。

但是,在以下情况,就会影响了。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script>
        document.addEventListener('DOMContentLoaded', function() {
            console.log('DOMContentLoaded');
        })
    </script>
    <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/5.0.0-alpha1/css/bootstrap-utilities.css">
    <script>
        console.log('下一个 js');
    </script>
</head>
<body>
    <h1>深圳</h1>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

网速很慢的情况下执行结果如下:

performance

可以看到,控制台没有输出内容了。说明此时 css 影响了 DOMContentLoaded。当 css 加载完成之后,才会输出内容。

performance

因此,css 会不会影响 DOMContentLoaded 取决于 css 下边还有没有 js 脚本,如果有,就会影响,没有的话就不会影响

为什么有脚本就会阻塞呢?

这是因为脚本可以操作 DOM,加载 CSS 的时候不知道下面的脚本会不会影响 DOM,所以就要一直等着

上次更新时间: 2024年01月04日 23:01:36