原理篇:浏览器加班现场——重排重绘血泪史!


嘿,小伙伴们!今天咱们继续聊聊浏览器的 “工作日常”。你知道吗?每次你打开一个网页,浏览器可不是简单地 “唰” 一下就搞定了。它背后可是有一大堆的“加班血泪史”呢!今天,我们就来揭秘浏览器的重排(Reflow)和重绘(Repaint)这两个 “加班大户”。

浏览器的 “工作流程”

想象一下,你是一个工厂的工人,每天的任务是把一堆原材料(HTML、CSS、JavaScript)组装成一个精美的产品(网页)。浏览器的工作也差不多,它需要把一堆代码变成你看到的漂亮网页。这个过程可以分为几个步骤:

  1. 解析 HTML:浏览器先把 HTML 代码解析成一个树状结构,叫做 DOM 树(文档对象模型)。这就像是把产品的一堆零件分类放好,方便后续组装。
    在这里插入图片描述
  2. 解析 CSS:然后,浏览器解析 CSS 代码,生成一个样式规则表。这就像是把每个零件的说明书准备好,告诉浏览器每个零件应该怎么装饰。
    在这里插入图片描述
  3. 构建渲染树 Render Tree:接下来,浏览器把 DOM 树和样式规则表结合起来,生成一个渲染树。这就像是把零件和说明书结合起来,开始组装产品。
    在这里插入图片描述
  4. 布局(Layout):浏览器根据渲染树计算每个元素的位置和大小。这就像是在工厂里,把每个零件放到正确的位置上。
  5. 绘制(Paint):浏览器把渲染树中的每个节点元素绘制到屏幕上。这就像是把组装好的产品涂上颜色,让它看起来漂亮。
  6. 分层树(Layer):接下来设计比较复杂页面元素的场景,比如3D变化、页面滚动、z-index在 z 轴排序需要渲染引擎为特定的节点生成专用图层,形成分层树。
  7. 合成显示(Composite):浏览器完成了分层树的构建后,它会把图层的绘制分成很多个绘制指令组成带绘制列表,然后提交给渲染引擎的合成线程处理,形成合成位图,最终呈现在屏幕上。
    在这里插入图片描述

重排——浏览器的 “搬砖” 工作

想象一下,你是工厂的工人,已经把一个产品组装好了,突然老板说:“嘿,这个零件要换个位置!” 这是你不得不再次把零件拆下来,重新组装。浏览器也是一样,当页面的内容发生变化时,比如你动态地改变了某个元素的大小或位置,浏览器就需要重新计算位置布局,这个过程就叫做重排(Reflow)。

重排的触发条件

浏览器可不是闲着没事干,只有在渲染树的部分或全部的元素节点的尺寸、结构或几何属性发生改变时,浏览器才会进行重排(也叫回流)。

会导致重排的操作(包括但不限于):

  • 浏览器窗口大小发生变化
  • 元素尺寸 / 位置发生变化:比如你用 JS 动态地改变了这个元素的 width/height
  • 元素内容发生变化:比如你改变了某个元素的文本内容,导致他的大小发生了变化
  • 元素字体大小变化
  • 添加 / 删除元素:比如你用 JS 动态地向页面中添加了一个新的元素,或者删除了一个元素
  • 伪类变化:比如 :hover 状态触发了,浏览器需要重新计算布局
  • 查询某些属性 / 方法:比如你查询了某个元素的 offsetWidthoffsetHeight,这会导致浏览器重新计算布局

重排的过程

在这里插入图片描述

重绘——浏览器的 “化妆” 工作

想象一下,你已经把一个产品组装好了,突然老板说:“嘿,这个零件的颜色要换一下!”你不得不再次给零件上色。浏览器也是一样,当页面的样式发生变化时,比如你动态地改变了某个元素的颜色,浏览器就需要重新绘制。这个过程就叫做重绘(Repaint)。

重绘的触发条件

浏览器也不是随便给自己 “化妆” 的,只有在特定情况下才会进行重绘。

会导致重绘的操作(包括但不限于):

  • 改变元素的样式:比如用 JS 动态改变了某个元素的颜色或背景
  • 元素的可见性(显示/隐藏)变化:比如改变了某个元素的 visibility

重绘的过程

重绘的过程就像是浏览器在给网页“化妆”。具体步骤如下:
在这里插入图片描述

重排重绘的 “血泪史”

现在,你可能觉得重排和重绘也没什么大不了的,不就是重新搬砖和上色嘛。但实际上,这两个过程对浏览器说是非常耗时的,尤其是当页面很复杂的时候。每次重排重绘,浏览器都得加班加点,这可不是闹着玩的!
比如,你有一个复杂的网页,里面有大量的元素。当你用 JavaScript 动态地改变其中一个元素的宽度时,浏览器不仅要重新计算这个元素的布局,还要重新计算它周围所有元素的布局,最后还要重新绘制这些元素。这个过程非常耗时,会导致网页的性能大幅下降。

如何避免浏览器 “不必要的加班”?

既然重排重绘这么耗能,那我们能不能避免它们呢?答案是:“Of course!”
虽然我们不能完全避免重排和重绘,但可以通过一些优化技巧减少发生的频率和范围。

(一)减少重排

减少 DOM 操作

直接操作修改真实 DOM 性能消耗较高,ReactVue 等框架的虚拟 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 的负担。比如使用 transformopacity 等属性,他们不会触发重排,只会触发重绘。

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 元素创建一个独立层,并在发生动画时只更新该层而不是整个页面。这样就可以提高动画的渲染性能。

总结

今天,我们揭秘了浏览器的重排和重绘这两个 “加班大户”。希望你通过这些轻松有趣的内容,能够更好地理解浏览器的工作原理,从而写出更高效的代码。记住,浏览器也是有感情的,它希望你能对它友好一点,让它享受下 “不加班” 轻松快感,它就会让你很快地加载呈现网页哦!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿小铭子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值