原理篇:浏览器加班现场——重排重绘血泪史!
嘿,小伙伴们!今天咱们继续聊聊浏览器的 “工作日常”。你知道吗?每次你打开一个网页,浏览器可不是简单地 “唰” 一下就搞定了。它背后可是有一大堆的“加班血泪史”呢!今天,我们就来揭秘浏览器的重排(Reflow)和重绘(Repaint)这两个 “加班大户”。
浏览器的 “工作流程”
想象一下,你是一个工厂的工人,每天的任务是把一堆原材料(HTML、CSS、JavaScript)组装成一个精美的产品(网页)。浏览器的工作也差不多,它需要把一堆代码变成你看到的漂亮网页。这个过程可以分为几个步骤:
- 解析 HTML:浏览器先把 HTML 代码解析成一个树状结构,叫做 DOM 树(文档对象模型)。这就像是把产品的一堆零件分类放好,方便后续组装。

- 解析 CSS:然后,浏览器解析 CSS 代码,生成一个样式规则表。这就像是把每个零件的说明书准备好,告诉浏览器每个零件应该怎么装饰。

- 构建渲染树 Render Tree:接下来,浏览器把 DOM 树和样式规则表结合起来,生成一个渲染树。这就像是把零件和说明书结合起来,开始组装产品。

- 布局(Layout):浏览器根据渲染树计算每个元素的位置和大小。这就像是在工厂里,把每个零件放到正确的位置上。
- 绘制(Paint):浏览器把渲染树中的每个节点元素绘制到屏幕上。这就像是把组装好的产品涂上颜色,让它看起来漂亮。
- 分层树(Layer):接下来设计比较复杂页面元素的场景,比如3D变化、页面滚动、z-index在 z 轴排序需要渲染引擎为特定的节点生成专用图层,形成分层树。
- 合成显示(Composite):浏览器完成了分层树的构建后,它会把图层的绘制分成很多个绘制指令组成带绘制列表,然后提交给渲染引擎的合成线程处理,形成合成位图,最终呈现在屏幕上。

重排——浏览器的 “搬砖” 工作
想象一下,你是工厂的工人,已经把一个产品组装好了,突然老板说:“嘿,这个零件要换个位置!” 这是你不得不再次把零件拆下来,重新组装。浏览器也是一样,当页面的内容发生变化时,比如你动态地改变了某个元素的大小或位置,浏览器就需要重新计算位置布局,这个过程就叫做重排(Reflow)。
重排的触发条件
浏览器可不是闲着没事干,只有在渲染树的部分或全部的元素节点的尺寸、结构或几何属性发生改变时,浏览器才会进行重排(也叫回流)。
会导致重排的操作(包括但不限于):
- 浏览器窗口大小发生变化
- 元素尺寸 / 位置发生变化:比如你用 JS 动态地改变了这个元素的
width/height - 元素内容发生变化:比如你改变了某个元素的文本内容,导致他的大小发生了变化
- 元素字体大小变化
- 添加 / 删除元素:比如你用 JS 动态地向页面中添加了一个新的元素,或者删除了一个元素
- 伪类变化:比如
:hover状态触发了,浏览器需要重新计算布局 - 查询某些属性 / 方法:比如你查询了某个元素的
offsetWidth或offsetHeight,这会导致浏览器重新计算布局
重排的过程

重绘——浏览器的 “化妆” 工作
想象一下,你已经把一个产品组装好了,突然老板说:“嘿,这个零件的颜色要换一下!”你不得不再次给零件上色。浏览器也是一样,当页面的样式发生变化时,比如你动态地改变了某个元素的颜色,浏览器就需要重新绘制。这个过程就叫做重绘(Repaint)。
重绘的触发条件
浏览器也不是随便给自己 “化妆” 的,只有在特定情况下才会进行重绘。
会导致重绘的操作(包括但不限于):
- 改变元素的样式:比如用
JS动态改变了某个元素的颜色或背景 - 元素的可见性(显示/隐藏)变化:比如改变了某个元素的
visibility
重绘的过程
重绘的过程就像是浏览器在给网页“化妆”。具体步骤如下:

重排重绘的 “血泪史”
现在,你可能觉得重排和重绘也没什么大不了的,不就是重新搬砖和上色嘛。但实际上,这两个过程对浏览器说是非常耗时的,尤其是当页面很复杂的时候。每次重排重绘,浏览器都得加班加点,这可不是闹着玩的!
比如,你有一个复杂的网页,里面有大量的元素。当你用 JavaScript 动态地改变其中一个元素的宽度时,浏览器不仅要重新计算这个元素的布局,还要重新计算它周围所有元素的布局,最后还要重新绘制这些元素。这个过程非常耗时,会导致网页的性能大幅下降。
如何避免浏览器 “不必要的加班”?
既然重排重绘这么耗能,那我们能不能避免它们呢?答案是:“Of course!”
虽然我们不能完全避免重排和重绘,但可以通过一些优化技巧减少发生的频率和范围。
(一)减少重排
减少 DOM 操作
直接操作修改真实 DOM 性能消耗较高,React、Vue 等框架的虚拟 DOM 能帮你 “攒够多次 DOM 的改动” 一次性批量 patch,大幅度降低重排次数。如果手写JS批量修改可以用 createDocumentFragment 缓存节点(代码如下):
const fragment = document.createDocumentFragment();
for (let i = 0;i < 100;i++) {
const li = document.createElement('li');
li.textContent = i;
fragment.appendChild(li);
}
document.body.appendChild(fragment);
批量修改样式
把多次样式修改调整为批量修改,可以避免一行代码重排/重绘一次。例如:
const el = document.getElementById('box');
// 糟糕写法:三次重排
el.style.width = '100px';
el.style.height = '100px';
el.style.background = 'red';
// 推荐:一次重排
el.style.cssText = `width:100px;height:100px;background:red;`;
// 或者先隐藏元素,改完再显示
el.style.display = 'none';
// ... 改一堆样式
el.style.display = 'block';
避免内联样式的使用
内联样式会导致浏览器每次都需要解析和应用样式,从而增加了重排和重绘的次数。我们可以尽量避免内联样式使用,把样式集中到 class 类,通过切换类名一次完成多属性变更:
div {
width: 100px;
height: 100px;
background: red;
}
用 CSS3 动画代替 Javascript 动画
CSS3 动画属性能触发浏览器的 GPU 来优化处理,减少 CPU 的负担。比如使用 transform、opacity 等属性,他们不会触发重排,只会触发重绘。
用 transform 代替 left/top/width/height 设置
transform 只影响 “浏览器渲染流程的最终合成层”,不触发重排:
.move {
transform: translateX(100px); // 相对原始位置右移100px(只重绘,不重排)
}
用 opacity / visibility 代替 display
| 特点 | 优点 | 缺点 | 性能影响 | |
|---|---|---|---|---|
| opacity: 0; | 设置为0,元素在页面不可见,但仍占据空间 | 元素仍然在文档流中,不会引起页面布局的重排 | 元素仍然占据空间,可能会影响页面布局 | 对性能影响相对较小 |
| visibility: hidden; | 元素不可见,但元素仍然占据空间 | 1. 与 opacity 优点同上;2.可以通过 Javascript 方便地切换元素的可见性 | 与 opacity 缺点同上 | 对性能影响相对较小 |
| display: none; | 完全从文档流移除元素,不占据任何空间。元素及其子元素都不会被渲染 | 对于不需要的元素,可以彻底停止其渲染,节省性能 | 切换显示状态时,会引起页面布局的重排 | 切换显示,对性能影响较大 |
使用请求动画帧(requestAnimationFrame)
想象一下浏览器是个电影导演,屏幕刷新就像胶片录制,一秒钟连放 60 张(也就是 60 FPS)画面比较流畅。如果你每改一次样式,导演就喊 “咔!重拍这一帧” 如果你断断续续改 5 次,导演就要重拍 5 次,就造成了 “零散重排”的血泪现场。如果使用 requestAnimationFrame ,回调函数的执行频率与浏览器屏幕刷新率一致,可以避免丢帧现象,节省 CPU 资源。它就像是导演的 “统一口令”:“各位,有啥改动统统写到剧本里,等下一次开拍一次性执行!”
(二)减少重绘
批量修改样式
把样式变化写到 class,一次性更改,浏览器只做一次性集体“补妆”。
// 低效:三次重写 style,三次重绘
el.style.color = '#fff';
el.style.background = '#000';
el.style.borderRadius = '4px';
// 高效:一次性变更(JS)
el.classList.add('newStyle');
// CSS
.newStyle {
color: #fff;
background: #000;
border-radius: 4px;
}
使用 CSS 的 will-change 属性
will-change 属性可以告诉浏览器某个元素即将发生变化,让浏览器提前做好准备,从而减少重排重绘的开销。
.box {
width: 100px;
height: 100px;
background-color: red;
transition: transform 1s;
}
.box:hover {
will-change: transform;
transform: scale(1.2);
}
上述代码中,在悬停 hover 状态下,我们设置了will-change属性为transform,告知浏览器在动画发生之前进行优化准备。这样,浏览器可以为 .box 元素创建一个独立层,并在发生动画时只更新该层而不是整个页面。这样就可以提高动画的渲染性能。
总结
今天,我们揭秘了浏览器的重排和重绘这两个 “加班大户”。希望你通过这些轻松有趣的内容,能够更好地理解浏览器的工作原理,从而写出更高效的代码。记住,浏览器也是有感情的,它希望你能对它友好一点,让它享受下 “不加班” 轻松快感,它就会让你很快地加载呈现网页哦!
1276

被折叠的 条评论
为什么被折叠?



