浏览器的重排与重绘是什么意思?

5 篇文章 0 订阅
2 篇文章 0 订阅

准备


为了理解重排与重绘,我们首先需要了解一点浏览器渲染的基础知识 ~

网页生成分为五步:

1. HTML 被 HTML 解析器解析成 DOM 树

2. CSS 被 CSS 解析器解析成 CSSOM 树

3. 结合 DOM 树和 CSSOM 树,生成一颗渲染树(Render Tree)

4. 将所有渲染树的所有子节点进行平面合成,生成布局(flow)  -- 重排(也叫回流)

5. 将布局绘制(paint)在屏幕上   -- 重绘

其中 4 5 步是最耗时的,这两步就是我们常说的 渲染 

 网页生成时候,至少会渲染一次,在用户访问的过程中,可能会不断的重新渲染,即执行上述第 4 5 步,或者只执行第 5 步。

简单来说:

重排指重新生成布局,重新排列元素。例如margin: 10px 变成 margin: 20px,会导致重排;

重绘指重新描绘已经生成的布局。例如颜色改变,不会改变布局,只是重新描绘颜色而已;

重排必定会导致重绘,重绘不一定有重排。因为重排在重绘前执行。所以上述 margin 改变 ,重排完成后触发重绘,成本是很高的。

 为什么 width height margin 会引起重排+重绘,color transform  仅会导致重绘呢?

CSS的最终表现分为以下四步:

Recalculate Style -> Layout -> Paint Setup and Paint -> Composite Layers

查找并计算样式 -> 排布 -> 绘制 -> 组合层

width height margin 位于 Layout 层,color transform 位于 Composite Layers

遵循从前到后顺序影响的原则:Layout 改变必定触发 Paint Setup and Paint 和 Composite Layers ,color transform 仅会触发 Composite Layers

所以 ~ 我们得到了答案

 

1 重排


1.1 影响范围

全局范围:从根节点 html 开始对整个 Render Tree 进行重新布局

局部范围 :对 Render Tree 的某小部分进行重新布局

/* 更改下例 <p> 的 width height,会对h2,h3,body,html都产生影响*/
<html>
    <body>
      <div>
        <h2>hello</h4>
        <p>word</p>
        <h3>!</h5>
      </div>
    </body>
</html>


/* 更改下例 <p> 的 width height,若不超出div的范围,则只在div这个dom内触发重排,产生局部影响*/
<html>
    <body>
      <div style="width:100px;height:100px">
        <h2>hello</h4>
        <p>word</p>
        <h3>!</h5>
      </div>
    </body>
</html>

1.2 引起重排的属性和方法

widthheightmarginpadding
displayborderpositionoverflow
clientWidthclientHeightclientTopclientLeft
offsetWudthoffsetHeightoffsetTopoffsetLeft
scrollWidthscrollHeightscrollTopscrollLeft
scrollIntoView()scrollTo()getComputedStyle() 
getBoundingClientRect()scrollIntoViewIfNeeded()  

2 重绘


 2.1 引起重绘的属性

colorborder-stylevisibilitybackground
text-decorationbackground-imagebackground-positionbackground-repeat
outline-coloroutlineoutline-styleborder-radius
outline-widthbox-shadowbackground-size 

 

3 性能优化


原则:减少重排次数,重排范围

参考:https://segmentfault.com/a/1190000017491520

根据浏览器的渲染队列机制,我们在js文件中尽量少用以下样式请求:(下列会强制刷新队列,让浏览器立即执行重排加重绘)

  1. offsetTop, offsetLeft, offsetWidth, offsetHeight
  2. scrollTop, scrollLeft, scrollWidth, scrollHeight
  3. clientTop, clientLeft, clientWidth, clientHeight
  4. getComputedStyle(), currentStyle()

浏览器渲染机制:

浏览器把引起重排重绘的操作放入队列中,等到队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器再批量执行这些操作;

// 例如:以下仅会触发1次重排+重绘
div.style.left = '10px';
div.style.top = '10px';

 但是有些操作,就会使浏览器立即执行渲染任务,比如以下例子:

// 例如:下列会触发2次重排+重绘
// 因为队列中,可能会有影响到这些值的操作,为了给我们最精确的值,浏览器会立即重排+重绘
div.style.left = '10px';
console.log(div.offsetLeft);
div.style.top = '10px';
console.log(div.offsetTop);

优化举例:

3.1 样式集中改变

// bad
var left = 10;
var top = 10;
el.style.left = left + "px";
el.style.top  = top  + "px";
// good 
el.className += " theclassname";
// good
el.style.cssText += "; left: " + left + "px; top: " + top + "px;";

建议通过改变 class 或者 csstext 属性集中改变样式

3.2 分离读写操作

div.style.left = '10px';
div.style.top = '10px';
console.log(div.offsetLeft);
console.log(div.offsetTop);

上面触发2次重排+重绘的代码,这次只触发了一次重排

3.3 缓存布局信息

// bad 强制刷新 触发两次重排
div.style.left = div.offsetLeft + 1 + 'px';
div.style.top = div.offsetTop + 1 + 'px';

// good 缓存布局信息 相当于读写分离
var curLeft = div.offsetLeft;
var curTop = div.offsetTop;
div.style.left = curLeft + 1 + 'px';
div.style.top = curTop + 1 + 'px';

3.4 离线改变 dom

  • 隐藏要操作的 dom

在要操作 dom 之前,通过 display 隐藏 dom,当操作完成之后,才将元素的 display 属性为可见,因为不可见的元素不会触发重排和重绘

// 修改 dom 样式前
dom.display = 'none'
// 修改 dom 样式后
dom.display = 'block'
  • 通过使用 DocumentFragment 创建一个 dom 碎片,在它上面批量操作 dom,操作完成之后,再添加到文档中,这样只会触发一次重排
  • 复制节点,在副本上工作,然后替换它

3.5 position 属性为 absolute 或 fixed

position 属性为 absolute 或 fixed 的元素(脱离文档流),重排开销比较小,不用考虑它对其他元素的影响

3.6 优化动画

  • 可以把动画效果应用到 position 属性为 absolute 或 fixed 的元素上,减少对其他元素的影响
  • 动画效果还应牺牲一些平滑,来换取速度,这中间的度自己衡量:比如实现一个动画,以1个像素为单位移动这样最平滑,但是 reflow 就会过于频繁,大量消耗 CPU 资源,如果以3个像素为单位移动则会好很多。
  • 启用 GPU加速

    此部分来自 优化CSS重排重绘与浏览器性能

GPU(图像加速器):

GPU 硬件加速是指应用 GPU 的图形性能对浏览器中的一些图形操作交给 GPU 来完成,因为 GPU 是专门为处理图形而设计,所以它在速度和能耗上更有效率。

GPU 加速通常包括以下几个部分:Canvas2D,布局合成,CSS3 转换(transitions),CSS3 3D 变换(transforms),WebGL 和视频 (video)。

/*
 * 根据上面的结论
 * 将 2d transform 换成 3d
 * 就可以强制开启 GPU 加速
 * 提高动画性能
 */
div {
  transform: translate3d(10px, 10px, 0);
}
 

 

4 diff算法


首先奉上本人结合Vue v-for 讲解diff算法的博客~

大家都知道 React 中状态变化后,UI层会及时响应,但是状态变化后并不会立即去计算并渲染DOM数的变化部分,是先建立一个虚拟DOM,改动首先同步到虚拟DOM中,最后在批量同步到真实DOM中。不是每次改动都操作真实DOM。

为什么不能每次改变都直接去操作DOM树?

这便是因为在浏览器中,每一次DOM操作都有可能引起重绘重排(回流),这太昂贵了,如果每一次改变都直接对DOM进行操作,这会带来性能问题,而批量操作只会触发一次DOM更新。

 

《Flutter实战》这本书上作者提出了一个思考题,我认为不错,在此分享,欢迎大家留言交流!

思考题:Diff 操作和 DOM 批量更新难道不应该是浏览器的职责吗?第三方框架中去做合不合适?

 

未完待续 ~

 

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值