前端性能优化指标RAIL
直接看这个文章前端性能优化 - 用RAIL模型分析性能
前端性能优化手段
重排重绘
网页生成的时候,至少会渲染一次。在用户访问的过程中,还会不断触发重排(reflow)和重绘(repaint),重排和重绘都会消耗性能,其中重排会消耗比较多的性能,需尽量避免
重绘不一定会导致重排,重排一定会导致重绘;
重排(reflow)
概念:
当DOM的变化影响了元素的几何信息(元素的的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排
重排也叫回流,简单的说就是重新生成布局,重新排列元素
下面情况会发生重排:
- 页面初始渲染,这是开销最大的一次重排
- 添加/删除可见的DOM元素
- 改变元素位置
- 改变元素尺寸,比如边距、填充、边框、宽度和高度等
- 改变元素内容,比如文字数量,图片大小等
- 改变元素字体大小
- 改变浏览器窗口尺寸,比如resize事件发生时
- 激活CSS伪类(例如::hover)
- 设置 style 属性的值,因为通过设置style属性改变结点样式的话,每一次设置都会触发一次reflow
- 查询某些属性或调用某些计算方法:offsetWidth、offsetHeight等,除此之外,当我们调用 getComputedStyle方法,或者IE里的 currentStyle 时,也会触发重排,原理是一样的,都为求一个“即时性”和“准确性”。
重排影响的范围:
由于浏览器渲染界面是基于流式布局模型的,所以触发重排时会对周围DOM重新排列,影响的范围有两种:
- 全局范围:从根节点html开始对整个渲染树进行重新布局
- 局部范围:对渲染树的某部分或某一个渲染对象进行重新布局
把一个dom的宽高之类的几何信息定死,然后在dom内部触发重排,就只会重新渲染该dom内部的元素,而不会影响到外界
重绘
当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘
etg: color
background
border-style
等;
重排优化
-
减少重排范围:
- 尽可能在低层级的DOM节点上
- 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局。那么在不得已使用table的场合,可以设置table-layout:auto;或者是table-layout:fixed这样可以让table一行一行的渲染,这种做法也是为了限制reflow的影响范围
-
减少重排次数:
- 样式集中改变
- 分离读写操作
- 将 DOM 离线
- 使用
display:none
- 通过
documentFragment
创建一个 dom 碎片,在它上面批量操作 dom,操作完成之后,再添加到文档中,这样只会触发一次重排 - 复制节点,在副本上工作,然后替换它
- 使用
- 使用
absolute
或fixed
脱离文档流 - 优化动画
- 可以把动画效果应用到
position
属性为absolute
或fixed
的元素 - 启用GPU加速 GPU 硬件加速
- 可以把动画效果应用到
// 样式集中改变
// bad
var left = 10;
var top = 10;
el.style.left = left + "px";
el.style.top = top + "px";
// 当top和left的值是动态计算而成时...
// better
el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
// better
el.className += " className";
// 分离读写操作
// bad 强制刷新 触发四次重排+重绘
div.style.left = div.offsetLeft + 1 + 'px';
div.style.top = div.offsetTop + 1 + 'px';
div.style.right = div.offsetRight + 1 + 'px';
div.style.bottom = div.offsetBottom + 1 + 'px';
// good 缓存布局信息 相当于读写分离 触发一次重排+重绘
var curLeft = div.offsetLeft;
var curTop = div.offsetTop;
var curRight = div.offsetRight;
var curBottom = div.offsetBottom;
div.style.left = curLeft + 1 + 'px';
div.style.top = curTop + 1 + 'px';
div.style.right = curRight + 1 + 'px';
div.style.bottom = curBottom + 1 + 'px';
/*
* 根据上面的结论
* 将 2d transform 换成 3d
* 就可以强制开启 GPU 加速
* 提高动画性能
*/
div {
transform: translate3d(10px, 10px, 0);
}
白屏
SSR
服务端渲染,在服务端将渲染逻辑处理好,然后将处理好的HTML直接返回给前端展示,这样就可以解决白屏的问题,也可以解决seo的问题
预渲染
通过 webpack
的prerender-spa-plugin
编译应用中的静态页面,并将其输出到对应的索引目录
骨架屏
骨架屏的实现原理和预加载类似,都是利用了Puppeteer
爬取页面的功能,Puppeteer是chrome出的一个headlessChromenode库,提供了API可以抓取SPA并生成预渲染内容,和预加载不太一样的是骨架屏技术会在Puppeteer生成内容后,利用算法将生成的内容进行替换,生成骨架页面
大量图片加载优化
- 首屏图片优先加载,等首屏图片加载完全后再去加载非首屏图片。
- 对大部分图片,减少图片体积,减少网络开销,加快下载速率
动画性能
- 将动画设为绝对定位属性
- 开启硬件加速,尽量避免浏览器创建不必要的图形层
- transform
- opacity
- filter
- 尽量减少js动画,如需要,使用对性能友好的
requestAnimationFrame
window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行
回调函数执行次数通常是每秒60次,但在大多数遵循W3C建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配 即 屏幕支持的 FPS - 使用chrome performance工具调试动画性能
渲染合成层
JavaScript
:JavaScript实现动画效果,DOM元素操作等。
Composite(渲染层合并)
:对页面中 DOM 元素的绘制是在多个层上进行的。在每个层上完成绘制过程之后,浏览器会将所有层按照合理的顺序合并成一个图层,然后显示在屏幕上。对于有位置重叠的元素的页面,这个过程尤其重要,因为一旦图层的合并顺序出错,将会导致元素显示异常
合成层创建标准
什么情况下能使元素获得自己的层?虽然 Chrome的启发式方法(heuristic)随着时间在不断发展进步,但是从目前来说,满足以下任意情况便会创建层:
- 3D 或透视变换(perspective transform) CSS 属性
- 使用加速视频解码的 元素 拥有 3D
- (WebGL) 上下文或加速的 2D 上下文的 元素
- 混合插件(如 Flash)
- 对自己的 opacity 做 CSS动画或使用一个动画变换的元素
- 拥有加速 CSS 过滤器的元素
- 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在自己的层里)
- 元素有一个z-index较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)
合成层的优点:
- 有自己的绘图上下文,并且会开启硬件加速,有利于性能提升
- 合成层的位图,会交由
GPU
合成,比CPU
处理要快 - 当需要
repaint
时,只需要repaint
本身,不会影响到其他的层 - 对于
transform
和opacity
效果,不会触发layout
和paint
性能优化点:
- 提升动画效果的元素 合成层的好处是不会影响到其他元素的绘制,因此,为了减少动画元素对其他元素的影响,从而减少paint,我们需要把动画效果中的元素提升为合成层。 提升合成层的最好方式是使用 CSS 的
will-change
属性。从上一节合成层产生原因中,可以知道will-change
设置为opacity
、transform
、top
、left
、bottom
、right
可以将元素提升为合成层。 - 使用
transform
或者opacity
来实现动画效果, 这样只需要做合成层的合并就好了。 - 减少绘制区域 对于不需要重新绘制的区域应尽量避免绘制,以减少绘制区域,比如一个 fix 在页面顶部的固定不变的导航header,在页面内容某个区域 repaint 时,整个屏幕包括 fix 的 header 也会被重绘。而对于固定不变的区域,我们期望其并不会被重绘,因此可以通过之前的方法,将其提升为独立的合成层。减少绘制区域,需要仔细分析页面,区分绘制区域,减少重绘区域甚至避免重绘