我们先看一下浏览器解析的工作流程
从图上可以看出,浏览器的工作流程可分为四步:
1、解析HTML构建DOM树:渲染引擎开始解析HTML文档,转换树中的HTML标签或者js生生的标签到DOM节点,它被称为内容树。即图中第一个黄色椭圆
2、构建渲染树:解析css(包括外部css文件和样式元素以及js生成的样式),根据css选择器计算出节点的样式,创建另一个数,渲染树。即图中第二个黄色椭圆
3、布局渲染树:从根节点递归调用,计算每一个元素的大小、位置等,给出每个节点所应该在屏幕上出现的青雀坐标。即图中第三个黄色椭圆
4、绘制渲染树:遍历渲染树,每个节点将使用UI后端层来绘制。即图中第四个黄色椭圆。
可以看出, reflow 和repaint分别对应步骤中的第三步和第四步
reflow 重写
1、什么是reflow
对于DOM结构中的各个元素都有自己的盒子模型,这些都需要浏览器根据各种样式(浏览器的、开发人员定义的等)来计算并根据计算结果将元素放到它该出现的位置,这个过程称为 reflow
2、什么情况下会导致reflow
(1)DOM元素的添加、修改(内容)、删除( Reflow + Repaint);
(2)当你移动 DOM 的位置,或是搞个动画的时候;
(3)当你 Resize 窗口的时候(移动端没有这个问题),或是滚动的时候。
(4)读取元素的某些属性(offsetLeft、offsetTop、offsetHeight、offsetWidth、 scrollTop/Left/Width/Height、clientTop/Left/Width/Height、 getComputedStyle()、currentStyle(in IE))
注:display:none 会发生reflow ,而,visibility:hidden 只会触发repaint ,因为它没有发现位置的变化。
3、为什么reflow不可取
DOM Tree里每一个结点都会有reflow方法,一个结点的reflow可能会导致其子结点,甚至父级结点以及同级结点的reflow,同时他会触发repaint,并且reflow的开销非常昂贵。在一些高性能的电脑上或许没什么,但是如果reflow发生在低性能比如手机上,那么这个过程非常痛苦和耗电的。
“A reflow is even more critical to performance because it involves changes that affect the layout of a portion of the page (or the whole page). Reflow of an element causes the subsequent reflow of all child and ancestor elements as well as any elements following it in the DOM. Reflows are very expensive in terms of performance, and is one of the main causes of slow DOM scripts, especially on devices with low processing power, such as phones. In many cases, they are equivalent to laying out the entire page again.”
repaint 重绘
1、什么是repaint
当盒子的位置、大小以及其他属性,例如颜色、字体大小等都确定下来之后,浏览器便把这些原色都按照各自的特性绘制一遍,将内容呈现在页面上,这个过程称为repaint
2、什么情况下会导致repaint
“A repaint occurs when changes are made to an elements skin that changes visibility, but do not affect its layout.” --重绘发生在元素的可见的外观被改变,但并没有影响到布局的时候。比如,仅修改DOM元素的字体颜色(只有Repaint,因为不需要调整布局)
3、为什么repaint 不可取
repaint的开销是昂贵的,因为浏览器必须验证DOM Tree中所有结点的可见性。
“repaint is expensive because the browser must verify the visibility of all other nodes in the DOM tree.”
////
怎么样优化
Reflow是不可避免的,只能将Reflow对性能的影响减到最小
(1)DOM离线后修改。如:
a)先把DOM给display:none(有一次reflow),然后你想怎么改就怎么改。比如修改100次,然后再把他显示出来。
b)如果需要创建多个DOM节点,可以使用DocumentFragment创建完后一次性的加入document
var fragment = document.createDocumentFragment();
fragment.appendChild(document.createTextNode('test 111'));
fragment.appendChild(document.createElement('br'));
fragment.appendChild(document.createTextNode('test 222'));
document.body.appendChild(fragment);
c)clone一个DOM节点(使用cloneNode()方法)到内存里,然后想怎么改就怎么改,改完后,和在线的那个的交换一下。克隆一个结点cloneNode(true)深度克隆(将deep设置成了true),即同时会克隆该结点的所有子结点。cloneNode(false)浅克隆(将deep设置成false),即仅仅克隆当前结点。如何将结点克隆到内存?如何和在线的交换?(暂时不会做)
(2)集中修改样式
a)尽可能少的修改style上的属性
b)尽量通过修改className来修改样式。
c)通过cssText属性来设置样式。因为 element.style.width=”80px”; //reflow 。调用一次就会产生一次reflow 调用越多产生越多。所以,element.style.cssText=”width:80px;height:80px;border:solid 1px red;”; //reflow 。 只产生一次reflow。
(3)缓存layout的值。var left = ele.offsetLeft; 多次使用left也就产生一次reflow。
(4)设置元素的position的属性为absolute 或者fixed 。元素脱离标准流也就从DOM Tree中脱离出来,在需要reflow的时候只需要reflow自身以及下级元素。
(5)尽量不要使用table布局。table元素一旦触发reflow就会导致table里所有的其它元素 reflow。在适合用table的场合,可以设置table-layout为auto或fixed,这样可以让table一行一行的渲染,这种做法也是为了限制reflow的影响范围
(6)避免使用expression,因为expression对浏览器资源要求比较高。它会每次调用都会重新计算一遍(包括加载页面)
在CSS中使用expression,用来把CSS属性和Javascript表达式关联起来,这里的CSS属性可以是元素固有的属性,也可以是自定义属性。就是说CSS属性后面可以是一段Javascript表达式,CSS属性的值等于Javascript表达式计算的结果。 在表达式中可以直接引用元素自身的属性和方法,也可以使用其他浏览器对象。
#container { width: expression((documentElement.clientWidth < 725) ? "725px" : "auto" ); }相当于:min-width: 725px
/-----------------------------实际应用---------------------------/
页面滚动时,网站导航条固定悬浮在页面顶部,同时固定悬浮条的出现和消失都是transition:ease-out动画效果。
1、具体实现
(1)当页面滚动一定距离(具体值自己根据需求设定)的时候隐藏。
(2)页面继续滚动到一定距离(具体值自己根据需求设定)的时候显示
有了以上的代码,页面的效果也就出来了,但是有点小bug。导航条元素原本有一定高度,因为使用fixed的定位之后,元素就会脱离文档流,原本在文档中的位置也就会消失,页面就会整体上移导航条高度的距离,会产生一次reflow,这次的reflow不可避免,因为是效果的需要。页面整体的上移就会是页面又一点抖的bug,如果文档的高度恰巧在浏览器窗口的高度与hidden的值之和与浏览器窗口的高度与hidden的值之和之间,而文档的高度又小于浏览器窗口的高度与一次scroll的高度(100px或者114px,不同内核的浏览器有些不一样),这段距离就是临界区域,鼠标滚动一下页面就会上下的闪一下,效果感觉就不会很好。
2、解决办法:当元素position:fixed脱离文档流的时候,原来导航条高度不让它消失。
(1)给body添加padding-top:导航条高度
(2)给导航条元素外再加一个父级div并设置height:导航条高度,当导航条脱离的时候仍有其父元素占位。
(3)给导航条的下一个元素(弟弟元素)添加margin-top:导航条高度。如果导航条所在页面是模板,有好多的页面都在引用,那么这种方法不可取,因为会有好多页面都会引用导航条所在的模板页面,这就导致了导航条的下一个元素不唯一,总不能所有的页面中的导航条的下一个元素都用js添加样式。
(4)初始状态给body添加一个padding-top,并且初始状态就将导航条用position:absolute脱离文档流
第一种方法和第三种方法的原理是一样的,都是用js操作css动态给DOM结点添加样式,会产生一次reflow。而第二种和第四种方式就不会产生reflow。那么这个效果的实现至少就会减少一次的reflow。