深入探索浏览器如何将代码转化为视觉盛宴,揭示高效渲染背后的技术原理
网络线程到主线程
当你在浏览器地址栏输入URL并按下回车时,浏览器网络线程首先接收到HTML文档。这个文档只是一串字符,浏览器需要将其转化为用户可见的像素。网络线程会产生一个渲染任务,并将其传递给渲染主线程的消息队列。
渲染起点 -> 网络线程接收HTML文档 -> 创建渲染任务 -> 加入渲染主线程队列
CSS加载为何不阻塞HTML解析?
我们在写html文档时,会将css放入body上面部分,以style或link等方式接入css,不只是为了快速渲染页面样式。
在HTML解析过程中遇到CSS资源时,浏览器会启动一个预解析线程专门处理CSS,生成CSSOM树(CSS对象模型)。这个预解析线程与主渲染线程并行工作,互不干扰。所以早放早运行。

JavaScript阻塞渲染,为什么不也用一个线程?
JavaScript需要直接操作DOM,而DOM树构建是渲染主线程的核心任务。如果JavaScript在单独的线程中执行,会导致复杂的线程同步问题,因此浏览器设计为在渲染主线程中顺序执行JavaScript。简单来说就是没有那个必要。
HTML解析开始 -> 遇到JavaScript -> 暂停HTML解析 -> 下载并执行JS -> 恢复HTML解析
行盒与块盒的奥秘
在CSS中,盒子的类型由display
属性决定,而不是HTML元素类型。W3C规定,内容必须包含在行盒中<p>a</p>
这在块盒中影响规定,浏览器会自动生成匿名行盒包裹 a。同时,行盒和块盒不能相邻,当行盒和块盒相邻时,浏览器会自动生成匿名盒。
css
<div>
<p>a</p>
b
<p>c</p>
</div>

DOM树与布局树的差异
DOM树表示文档结构,而布局树(渲染树)只包含可见元素及其样式信息。两者并非一一对应:
- display:none节点没有几何信息,所以不会生成到布局树,但是会生成到dom树
- 伪元素有几何信息,会生成到布局树,但是dom树不存在伪元素节点(css中写的)
不知道伪元素是什么可以看看我之前的文章CSS 伪元素
分层与合成(渲染加速)
为了提高渲染效率,浏览器会将页面分成多个层,独立处理:
主线程会使用自己的策略对布局树分层
- 为了在某个层改变仅对
修改层
处理,提升效率 - 滚动条、堆叠上下文、transform、opacity 等样式都会或多或少影响分层结果
- 通过will-change属性更大程度的影响分层结果
- 但也都是影响,还是看浏览器怎么分
百度首页的分层效果:

渲染流水线
浏览器渲染的最后阶段将分层后的内容转化为屏幕上的像素:

绘制
- 主线程会为每个层单独生成绘制指令集(描述这层怎么画)
- 然后将每个图层的绘制信息交给合成线程
分块
- 合成线程会为每个图层生成分块信息
光栅化
- 合成线程将分块信息给GPU进程,快速完成光栅化
- GPU进程会开启多个线程来完成光栅化,并优先处理靠近视口区的块
- 最终呈现一块一块的位图
画
- 合成线程计算出每个位图在屏幕上的位置
- 交给GPU进行最终呈现
为什么交给GPU,而不是自己
1.渲染进程(在沙盒)独立 安全
2.渲染主线程
- 合成线程(transform也在这,不用参与前面的步骤,效率极高)//滚动也是
- 渲染进程和操作系统的硬件是隔离的,所以合成线程无法直接交给硬件
transform优势展示
transfrom 直接就走最后像素展示的流程,所以不管你JS怎么折腾(卡死循环)都无济于事。滚动条也是在这里所以卡死循环也不影响页面展示滚动条,大家可以自己试试。

完整代码:
html
<!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>
.ball{
width: 100px;
height: 100px;
background: pink;
border-radius: 50%;
margin: 30px;
}
.ball1{
animation: move1 1s alternate infinite ease-in-out;
}
.ball2{
position: fixed;
left: 0;
animation: move2 1s alternate infinite ease-in-out;
}
@keyframes move1{
to{
transform: translateX(100px);
}
}
@keyframes move2{
to{
left: 100px;
}
}
</style>
</head>
<body>
<button id="btn">卡你五秒如何呢</button>
<div class="ball ball1"></div>
<div class="ball ball2"></div>
<script>
function delay(time){
var start = Date.now();
while(Date.now() - start < time){}
}
document.getElementById('btn').onclick = function(){
delay(5000);
}
</script>
</body>
</html>
重排(Reflow)与重绘(Repaint)
理解重排和重绘对性能优化至关重要,以下是重排重绘的代码简单示例,大家可以自己玩一下:

html
<!DOCTYPE html>
<html>
<head>
<title>重排与重绘</title>
<style>
.reflow-demo {
width: 300px;
height: 200px;
border: 2px solid #3498db;
padding: 15px;
margin: 20px auto;
position: relative;
}
.box {
width: 100px;
height: 100px;
background: #2ecc71;
transition: all 0.3s;
}
.controls {
text-align: center;
margin: 20px 0;
}
button {
padding: 8px 15px;
margin: 0 5px;
background: #3498db;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.console {
background: #2c3e50;
color: #ecf0f1;
padding: 15px;
font-family: monospace;
height: 100px;
overflow-y: auto;
margin-top: 20px;
}
</style>
</head>
<body>
<div class="reflow-demo">
<div class="box" id="demoBox"></div>
</div>
<div class="controls">
<button onclick="changeSize()">改变尺寸(重排)</button>
<button onclick="changeColor()">改变颜色(重绘)</button>
<button onclick="transformBox()">Transform(合成)</button>
</div>
<div class="console" id="console"></div>
<script>
const box = document.getElementById('demoBox');
const consoleEl = document.getElementById('console');
function log(message) {
consoleEl.innerHTML += `[${new Date().toLocaleTimeString()}] ${message}\n`;
consoleEl.scrollTop = consoleEl.scrollHeight;
}
function changeSize() {
log('开始:改变元素尺寸...');
const start = performance.now();
// 触发重排
box.style.width = box.offsetWidth === 100 ? '150px' : '100px';
// 读取布局信息 - 强制同步重排
const width = box.offsetWidth;
const duration = performance.now() - start;
log(`完成:尺寸改变 (${duration.toFixed(2)}ms)`);
log(`读取宽度: ${width}px (强制同步重排)`);
}
function changeColor() {
log('开始:改变背景颜色...');
const start = performance.now();
// 只触发重绘
box.style.backgroundColor = box.style.backgroundColor === 'rgb(46, 204, 113)' ?
'#9b59b6' : '#2ecc71';
const duration = performance.now() - start;
log(`完成:颜色改变 (${duration.toFixed(2)}ms)`);
}
function transformBox() {
log('开始:应用transform...');
const start = performance.now();
// 使用transform只触发合成
box.style.transform = box.style.transform === 'translateX(50px)' ?
'translateX(0)' : 'translateX(50px)';
const duration = performance.now() - start;
log(`完成:transform应用 (${duration.toFixed(2)}ms)`);
}
</script>
<div class="explanation">
<p><strong>性能关键点:</strong></p>
<ul>
<li>重排(reflow)会重新计算布局 - 代价最高</li>
<li>重绘(repaint)不改变布局,但需要重新绘制</li>
<li>transform和opacity只触发合成(composite) - 最高效</li>
<li>重排是异步的,但读取布局属性会强制同步重排</li>
</ul>
</div>
</body>
</html>
reflow 它是异步的,会等js执行完成一起提交给合成线程
但是在读取的时候是同步的(因为读取的时候如果js将其中元素改变,异步的话,读取就会还是原来元素的状态)
总体流程

总结
浏览器渲染是一个复杂而精密的流水线作业,从接收HTML字符串开始,经历解析、样式计算、布局、分层、绘制、光栅化到最终合成,每一步都经过高度优化。理解这个过程有助于开发者:
- 优化页面性能:减少重排和重绘,优先使用transform和opacity
- 合理组织资源:CSS放头部,JS放底部或使用async/defer
- 避免布局抖动:避免在循环中交替读写布局属性
- 利用分层优势:合理使用will-change提示浏览器优化渲染