影响前端性能的本源——Reflow和Repaint

2,240 阅读7分钟

by LiDong from www.lidongtech.com/source-of-i…
本文可全文转载,但需得到原作者书面许可,同时保留原作者和出处。如果只是引流,请随意~

自从移动客户端飞速发展开始,前端工程师们也面临了一个非常重要的历史转折点。真是可以用一个短语来表示,风口上的猪。各种地方都在招聘前端工程师,工资动辄八千一万的。我们的春天来了……不过春天过后还是会有冬天……
风口的猪

在移动端的web开发工作中,经常遭遇到的问题就是客户反映,打开速度慢,这个说专业点就是性能变差。当接到这种投诉的时候自己心中也是一脸懵逼,在开发环境中测试的好好,怎么正式运行之后就慢了。心里虽然在考虑现象的真实原因,但在嘴上估计很多人第一时间就回答给客户是网速的问题而非自己程序的问题。

开销过大占用过多的计算资源就会导致性能问题,它出现的因素有很多,后端接口速度过慢,网速状态差……这些问题都是客观存在,不能否认不能回避也不能控制。那么前端工作中就没有影响性能问题的存在么?回答是肯定的。

浏览器是连接访问者和web页面的“桥梁”,浏览器渲染解析的效率就是决定页面“呈现出来”速度的关键因素,浏览器的渲染解析又会受到页面代码质量的影响。这一大圈又把责任饶到了我们自己身上,没关系,有责任也不会有人来追究的,我们需要的只是正视自己的工作。虽然我们嘴上可以把责任推给那些我们不能控制的因素,但是我们内心一定要认清楚,我们的工作对性能问题也是有影响的。

笔锋一转,我们再回来继续。我们都知道浏览器的对待页面的“姿势”是先布局后渲染。//布局的时间略微提前于渲染。浏览器在对页面经行布局和渲染的时候,就会面对两个很“矫情”的厉害家伙——ReflowRepaint。大神们对这俩货肯定不陌生了,我们这些小白面对这俩货的时候第一反应就是懵逼,然后就是不知所措,最后就是恐惧。虽然说在PC端ReflowRepaint对于性能的影响是微乎其微,但是在移动端这俩货简直就是性能杀手。
一脸懵逼

这俩货到底是干啥的,这么生猛?这个地方先系上一个扣,等一下下我们就解开。套用评书里的话就是“花开两支,各表一枝”。我们先来说说CSS动画的问题,CSS3中定义关于动画和过渡的属性,这两个小小的属性能让我们不用使用JS的情况下就能做出很炫的交互效果。这么猛的东西肯定要支付一定的代价了,代价就是浏览器性能。不行可以试试做一个超级复杂的动画,看看自己的浏览器卡顿不?另外JS关于DOM编程也让我们对页面DOM的操作更加顺畅便捷,这些便捷和顺畅也是在牺牲一定性能的情况获得的优势,比如一次性添加或者操作多个DOM元素,看看页面会不会“迟钝”。

我举得例子其实已经说明了这俩生猛的货的来路了。

Repaint就是“重绘”,它会在你改变 DOM 元素的视觉效果时进行,改变布局时不会触发。比如,opacity,background-color,visibilityoutline等都会触发,“重绘”的开销还是比较昂贵的,因为浏览器会在某一个DOM元素的视觉效果改变后去check这个DOM元素内的所有节点。

Reflow 就是“回流”,它的影响更大。它会在某一个DOM元素的位置发生改变后触发,而且它会重新计算所有元素的位置和在页面中的占有的面积,这样的话将会引起页面某一个部分甚至整个页面的重新渲染。改变某一个元素会影响它所有的子节点 (children)、祖先节点 (ancestors) 及兄弟节点(siblings)。

举个例子就能明白了这两个概念。譬如有一颗大树,季节变换了叶子也会从绿色变成黄色,这个就是Repaint。如果树枝或者其他什么部位被砍了或者插枝嫁接了,大树会自动恢复伤口或将新枝包裹成为自己的一部分,最后变成一个完整的大树。这个过程就类似Reflow。

Reflow和Repaint都是浏览器慢的元凶。用户和Web页面都不能在Reflow和Repaint执行时做任何操作和响应,而且在极端的情况下,CSS会拖慢JS的执行速度,这就是浏览器有的时候就是在操作之后没反应的原因之一。

在“两害相权取其轻,两利相权取其重”的思想照耀下,发现这俩货相比之下Reflow(回流)的开销更为昂贵,这么贵的东西我们肯定是会绕着走,什么情况下会触发Reflow?

  • 添加、删除或者改变DOM元素的可见性时:使用JS去改变DOM元素时会触发Reflow。
  • 添加、删除或者改变CSS样式:直接改变CSS Style或者元素的class可能会影响布局,还有改变一个元素的宽度能够影响它所在的DOM节点中的所有元素,以及它周围的那些元素。
  • CSS3 动画(animation)和过渡(transition): 动画的每一frame都会触发Reflow。
  • 使用offsetWidthoffsetHeight:这一点很特别,你读一个DOM的offsetWidthoffsetHeight属性同样会触发一下Reflow,因为这两个属性需要依赖一些元素去计算。
  • 用户交互:用户可以通过:hover一下<a>链接,在input里面输入文字,拖动浏览器的大小,改变字体大小,更换样式表或者字体等都会触发reflow。

既然我们知道了触发Reflow的条件了,我们发其道而行就能避免增加额外的资源开销。这个地方我就懒的自己去写了,我摘抄了其他一些同行总结的经验,做了一个信息拼合,大家将就看吧。

一些常用的提高性能的原则
方面 方法
布局 不要用 inline style 或 table 布局,flexbox 布局也会给性能带来一些小困扰。inline style 会在 html 下载完后进行一次额外的 Reflow,table布局的开销远比其他 DOM 元素的布局开销要大。flexbox 的 item 会在 HTML 下载完成后改变尺寸。
简写CSS 尽量简写CSS,避免使用复杂的CSS选择器,使用 Unused CSS https://unused-css.com/), uCSS(https://github.com/oyvindeh/ucss), gulp-uncss(https://github.com/ben-eb/gulp-uncss)可以有效的减少样式的定义和文件的大小。
优化DOM 减少DOM的层级,减少DOM的数量,如果不需适配老浏览器,删掉一些无用的wrapper性质的DOM元素,总之越少越好。
慎改class 在一个DOM树中,尽可能改那些没有特别多子元素DOM的class,子元素少的可以改,多的不推荐。
避免复杂动画 删掉复杂的动画,运用动画的元素尽量是position:absolute或position:fixed的,这样会让他们脱离文档流,不去影响其他的元素。
善用display:none display:none的元素不会引发Reflow和Repaint,可以在让这些元素在display之前进行一些诸如颜色、尺寸什么的改变。
批量更新元素 将状态写入一个类中,用JQ直接给元素添加或删除这个类,而不是直接通过方法来修改元素样式状态。
避免大量DOM互相影响 比如Tabs这种场景,如果你点击一个Tab会显示它控制的区块,显示的那个区块会影响其他的区块,这样可能会引起Reflow,因为它们的高度不一样,可以通过定个高度来优化这种场景。
性能永远比酷炫重要 记住一个原则,你网页的动画再牛逼,性能还是第一位的,如果每一帧移动1个像素会造成你的页面卡顿,那宁愿每一帧移动10像素让动画的帧变得迟钝一些,也不要让页面的性能降下来。