vue学习--浏览器渲染引擎、html重绘和重排

html优化 与重点概念

浏览器渲染引擎

##主要模块

一个渲染引擎主要包括:html解析器,css解析器,javascript引擎,布局layout模块,绘图模块。

  • html解析器:解释HTML文档的解析器,主要作用是将HTML文本解释成DOM树。
  • CSS解析器,它的作用是为DOM中的各个元素对象计算出样式信息,为布局提供基础设施
  • Javascript引擎:使用Javascript代码可以修改网页的内容,也能修改css的信息,jacascript引擎能够解释javascript代码,并通过DOM接口来修改网页内容和样式信息,从而改变渲染的结果。
  • 布局(layout):在DOM创建之后,Webkit需要将其中的元素对象同样式信息结合起来,计算他们的大小位置等布局信息,形成一个能表达这所有信息的内部表示模型
  • 绘图模块(paint):使用图形库将布局计算后的各个网页的节点绘制成图像结果

备注:文档对象模型(Document Object Model,简称DOM)

 

一、渲染过程

  • 浏览器渲染页面的整个过程:浏览器会从上到下解析文档。
    1. 遇见 HTML 标记,调用HTML解析器解析为对应的 token (一个token就是一个标签文本的序列化)并构建 DOM 树(就是一块内存,保存着tokens,建立它们之间的关系)。
    2. 遇见 style/link 标记 调用解析器 处理 CSS 标记并构建 CSS样式树。【谷歌浏览器内部style样式,交给HTML解析器进行解析。】
    3. 遇见 script 标记 调用 javascript解析器 处理script标记,绑定事件、修改DOM树/CSS树 等
    4. 将 DOM树 与 CSS树 合并成一个渲染树。
    5. 根据渲染树来渲染,以计算每个节点的几何信息(这一过程需要依赖图形库)。
    6. 将各个节点绘制到屏幕上

备注:以上这些模块依赖很多其他的基础模块,包括要使用到网络 存储 2D/3D图像 音频视频解码器 和 图片解码器。 所以渲染引擎中还会包括如何使用这些依赖模块的部分。
 

二、阻塞渲染

####1.关于css阻塞: 声明:只有link引入的外部css才能够产生阻塞。

1.style标签中的样式: (1). 由html解析器进行解析; (2). 不阻塞浏览器渲染(可能会产生“闪屏现象”); (3). 不阻塞DOM解析;

2.link引入的外部css样式(推荐使用的方式): (1). 由CSS解析器进行解析。 (2). 阻塞浏览器渲染(可以利用这种阻塞避免“闪屏现象”)。 (3). 阻塞其后面的js语句的执行 (4). 不阻塞DOM的解析:

[原因]:如果后面js的内容是获取元素的样式,例如宽高等CSS控制的属性,如果不等样式解析完毕,后面的js就获得了错误的信息。

由于浏览器也不知道后续js的具体内容,所以只好等前面所有的样式解析完毕后,再执行js。

例如:Firefox在样式表加载和解析的过程中,会禁止掉所有的脚本。

【注意】:现代浏览越发注重用户体验,对于WebKit内核的浏览器而言,仅当脚本尝试访问的样式属性、或可能受尚未加载的样式表影响时,它才会禁止该脚本。

(4). 不阻塞DOM的解析:

【原因】DOM解析和CSS解析是两个并行的进程,浏览器解析DOM生成DOM Tree。

解析CSS生成CSS tree,最终组成render tree,再渲染页面。

即:DOM的解析,和CSS的解析是并行执行的,即:不阻塞DOM的解析。

3.优化核心理念:尽可能快的提高外部css加载速度 (1).使用CDN节点进行外部资源加速。 (2).对css进行压缩(利用打包工具,比如webpack,gulp等)。 (3).减少http请求数,将多个css文件合并。 (4).优化样式表的代码

####2.关于js阻塞

1.阻塞DOM解析: 原因:浏览器不知道后续脚本的内容,如果先去解析了下面的DOM,而随后的js删除了后面所有的DOM, 那么浏览器就做了无用功,浏览器无法预估脚本里面具体做了什么操作,例如像document.write 这种操作,索性全部停住,等脚本执行完了,浏览器再继续向下解析DOM。

2.阻塞页面渲染: 原因:js中也可以给DOM设置样式,浏览器同样等该脚本执行完毕,再继续干活,避免做无用功。

3.阻塞后续js的执行: 原因:维护依赖关系,例如:必须先引入jQuery再引入bootstrap


####3.备注

【备注1】:css的解析和js的执行是互斥的(互相排斥),css解析的时候js停止执行,js执行的时候css停止解析。

【备注2】:无论css阻塞,还是js阻塞,都不会阻塞浏览器加载外部资源(图片、视频、样式、脚本等) 原因:浏览器始终处于一种:“先把请求发出去”的工作模式,只要是涉及到网络请求的内容, 无论是:图片、样式、脚本,都会先发送请求去获取资源,至于资源到本地之后什么时候用, 由浏览器自己协调。这种做法效率很高。(比如src,href,后面的资源先请求发出去) 【备注3】:WebKit 和 Firefox 都进行了【预解析】这项优化。在执行js脚本时,浏览器的其他线程会解析文档的其余部分, 找出并加载需要通过网络加载的其他资源。通过这种方式,资源可以在并行连接上加载, 从而提高总体速度。请注意,预解析器不会修改 DOM 树。

在上述的过程中,网页在加载和渲染过程中会触发“DOMContentloaded”和“onload”事件 分别是在DOM树构建(解析)完成之后,以及DOM树构建完并且网页所依赖的资源都加载完之后。

  • 上面介绍的是一个完整的渲染过程,但现代网页很多都是动态的,这意味着在渲染完成之后,由于网页的动画或者用户的交互, 浏览器其实一直在不停地重复执行渲染过程。(重绘重排),以上的数字表示的是基本顺序,这不是严格一致的, 这个过程可能重复也可能交叉

##css图层

浏览器在渲染一个页面时,会将页面分为很多个图层,图层有大有小,每个图层上有一个或多个节点。
​ 在渲染DOM的时候,浏览器所做的工作实际上是:

        1. 获取DOM后分割为多个图层
        2. 对每个图层的节点计算样式结果        (Recalculate style--样式重计算)
        3. 为每个节点生成图形和位置            (Layout--重排,回流) ---》 更新图层树
        4. 将每个节点绘制填充到图层位图中        (Paint--重绘)
        5. 图层作为纹理上传至GPU
        6. 组合多个图层到页面上生成最终屏幕图像    (Composite Layers--图层重组)

 

##图层创建的条件

大致来说,Chrome浏览器满足以下任意情况就会创建图层:

  1. 拥有具有3D变换的CSS属性

  2. 使用加速视频解码的<video>节点

  3. <canvas>节点

  4. CSS3动画的节点

  5. 拥有CSS加速属性的元素(will-change)
 

##浏览器的运行机制

1、构建DOM树(parse),渲染引擎解析HTML文档,首先将标签转换成DOM树中的DOM node(包括js生成的标签)生成内容树(content Tree/DOM Tree);

2、构建渲染树(construct):解析对应的css样式文件信息(包括js生成的样式和外部css文件),而这些文件信息以及HTML中可见的指令(如<b></b>),构建渲染树(rendering Tree/Frame Tree);render tree中每个NODE都有自己的style,而且render tree不包含隐藏的节点(比如display:none的节点,还有head节点),因为这些节点不会用于呈现

3:布局渲染树(reflow/layout):从根节点递归调用,计算每一个元素的大小、位置等,给出每个节点所应该在屏幕上出现的精确坐标。

4:绘制渲染树(paint/repaint):遍历渲染树,使用UI层来绘制每个节点。

先有dom树,在加上css,再计算各个元素的位置,再进行绘制画出来。

##重绘(repaint或redraw)

放盒子位置、大小以及其他属性,例如颜色、字体大小等都确定下来之后,浏览器便把这些原色都按照各自的特性绘制一遍,将内容呈现在页面上。

重绘是指一个元素外观的改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,是元素呈现新的外观。

触发重绘的条件:改变元素外观属性。如:color,background-color等。

注意:table及其内部元素可能需要多次计算才能确定好其在渲染树种节点的属性值,比同等元素要多花两倍时间,这就是我们尽量避免使用table布局页面的原因之一。重绘是以图层为单位,如果图层中某个元素需要重绘,那么整个图层都需要重绘。比如一个图层包含很多节点,其中有个gif图,gif图的每一帧,都会重绘整个图层的其他节点,然后生成最终的图层位图。所以这需要通过特殊的方式来强制gif图属于自己一个图层(translateZ(0)或者translate3d(0,0,0))css的动画也是一样(好在绝大部分情况浏览器自己会为css3动画的节点创建图层)

##重排(reflow,回流)

渲染对象在创建完成并添加到渲染树时,并不包含位置和大小信息。计算这些值的过程称为布局或重排。

重排(重构、回流、reflow):当渲染树种一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建,这就称为回流(如reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候。

重绘和重排的关系:在回流的时候,浏览器会使渲染树中收到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程称为重绘。

所以,重排必定会引发重绘,但重绘不一定会引发重排。

触发重排的条件:任何页面布局和几何属性的改变都会触发重排,Reflow的成本比Repaint的成本高得多的多。DOM Tree里的每个结点都会有reflow方法,一个结点的reflow很有可能导致子节点,甚至父点以及同级结点的reflow。在一些高性能的电脑上也许还没什么,但是如果reflow发生在手机上,那么这个过程是非常痛苦和耗电的。比如:

Chrome中满足以下任意情况就会创建图层:

  • 3D或透视变换(perspective transform)CSS属性
  • 使用加速视频解码的<video>节点
  • 拥有3D(WebGL)上下文或加速的2D上下文的<canvas>节点
  • 混合插件(如Flash)
  • 对自己的opacity做CSS动画或使用一个动画webkit变换的元素
  • 拥有加速CSS过滤器的元素
  • 元素有一个包含复合层的后代节点(一个元素拥有一个子元素,该子元素在自己的层里)
  • 元素有一个z-index较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)
  • 需要注意的是,如果图层中某个元素需要重绘,那么整个图层都需要重绘。比如一个图层包含很多节点,其中有个gif图,gif图的每一帧,都会重回整个图层的其他节点,然后生成最终的图层位图。所以这需要通过特殊的方式来强制gif图属于自己一个图层(translateZ(0)或者translate3d(0,0,0)),CSS3的动画也是一样(好在绝大部分情况浏览器自己会为CSS3动画的节点创建图层)

1:页面渲染初始化(无法避免)

2:添加或删除可见的DOM元素

3:元素位置的改变,或者使用动画

4:元素尺寸的改变--大小,外边距,边框

5:当Resize浏览器窗口,尺寸变化时(移动端没有这个问题,因为移动端的缩放没有影响布局视口)

6:填充内容的改变,比如文本的改变或图片大小改变而引起的计算值宽度和高度的改变l

7:读取某些元素属性:(offsetLeft/Top/Height/Width,clientTop/Left/Width/Height.scrollTop/Left/Width/Height,width/height,getComputedStyle(),currentStyle(IE))

注意:display:none 会触发reflow,而visibility:hidden只会触发repaint,因为没有发生位置变化。

重绘重排的代价:耗时,导致浏览器卡慢

##优化:

如果我们需要使得动画或其他节点渲染的性能提高,需要做的就是减少浏览器再运行时所需要做的工作(尽量减少1234步)
浏览器中会进行五步,Recalculate style--样式重计算,layout--回流和重布局,Update Layer Tree,更新图层树,Paint,绘制图层树,Composite Layers--图层重组

1.计算需要被加载到节点上的样式结果(Recalculate style--样式重计算)

2.为每个节点生成图形和位置(layout--回流和重布局)

3.将每个节点填充到图层中(Paint Setup 和Paint --重绘)

4.组合图层到页面上(Composite Layers--图层重组)

##优化我们可以做些什么

1)元素位置移动变换时尽量使用css的transform来代替对top left等的操作。

变换(transform)和透明度(opacity)的改变仅仅影响图层的组合。

2)使用opacity来代替visibility

     1:使用visibility不触发重排,但是依然重绘。

     2:直接使用opacity即触发重绘,又触发重排(GPU底层设计如此!)

     3:opacity配合图层使用,即不触发重绘也不触发重排。

     原因:需要注意的是,上面那些触发重绘的属性里面没有opacity(透明度),很奇怪不是吗?实际上透明度的改变后,GPU在绘画时只是简单的降低之前已经画好的纹理的alpha值来达到效果,并不需要整体的重绘。不过这个前提是这个被修改opacity本身必须是一个图层,如果图层下还有其他节点,GPU也会将他们透明化

3)不要使用table布局   table-cell

4)将多次改变样式属性的操作合成一次操作,不要一条条的修改DOM的样式,预先定义好class,然后修改DOM的className。

5)将DOM离线后再修改

由于display属性为none的元素不在渲染树种,对隐藏的元素操作不会引发其他元素的重排。

如果要对一个元素进行复杂的操作时,可以先隐藏它,操作完成后再显示。这样只在隐藏和显示时触发2次。

6)利用文档碎片(documentFragment)----vue使用了该种方式提升性能。

7)不要把获取某些DOM节点的属性值放在一个循环里当成循环的变量

      当你请求向浏览器请求一些style信息的时候,就会让浏览器flush队列,比如:

      1.offsetTop,offetLeft,offsetWidth,offsetHeight

      2.scrollTop/Left/width/Height

      3.clientTop/Left/width/height

      4.width,height

     当你请求上面的一些属性时,浏览器为了给你最精确的值,需要刷新内部队列。因为队列中可能会有影响到这些值的操作。即使你获取元素的布局和样式信息跟最近的布局信息差不多, 浏览器都会强行刷新渲染队列。

8)动画实现过程中,启用GPU硬件加速:transform:tranlateZ(0)

9)为动画元素新建图层,提高动画元素的z-index

 

1:浏览器自己的优化:浏览器会维护1个队列,把所有会引起回流、重绘的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理。这样就会让多次的回流、重绘变成一次回流重绘。

2:我们要注意的优化:我们要减少重绘和重排就是减少对渲染树的操作,则我们可以合并多次的DOM和样式的修改。并减少对style样式的请求。

1)直接改变元素的className

2)dispaly:none;先设置元素为display:none;然后进行页面布局等操作;设置完成后将元素设置为dispaly:block,这样的话就只引发两次重绘和重排。

3)使用cloneNode(true or false) 和repalceChild技术,引发一次回流和重绘。

4)将需要多次重排的元素,position属性设为absolute或fixed,元素脱离了文档流,它的变化不会影响到其他元素。

5)如果需要创建多个dom节点,可以使用DocumentFragment创建完后一次性加入document

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值