什么是Repaint/Reflow?
先扒一张图,来解释下浏览器的工作流程

上图可归纳为四步:
- 解析构建 DOM 树
渲染引擎开始解析 html 文档,转换树中的标签或者生成的标签到 DOM 节点,这时的它称为内容树 - 构建渲染树
解析所有 CSS,并据此计算节点样式,创建渲染树 - 布局渲染树
从根节点递归调用,计算元素的大小位置,并把每个节点放在该出现的精准坐标位置 - 绘制渲染树
遍历渲染树,每个节点使用 UI 后端层绘制
从以上不难看出,Repaint 和 Reflow 发生在了第三四步,故给出定义:
浏览器在解析页面时,根据 css 以及 js来计算并把相应的元素置于该出现的位置,这个过程就是 Reflow;
当元素的位置大小颜色确定后,浏览器会按照各自的属性进行绘制,这个过程就是 Repaint。故我们需要避免引发此类操作,
以此来提高渲染速度。
引起Repaint/Reflow的一些操作
Reflow 的成本比 Repaint 的成本高得多的多。DOM Tree 里的每个结点都会有 reflow 方法,一个结点的 reflow 很有可能导致子结点,甚至父点以及同级结点的 reflow。在一些高性能的电脑上也许还没什么,但是如果 reflow 发生在手机上,那么这个过程是非常痛苦和耗电的。 所以,下面这些动作有很大可能会是成本比较高的。
- 当你增加、删除、修改 DOM 结点时,会导致 Reflow 或 Repaint。
- 当你移动 DOM 的位置,或是搞个动画的时候。
- 当你修改 CSS 样式的时候。
- 当你 Resize 窗口的时候(移动端没有这个问题),或是滚动的时候。
- 当你修改网页的默认字体时。
注:display:none 会触发 reflow,而 visibility:hidden 只会触发 repaint,因为没有发现位置变化。
如何优化
Reflow是不可避免的,只能将Reflow对性能的影响减到最小,给出下面几条建议:
不要一条一条地修改 DOM 的样式。与其这样,还不如预先定义好 css 的 class,然后修改 DOM 的 className:
1234567// 不推荐的写法var left = 10,top = 10;el.style.left = left + "px";el.style.top = top + "px";// 推荐写法el.className += " theclassname";把 DOM 离线后修改。如:
a> 使用 documentFragment 对象在内存里操作 DOM。
b> 先把 DOM 给 display:none (有一次 repaint),然后你想怎么改就怎么改。比如修改 100 次,然后再把他显示出来。
c> clone 一个 DOM 节点到内存里,然后想怎么改就怎么改,改完后,和在线的那个的交换一下。- 不要把 DOM 节点的属性值放在一个循环里当成循环里的变量。不然这会导致大量地读写这个结点的属性。
- 尽可能的修改层级比较低的 DOM节点。当然,改变层级比较底的 DOM节点有可能会造成大面积的 reflow,但是也可能影响范围很小。
- 为动画的 HTML 元件使用 fixed 或 absoult 的 position,那么修改他们的 CSS 是会大大减小 reflow 。
- 尽量不要使用 table 布局。因为可能很小的一个小改动会造成整个 table 的重新布局。
css选择器
上面提到了Repaint和Reflow,以及如何优化的问题。我们知道浏览器工作流程第三步就是解析css然后计算元素位置并放置,那么如何从这点上来提高效率呢?这就要提到css的渲染效率了,下面我们来探讨下。
浏览器如何识别你的选择器
浏览器读取你的选择器,遵循的原则是由右向左读取,换句话讲,读取选择器会由右向左进行。举个栗子:
上述选择器,浏览器会尝试在你的 html 标签中寻找 p 元素,接着匹配 li 和 ul,最后再去匹配 div.header,所谓的由右到左就是这样。 选择器的最后一部分,也就是最右边的部分被称为 关键选择器,而它决定着你的选择器效率的高低。而关键选择器的作用就是提高选择器的效率,每少检查一个给定的规则,就会更有效的将样式匹配给对应的html元素。所以我们的方向就是让关键选择器更加有效性能化更高,那么如何实现呢?很简单,关键选择器越具体,性能化就越高。
选择器效率
如果你是个合格的前端开发者,对 css 选择器有一定了解的话,应该对那些选择器以及它的优先级不会太陌生。
来让我们看下 Steve Souders 大神给的优先级排序:
- id选择器 (#id)
- 类选择器 (.class)
- 标签选择器 (h1,div,p..)
- 相邻选择器 (h1+p)
- 子选择器 (div < span)
- 后代选择器 (div span)
- 通配符选择器 (*)
- 属性选择器 (a[rel=’’])
- 伪类选择器 (a:hover,li:nth-child(1))
上述选择器优先级按照降序排列,当然还有上面你没提到的行内样式和 !important,建议还是少用为妙,毕竟对于维护来讲太恶心。
书写高效的css选择器
来自Mozilla的几点建议(搬运工)
David在《Use efficient CSS selectors》中介绍了几种书写高效率的CSS选择器的方法:
- 避免普遍规则
- 不要在 ID 选择器前加标签名或类名
- 不要在类名选择器前加标签名
- 尽可能使用具体的类别
- 避免使用后代选择器
- 标签分类规则中不应该包含一个子选择器
- 子选择器的问题
- 借助相关继承关系
- 使用范围内的样式表
一些降低渲染资源消耗的实战经验
十六进制颜色值对位数与大小写
默认标准是大写以及 6 位数标注,建议书写规范,虽然未有确实数据表明不采用写法会对渲染速率有影响。display 与 visibility 差异
两者均用于设置或检索是否显示对象。前者隐藏对象且不保留物理空间,而后者会保存物理空间。浏览器渲染被占据的物理空间时,会有所消耗。建议采用 display:none;border:none; 与 border:0;区别
区别同上, border:0; 把 border 设为 “0” 像素虽然在页面上看不见,但按 border 默认值理解,浏览器依然对 border-width/border-color 进行了渲染,即已经占用了内存值。
border:none; 把 border 设为 “none” 即没有,浏览器解析 “none” 时将不作出渲染动作,即不会消耗内存值。 建议使用border:none;不宜过小的图片平铺
一张宽高 1px 的背景图片,虽然文件体积非常之小,但渲染宽高500px的板块需要重复平铺 2500 次。提高背景图片渲染效率跟图片尺寸及体积有关,最大的图片文件体积保持约 70KB。建议采用衡量适中体积及尺寸的背景图片IE的滤镜
IE的滤镜除了比较消耗资源外也有兼容性问题。当中有令 PNG 透明的滤镜,可采用 GIF 或 JPG 似透非透的办法来避免使用此滤镜。建议只在 IE6 应用 GIF 透明,因为 IE7 以上已经支持了 PNG 透明。通配符*{margin:0;padding:0;}
- 号通配符把所有标签都初始化一遍,浏览器的渲染消耗一定的资源。有部分在标签在不同浏览器上几乎无差异,或是某些已经不推荐使用的标签(因为你不会去用它),它们不需通配符要重新初始化一遍这样做能节省一点资源。
- 推荐方案(代替 reset ):normalize.css(如果没听说过自行百度)
不要添加额外的标签来描述 class 或 id
如果你有一个选择器是以 id 作为关键选择符,请不要添加多余标签名上去。因为 ID 是唯一的,你不要为了一个不存在的理由而降低了匹配的效率。- 不赞成 - button#backButton { }
- 不赞成 - .menu-left #newMenuIcon { }
- 建议用 - #backButton { }
- 建议用 - #newMenuIcon { }
尽量选择最特殊的类来存放选择器
降低系统效率的一个最大原因是我们在标签类中用了过多的选择符。通过添加 class 到元素,我们可以将类别进行再细分为 class 类,这样就不用为了一个标签浪费时间去匹配过多的选择符了。- 不赞成 - treeitem[mailfolder=”true”] > treerow > treecell { }
- 建议用 - .treecell-mailfolder { }
避免子孙选择符
子孙选择符是 CSS 中最耗资源的选择符。他真的是非常的耗资源,尤其是在选择器使用标签类或通用类的时候。很多情况中,我们真正想要的是子选择符。除非有明确说明,在 UI CSS 中是严禁使用子孙选择符的。- 不赞成 - treehead treerow treecell { }
- 好一点,但还是不行(参照下一条) - treehead > treerow > treecell { }
标签类中不要包含子选择符
不要在标签类中使用子选择符。否则,每次元素的出现,都会额外地增加匹配时间。(特别是当选择器似乎多半会被匹配的情况下)- 不赞成 - treehead > treerow > treecell { }
- 建议用 - .treecell-header { }
留意所有子选择符的使用
小心地使用子选择符。如果你能想出一个的不使用他的方法,那么就不要使用。特别是在 RDF 树和菜单会频繁地使用子选择符,像这样。- 不赞成 - treeitem[IsImapServer=”true”] > treerow > .tree-folderpane-icon { } 请记住 RDF 的属性是可以在模板中被复制的!利用这一点,我们可以复制那些想基于该属性改变的子 XUL 元素上的 RDF 属性。
- 建议用 - .tree-folderpane-icon[IsImapServer=”true”] { }.
暂时就这么多了,国庆在家没网,更个东西也是费死个老劲。