前端基本功(四):性能优化之你真的懂回流、重绘与合成层吗?

1. 页面的呈现流程

  1. 浏览器把获取到的HTML代码解析成1个DOM树,HTML中的每一个tag都是DOM树中的1个节点,根节点就是咱们经常使用的document对象。DOM树里包含了全部HTML标签,包括display:none隐藏,还有用JS动态添加的元素等。
  2. 浏览器把全部样式(用户定义的CSS和用户代理)解析成样式结构体,在解析的过程当中会去掉浏览器不能识别的样式,好比IE会去掉-moz开头的样式,而FF会去掉_开头的样式。
  3. DOM Tree 和样式结构体组合后构建render tree, render tree相似于DOM tree,但区别很大,render tree能识别样式,render tree中每一个NODE都有本身的style,并且 render tree不包含隐藏的节点 (好比display:none的节点,还有head节点),由于这些节点不会用于呈现,并且不会影响呈现的,因此就不会包含到 render tree中。注意 visibility:hidden隐藏的元素仍是会包含到 render tree中的,由于visibility:hidden 会影响布局(layout),会占有空间。根据CSS2的标准,render tree中的每一个节点都称为Box (Box dimensions),理解页面元素为一个具备填充、边距、边框和位置的盒子。
  4. 一旦render tree构建完毕后,浏览器就能够根据render tree来绘制页面了。

image

2. 什么是回流与重绘

  1. 当render tree中的一部分(或所有)由于元素的规模尺寸,布局,隐藏等改变而须要从新构建。这就称为回流(reflow)。每一个页面至少须要一次回流,就是在页面第一次加载的时候。在回流的时候,浏览器会使渲染树中受到影响的部分失效,并从新构造这部分渲染树,完成回流后,浏览器会从新绘制受影响的部分到屏幕中,该过程成为重绘。
  2. 当render tree中的一些元素须要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,好比background-color。则就叫称为重绘。
  3. 回流必将引发重绘,而重绘不必定会引发回流。

repaint,就是浏览器得知元素产生了不影响排版的状况下后对这个元素进行从新绘制的过程。例如咱们改变了元素的颜色,加个下划线等。前端

reflow, 浏览器得知元素产生了对文档树排版有影响的样式变化,对全部受影响的dom节点进行从新排版工做node

3. 回流发生场景

当页面布局和几何属性改变时就须要回流。git

  1. 添加或者删除可见的DOM元素;
  2. 元素位置改变;
  3. 元素尺寸改变——边距、填充、边框、宽度和高度
  4. 内容改变——好比文本改变或者图片大小改变而引发的计算值宽度和高度改变;
  5. 页面渲染初始化;
  6. 浏览器窗口尺寸改变——resize事件发生时;
var s = document.body.style;
s.padding = "2px"; // 回流+重绘
s.border = "1px solid red"; // 再一次 回流+重绘
s.color = "blue"; // 再一次重绘
s.backgroundColor = "#ccc"; // 再一次 重绘
s.fontSize = "14px"; // 再一次 回流+重绘
// 添加node,再一次 回流+重绘
document.body.appendChild(document.createTextNode('abc!'));
复制代码

4.回流与重绘的影响

回流比重绘的代价要更高,回流的花销跟render tree有多少节点须要从新构建有关系,假设你直接操做body,好比在body最前面插入1个元素,会致使整个render tree回流,这样代价固然会比较高,但若是是指body后面插入1个元素,则不会影响前面元素的回流。github

5. 浏览器如何处理

每句JS操做都去回流重绘的话,浏览器可能就会受不了。浏览器

浏览器会维护1个队列,把全部会引发回流、重绘的操做放入这个队列,等队列中的操做到了必定的数量或者到了必定的时间间隔,浏览器就会flush队列,进行一个批处理。这样就会让屡次的回流、重绘变成一次回流重绘。缓存

虽然有了浏览器的优化,但有时候咱们写的一些代码可能会强制浏览器提早flush队列,这样浏览器的优化可能就起不到做用了。当你请求向浏览器请求一些 style信息的时候,就会让浏览器flush队列:性能优化

  1. offsetTop, offsetLeft, offsetWidth, offsetHeight
  2. scrollTop/Left/Width/Height
  3. clientTop/Left/Width/Height
  4. width,height
  5. 请求了getComputedStyle(), 或者 IE的 currentStyle

当你请求上面的一些属性的时候,浏览器为了给你最精确的值,须要flush队列,由于队列中可能会有影响到这些值的操做。即便你获取元素的布局和样式信息跟最近发生或改变的布局信息无关,浏览器都会强行刷新渲染队列。引擎会从新渲染来确保获取的值 是实时的。bash

6. 如何减小回流与重绘

减小回流、重绘其实就是须要减小对render tree的操做(合并屡次多DOM和样式的修改),并减小对一些style信息的请求,尽可能利用好浏览器的优化策略。app

  1. 对Render Tree的计算一般只须要遍历一次就能够完成,但table及其内部元素除外,他们可能须要屡次计算,一般要花3倍于同等元素的时间,这也是为何要避免使用table布局的缘由之一。
  2. 尽量在DOM树的最末端改变class。避免设置多层内联样式。将动画效果应用到position属性为absolute或fixed的元素上。避免使用CSS表达式(例如:calc())。
  3. 避免频繁操做样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性。避免频繁操做DOM,建立一个documentFragment,在它上面应用全部DOM操做,最后再把它添加到文档中。也能够先为元素设置display: none,操做结束后再把它显示出来。由于在display属性为none的元素上进行的DOM操做不会引起回流和重绘。避免频繁读取会引起回流/重绘的属性,若是确实须要屡次使用,就用一个变量缓存起来。对具备复杂动画的元素使用绝对定位,使它脱离文档流,不然会引发父元素及后续元素频繁回流。

6. 再次理解display:none 与 visibility:hidden 的异同

  1. 二者均可以在页面上隐藏节点。
    • display:none 隐藏后的元素不占据任何空间。它的宽度、高度等各类属性值都将“丢失”
    • visibility:hidden 隐藏的元素空间依旧存在。它仍具备高度、宽度等属性值
  2. 性能的角度而言,便是回流与重绘的方面。
    • display:none 会触发 reflow(回流)
    • visibility:hidden 只会触发 repaint(重绘),由于没有发现位置变化

他们二者在优化中 visibility:hidden 会显得更好,由于咱们不会由于它而去改变了文档中已经定义好的显示层次结构了。dom

  1. 对子元素的影响
    • display:none 一旦父节点元素应用了 display:none,父节点及其子孙节点元素所有不可见,并且不管其子孙元素如何设置 display 值都没法显示;
    • visibility:hidden 一旦父节点元素应用了 visibility:hidden,则其子孙后代也都会所有不可见。不过存在隐藏“失效”的状况。当其子孙元素应用了 visibility:visible,那么这个子孙元素又会显现出来。

无线性能优化:Composite

一个 Web 页面的展现,简单来讲能够认为经历了如下下几个步骤。

image

性能优化

提高为合成层简单说来有如下几点好处:

  1. 合成层的位图,会交由 GPU 合成,比 CPU 处理要快
  2. 当须要 repaint 时,只须要 repaint 自己,不会影响到其余的层
  3. 对于 transform 和 opacity 效果,不会触发 layout 和 paint

1. 提高动画效果的元素

合成层的好处是不会影响到其余元素的绘制,所以,为了减小动画元素对其余元素的影响,从而减小 paint,咱们须要把动画效果中的元素提高为合成层。

提高合成层的最好方式是使用 CSS 的 will-change 属性。从上一节合成层产生缘由中,能够知道 will-change 设置为 opacity、transform、top、left、bottom、right 能够将元素提高为合成层。

#target {
  will-change: transform; //兼容性很差
}
//对于那些目前还不支持 will-change 属性的浏览器
//目前经常使用的是使用一个 3D transform 属性来强制提高为合成层
#target {
  transform: translateZ(0);
}
复制代码

但须要注意的是,不要建立太多的渲染层。由于每建立一个新的渲染层,就意味着新的内存分配和更复杂的层的管理。

若是你已经把一个元素放到一个新的合成层里,那么可使用 Timeline 来确认这么作是否真的改进了渲染性能。别盲目提高合成层,必定要分析其实际性能表现。

2. 使用 transform 或者 opacity 来实现动画效果

其实从性能方面考虑,最理想的渲染流水线是没有布局和绘制环节的,只须要作合成层的合并便可:

image

为了实现上述效果,就须要只使用那些仅触发 Composite 的属性。目前,只有两个属性是知足这个条件的:transforms 和 opacity。

3. 减小绘制区域

  1. 对于不须要从新绘制的区域应尽可能避免绘制,以减小绘制区域,好比一个 fix 在页面顶部的固定不变的导航 header,在页面内容某个区域 repaint 时,整个屏幕包括 fix 的 header 也会被重绘。
  2. 而对于固定不变的区域,咱们指望其并不会被重绘,所以能够经过以前的方法,将其提高为独立的合成层。

4. 合理管理合成层:建立一个新的合成层并非免费的,它得消耗额外的内存和管理资源。实际上,在内存资源有限的设备上,合成层带来的性能改善,可能远远赶不上过多合成层开销给页面性能带来的负面影响。

大多数人都很喜欢使用 translateZ(0) 来进行所谓的硬件加速,以提高性能,可是性能优化并无所谓的“银弹”,translateZ(0) 不是,本文列出的优化建议也不是。抛开了对页面的具体分析,任何的性能优化都是站不住脚的,盲目的使用一些优化措施,结果可能会拔苗助长。所以切实的去分析页面的实际性能表现,不断的改进测试,才是正确的优化途径。

参考文档:淘宝FED
你们一块儿学前端地址: front-end-Web-developer-interview
相关文章
相关标签/搜索