🎭 渲染树与回流重绘:一场浏览器小镇的"爱恨情仇"
💡 每次面试被问"什么是回流和重绘",你是不是只会机械背诵? 今天,让我们走进浏览器小镇,通过一场生动的对话,把这段关系彻底搞懂!
👥 第一章:小镇居民登场
在浏览器小镇里,住着几位性格迥异的居民:
| 角色 | 职责 | 性格特点 |
|---|---|---|
| 🏗️ DOM 君 | 搭建房屋骨架(HTML) | 结构控,严谨死板 |
| 🎨 CSS 小姐 | 房屋装修(样式/布局) | 审美达人,追求完美 |
| ⚡ JS 捣蛋鬼 | 动态修改页面 | 精力旺盛,总爱搞事情 |
| 🔧 渲染引擎大叔 | 把 DOM+CSS 变成画面 | 小镇管家,稳重可靠 |
| 🌳 渲染树先生 | 整理"哪些元素要画" | 大叔的得力助手,戴眼镜的知识分子 |
🌱 第二章:渲染树的诞生
有一天,DOM 君刚搭好一栋房子的骨架:
html
<div class="house">
<h1>标题</h1>
<p>内容</p>
<div style="display:none;">隐藏的房间</div></div>
CSS 小姐拿着她的装修方案来了:
css
.house { width: 300px; height: 200px; background: #f0f0f0; }
h1 { color: red; font-size: 24px; }
p { color: blue; margin: 10px; }
渲染引擎大叔说:"好,现在我要请渲染树先生出场了!"
渲染树先生推了推眼镜,开始工作:
💬 "我的任务很简单:遍历 DOM 树,结合 CSS 样式,生成一棵只包含『可见元素』的树。"
他指着那个 display:none 的 div 说:
💬 "这个元素虽然存在于 DOM 中,但它不需要显示,所以不会进入渲染树。"
最终,渲染树长这样:
RenderTree
├── RenderDiv (class="house")
├── RenderH1 ("标题")
│ └── RenderP ("内容")
📌 关键点
- ✅ 渲染树 ≠ DOM 树
- ✅ 渲染树只包含可见节点(
display:none的元素不在其中) - ⚠️ 注意:
visibility:hidden的元素仍在渲染树中(因为它占据空间,只是不绘制) - ✅ 渲染树中的每个节点都有对应的几何信息(位置、大小)
🏗️ 第三章:回流(Reflow)—— 大工程
某天,JS 捣蛋鬼跑过来,一把扯掉了 house 的宽度:
javascript
document.querySelector('.house').style.width = '500px';
渲染树先生立刻大喊:
🔴 "停!所有人注意,我要重新计算布局了!"
这就是回流(Reflow),也叫布局(Layout)。
❓ 为什么叫"大工程"?
因为改变一个元素的尺寸或位置,可能会影响它的所有子孙元素,甚至兄弟元素的位置。想象一下:
🏠 你把客厅的墙往右推了 2 米,那么:
- 沙发要挪位置
- 电视柜要重新摆放
- 吊灯的高度可能要调整
- 隔壁卧室的门框可能也要跟着动
⚠️ 触发回流的常见操作
- 改变元素的宽高(
width、height) - 改变元素的位置(
top、left、margin、padding) - 改变字体大小(
font-size) - 添加或删除可见的 DOM 元素
- 读取某些属性(
offsetWidth、clientHeight等,浏览器会强制同步回流以返回最新值)
渲染树先生擦着汗说:
😰 "每次回流,我都要从受影响的元素开始,重新计算整个子树的几何信息。这很贵!"
🎨 第四章:重绘(Repaint)—— 小修小补
又一天,JS 捣蛋鬼只是改了个颜色:
javascript
document.querySelector('h1').style.color = 'green';
渲染树先生看了一眼,松了口气:
😌 "还好,只是换了个颜色,位置和大小都没变。我只需要通知画家重新涂一遍颜色就行。"
这就是重绘(Repaint)。
❓ 为什么比重流便宜?
因为重绘不涉及布局计算,只需要重新绘制像素。就像:
🛋️ 你只是把沙发的套子从红色换成了蓝色,沙发本身没动,邻居也不用挪位置。
⚠️ 触发重绘的常见操作
- 改变颜色(
color、background-color) - 改变可见性但不影响布局(
visibility) - 改变边框样式(
border-style,但注意:如果边框宽度变了,会触发回流!)
🔗 第五章:两者的关系
DOM 君、CSS 小姐和 JS 捣蛋鬼围坐在一起,渲染树先生拿出白板:
┌─────────────────────────────────────┐
│ 修改元素属性 │
└──────────────┬──────────────────────┘
│
┌───────▼────────┐
│ 是否影响几何? │
└───┬────────┬───┘
│ │
YES NO
│ │
┌─────▼──┐ ┌──▼──────┐
│ 回流 │ │ 重绘 │
│(Reflow)│ │(Repaint) │
└────┬───┘ └─────────┘
│
┌────▼────┐
│ 必然伴随 │
│ 重绘 │
└─────────┘
渲染树先生总结道:
💡 "回流一定伴随重绘,但重绘不一定伴随回流。"
💡 "你可以把回流理解为『重新排座位』,重绘理解为『重新穿衣服』。排座位肯定得重新穿衣服,但换衣服不用重新排座位。"
🚀 第六章:性能优化小贴士
JS 捣蛋鬼问:"那我平时写代码要注意什么?"
渲染树先生给出了几条建议:
1️⃣ 批量修改 DOM
❌ 坏做法:
javascript
// 每次修改都触发回流
el.style.width = '100px';
el.style.height = '200px';
el.style.margin = '10px';
✅ 好做法:
javascript
// 一次性修改,只触发一次回流
el.style.cssText = 'width:100px;height:200px;margin:10px;';
// 或者用 class
el.classList.add('new-style');
2️⃣ 使用 transform 和 opacity 做动画
这两个属性不会触发回流,浏览器会把它们交给 GPU 处理:
css
/* ✅ 好的动画 */
.box {
transform: translateX(100px); /* 不触发回流 */
opacity: 0.5; /* 不触发回流 */
}
/* ❌ 坏的动画 */
.box {
left: 100px; /* 触发回流! */
margin-left: 10px; /* 触发回流! */
}
*/}
3️⃣ 避免频繁读取会触发回流的属性
javascript
// ❌ 坏:每次读取都强制同步回流
for (let i = 0; i < 100; i++) {
el.style.left = el.offsetLeft + 10 + 'px'; // offsetLeft 触发回流
}
// ✅ 好:先缓存
const currentLeft = el.offsetLeft; // 只触发一次回流
for (let i = 0; i < 100; i++) {
el.style.left = currentLeft + 10 * (i + 1) + 'px';
}
4️⃣ 使用 DocumentFragment 批量插入
javascript
// ❌ 坏:每次 append 都可能触发回流
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
ul.appendChild(li);
}
// ✅ 好:批量插入,只触发一次回流
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.appendChild(li);
}
ul.appendChild(fragment);
📝 第七章:面试速记卡
最后,渲染树先生给 JS 捣蛋鬼一张小卡片:
| 概念 | 触发条件 | 成本 | 例子 |
|---|---|---|---|
| 🌳 渲染树构建 | 页面首次加载 / DOM 结构变化 | 🔴 高 | 解析 HTML + CSS,生成可见节点树 |
| 🏗️ 回流 (Reflow) | 元素几何信息变化 | 🔴🔴 很高 | 改宽高、位置、字体大小 |
| 🎨 重绘 (Repaint) | 元素外观变化但不影响布局 | 🟡 中等 | 改颜色、背景色、visibility |
🎯 一句话总结
回流是重新算位置,重绘是重新涂颜色。回流必带重绘,重绘不带回流。能用 transform/opacity 就别动 layout 属性。
🎬 尾声
JS 捣蛋鬼听完,若有所思地点点头:
😄 "原来如此!以前我只知道背定义,现在终于明白它们之间的关系了。下次面试,我就讲这个故事!"
渲染树先生微笑着说:
🌟 "记住,理解比背诵更重要。当你真正理解了浏览器的渲染机制,写出高性能的代码就是自然而然的事了。"
💬 互动时间
如果你也觉得这个故事有帮助:
- 👍 点赞支持一下
- 💬 评论说说你在项目中遇到的性能优化案例
- 🔖 收藏方便面试前复习
- 📢 分享给正在准备前端面试的朋友
关注我,下期带你解锁更多浏览器原理的趣味故事!🚀
本文首发于掘金,转载请注明出处