在CSS规范中有一个渲染对象的概念,通常用一个盒子(box, rectangle)来表示。mozilla通过一个叫frame的对象对盒子进行操作。frame主要的动作有三个:

  • 构造frame, 以建立对象树(DOM树)
  • reflow, 以确定对象位置,或者是调用mozilla的Layout(这里是指源码的实现)
  • 绘制,以便对象能显示在屏幕上
总结了在哪些情况下会导致reflow发生:
  • 改变窗囗大小
  • 改变文字大小
  • 添加/删除样式表
  • 内容的改变,如用户在输入框中敲字(这样也会-_-||)
  • 激活伪类,如:hover (IE里是一个兄弟结点的伪类被激活)
  • 操作class属性
  • 脚本操作DOM
  • 计算offsetWidth和offsetHeight
  • 设置style属性
reflow是不可避免的,只能将reflow对性能的影响减到最小。Nicole提出6点建议:
      • Change classes on the element you wish to style (as low in the dom tree as possible)
        尽可能限制reflow的影响范围。以上面的代码为例,要改变p的样式,class不要加在div上,通过父级元素影响子元素不好。最好直接加在p上。
      • Avoid setting multiple inline styles
        通过设置style属性改变结点样式的话,每设置一次都会导致一次reflow。所以最好通过设置class的方式。
      • Apply animations to elements that are position fixed or absolute
        实现元素的动画,它的position属性应当设为fixed或absolute,这样不会影响其它元素的布局。
      • Trade smoothness for speed
        权衡速度的平滑。比如实现一个动画,以1个像素为单位移动这样最平滑,但reflow就会过于频繁,CPU很快就会被完全占用。如果以3个像素为单位移动就会好很多。
      • Avoid tables for layout
        不要用tables布局的另一个原因就是tables中某个元素一旦触发reflow就会导致table里所有的其它元素reflow。在适合用 table的场合,可以设置table-layout为auto或fixed,这样可以让table一行一行的渲染,这种做法也是为了限制reflow的 影响范围。
      • Avoid JavaScript expressions in the CSS (IE only)
        很多情况下都会触发reflow,如果css里有expression,每次都会重新计算一遍。

总的来说,reflow就是载入内容树(在HTML中就是DOM树)和创建或更新frame结构的响应的一种过程。

要提高页面性能,其实就是避免reflow的开销。那么,有哪些方面是需要reflow的呢?比如,未指定图片宽高的话,图片的载入会使页面 reflow, 因为要根据图片宽高来更新frame。这里就有一个提高页面性能的小技巧:如果事先能够确定图片宽高的话,最好在HTML里写上。

在编写一些常见的动态效果时,一般使用CSS的display来切换可见性。很不幸,这也会产生reflow. 把元素置为display:none,相当于把这个元素的frame销毁了,再置回非none时,需要重新构造frame,这就产生了reflow. 而另外一个切换可见性的属性visibility则不存在reflow问题,置为visibility:hidden的元素的frame并没有销毁,需要 显示的时候其实就是一个绘制(上面提到的动作第三步)过程而已,没有reflow,因此效率会更高。如果你看过一些JavaScript库/框架的源码, 会发现它们大量使用visibility而不是display,道理应该如此。

移动端fixed元素滑动惯性平滑度

我们常常遇到这种产品需求,tab标签栏开始固定,当滚动向下超过该标签栏后便会变成fixed元素,一直出现在头部,这样的需求在电脑上没有问题,但是在iPhone5s以下的手机常常会出现小范围错位或者快速移动大范围错位的问题。

这个时候我们可以引起reflow迫使浏览器重绘以解决这个问题,这里推荐一个奇怪的hack写法:同时设置三个image元素的src属性,便可以全范围解决该难题,  该方案被团队证实并得到应用。

//三图片src,引发reflow,处理fixed方案惯性问题

var el = this.els.ctlc.find('img');

$(el[0]).attr("src", 'http://res.m.ctrip.com/html5/Content/images/144.png');

$(el[1]).attr("src", 'http://res.m.ctrip.com/html5/Content/images/144.png');

$(el[2]).attr("src", 'http://res.m.ctrip.com/html5/Content/images/144.png');

另外,上图中的tab标签下面的蓝线具有动画,但是在小米或者三星手机上可能不会移动,这个时候也可以动态引起reflow解决这个BUG。

其它

l  CSS选择器尽量使用id与class,避免过度层叠

l  避免使用数值,比如:border: none不会引起渲染,而boder: 0会

l  动画时候让元素脱离文档流,以免导致大量reflow

l  避免逐条修改DOM样式,改以className实现同样功能

l  操作DOM时将display设置为none,因为这种元素不会影响渲染,或者操作fragment对象取代操作显示在页面上的DOM

l  避免将获取DOM样式属性的操作写在循环中,可能引起重复reflow

 

我们再来讨论一下重绘与回流的区别:

前言:什么是重绘与回流?

重绘(repaints)是一个元素外观的改变所触发的浏览器行为,例如改变vidibility、outline、背景色等属性。浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。重绘不会带来重新布局,并不一定伴随回流。

回流(reflow)是更明显的一种改变,可以理解为渲染树需要重新计算。

1,为什么我们讨厌回流?

     回流这家伙,只要出现,就会重新对DOM树进行渲染,而大多数时候,这种全局性的渲染是无必要的。就像你花钱买了门票去景区玩,进门查票,出门查票,都可 以理解是吧?但是你吃饭也查票,上厕所也查票,就完全没必要了对吧?浏览器虽然不是人,但是没准也有脾气,回流的次数多了,性能跟不上了,就是立马崩溃掉 也是有可能的事儿。回流=查票

2,我们怎样搞定回流?

2.1 CSS中的定位、隐藏

   前面说到,回流的危害在于重新对DOM树进行渲染,那么,脱离文档流之后,进行的任何操作,都不会造成回流了!如果有需要经常进行复杂操作的地方,不妨使用position:absolute/fixed定位;或者是display:none,使之脱离文档流后进行操作,操作完成后再进入到文档流之中。

2.2 CSS中的顽固属性

   以下这些属性,只要是改动了他们的值,就会造成回流,建议将他们合并到一起操作,可以减少回流的次数。这 些属性包括:offsetTop、offsetLeft、 offsetWidth、offsetHeight;scrollTop、scrollLeft、scrollWidth、 scrollHeight;clientTop、clientLeft、clientWidth、 clientHeight;getComputedStyle() 、currentStyle()。

2.3 Javascript控制样式的最优解

   我们在利用Javascript进行样式控制的时候,不妨只是改变被控制者的class,而非在函数内部直接修改具体的样式。因为浏览器在我们用 Javascript修改每一个具体样式的时候,都会进行一次重绘,如果是修改了2.2中的顽固属性,还会进行回流,这给浏览器造成了巨大的负担。例如, 我们需要用这样的代码:

坏代码:
function selectAnchor(element){
var changeDiv = document.getElementById(element);
changeDiv.style.color = ‘#093′;
changeDiv.style.background = ‘#eee’;
changeDiv.style.height = ’200px’;
}
 
好代码:
CSS:
changeDiv {
background: #eee;
color: #093;
height: 200px;
}
JavaScript:
function selectAnchor(element) {
document.getElementById(element).className = ‘changeDiv’;
}

这样写的话可以有效地减少回流的次数,给浏览器减压。

 

posted on 2015-09-15 14:12  薛理想  阅读(1148)  评论(0编辑  收藏  举报