谷歌的数据表明,一个有10条数据0.4秒可以加载完的页面,在变成30条数据加载时间为0.9秒后,流量和广告收入减少了20%。当谷歌地图的首页文件大小从100kb减少到70~80kb时,流量在第一周涨了10%,接下来的三周涨了25%。
腾讯的前端工程师根据长期的数据监控也发现页面的一秒钟延迟会造成9.4%的PV的下降,8.3%跳出率的增加以及3.5%转化率的下降。
让用户在使用产品时有更快更舒适的浏览体验
Response:100ms内响应;
在用户注意到滞后之前,我们有 100 ms的时间可以响应用户输入。
对于需要超过 500 毫秒才能完成的操作,始终提供反馈,则用户不会陷入困惑。这也是一些交互设计师一直坚持处处操作有反馈的原因。
Animation:10ms内生成一帧;
在数学上来说,人眼感受到的帧数为60帧/s,则会认为是流畅的动画。
1s/60 = 1000ms/60 = 16ms/帧;
也就是说加上每一帧的预算是16ms,减去浏览器绘制帧的时间,留给我们的大约只有10ms/帧。 如果超过这个时间,用户眼中动画的流畅度就会降低。
Idle:最大程度增加空闲时间;
利用空闲的时间完成推迟的工作。
例如,尽可能减少预加载数据以便应用快速加载,并利用空闲时间加载剩余数据。推迟的工作应分成每个耗时约 50 毫秒的多个块。
如果用户开始交互,优先级最高的事项是响应用户。
Load:1000ms内呈现内容;
在 1 秒钟内网站加载完毕。
否则用户的注意力会分散,他们处理任务的感觉会中断。启用渐进式渲染和在后台执行一些工作。将非必需的加载推迟到空闲时间段。
用户评价性能延迟
0-16ms:用户可以感知每秒渲染 60 帧的平滑动画转场。也就是每帧 16 毫秒。留给应用大约 10 毫秒的时间来生成一帧。
0-100ms:在此时间窗口内响应用户操作,他们会觉得可以立即获得结果。时间再长,操作与反应之间的连接就会中断。
100-300ms:轻微可觉察的延迟
300-1000ms:延迟感觉像是任务自然和持续发展的一部分(用户觉得这是正常流,但不会觉得快)
1000+ms:用户的注意力将离开他们正在执行的任务。
10,000+ms:用户感到失望,可能会放弃任务;之后他们或许不会再回来。
评估每个资产的表现:其价值及其技术性能。
它提供给用户的价值能否抵消下载并显示它的开销?
是否能够评估并证明其价值?
该资源(特别是第三方资源)能否保持稳定的性能?
该资源是否处于或是否需要处于关键路径中?
如果该资源不可用,是否会影响网页的性能和用户体验?
不断地去对资源做检查,以给用户展示想看到的高性能、有价值信息。
——需要动画:gif
——不需要动画
————不需要高画质细节清晰:jpg
————需要高画质细节清晰
——————需要>256色图片:png24
——————不需要>256色:png8
优化gif图:http://www.lcdf.org/gifsicle/
优化jpg图:http://jpegclub.org/jpegtran/
png无损优化:http://optipng.sourceforge.net/
png有损优化:https://pngquant.org/
svg压缩:https://github.com/svg/svgo
web优化中的减少http请求数量,通过减少页面图片的数量来实现。
合并图片后,可以通过css的background-image、background-size、background-position属性定位使用单个图片。
合并主要用于图标和按钮等小而多的元素,复杂的图像尽量不合并,尤其是jpg格式。
logo和内容图片不要合并,不能破坏html本身的语义结构。
尽可能让颜色值相近的图片合并到同一张雪碧图里面。
空白也要占用空间,控制图片之前的空隙。
追求优化度可以手动合并,追求速度可以使用工具合并,例如cssGaga:http://www.99css.com/cssgaga/
不要用 inline style 或 table 布局,flexbox 布局也会给性能带来一些小困扰。inline style 会在 html 下载完后进行一次额外的 Reflow,table布局的开销远比其他 DOM 元素的布局开销要大。flexbox 的 item 会在 HTML 下载完成后改变尺寸。
尽量简写 CSS,避免使用复杂的 CSS 选择器,使用 Unused CSS, uCSS, gulp-uncss可以有效的减少样式的定义和文件的大小。
减少 DOM 的层级,减少 DOM 的数量,如果不需适配老浏览器,删掉一些无用的 wrapper 性质的 DOM 元素,总之越少越好。
display:none 的元素不会引发 Reflow 和 Repaint,可以在让这些元素在 display 之前进行一些诸如颜色、尺寸的改变。
批量去更新元素
避免大量 DOM 之间互相影响
性能优化中重要的指标——首次有效绘制(First Meaningful Paint,简称FMP)即指页面的首要内容出现在屏幕上的时间。这一指标影响用户看到页面前所需等待的时间,而内联首屏关键CSS(即Critical CSS,可以称之为首屏关键CSS)能减少这一时间。
压缩代码使用代码压缩工具压缩代码,去掉多余空格和换行等多余部分
将马上要用到的文件,放在页面头部加载。其他模块的CSS可以使用loadCSS 和 Preload待页面渲染完后异步加载
“阻塞渲染”仅是指浏览器是否需要暂停网页的首次渲染,直至该资源准备就绪。CSS 是阻塞渲染的资源。需要将它尽早、尽快地下载到客户端,以便缩短首次渲染的时间。
无论我们使用 标记还是内联 JavaScript 代码段,浏览器都会先暂停并执行脚本,然后才会处理剩余文档。不过,如果是外部 JavaScript 文件,浏览器必须停下来,等待从磁盘、缓存或远程服务器获取脚本,这就可能给关键渲染路径增加数十至数千毫秒的延迟。为此,我们可以将脚本标记为——异步:
向 script 标记添加异步关键字可以指示浏览器在等待脚本可用期间不阻止 DOM 构建,可以显著提升性能。
不要用 @import,@import会影响CSS的加载速度
在页面中添加dns-prefetch属性,告诉浏览器对指定域名预解析,进行预解析之后,用户在访问相应域名就不会再有延迟了,从而加快加载速度
浏览器在渲染一个页面时,会将页面分为很多个图层,图层有大有小,每个图层上有一个或多个节点。在渲染DOM的时候,浏览器所做的工作实际上是:
满足以下任意情况便会创建层:
满足基本条件后,达到下面的条件是才可能被提升为合成层:
合成层的位图,会交由 GPU 合成,比 CPU 处理要快;当需要 repaint 时,只需要 repaint 本身,不会影响到其他的层;
对于transform 和 opacity 效果,不会触发 layout 和 paint。
合成层的好处是不会影响到其他元素的绘制,可以减少动画元素对其他元素的影响,从而减少 paint。
使用 CSS 的 will-change 属性:will-change 设置为 opacity、transform、top、left、bottom、right 可以将元素提升为合成层。
对于那些目前还不支持 will-change 属性的浏览器(IE、Opera),目前常用的是使用一个 3D transform 属性来强制提升为合成层。
对于不需要重新绘制的区域应尽量避免绘制,以减少绘制区域。
比如一个 fix 在页面顶部的固定不变的导航 header,在页面内容某个区域 repaint 时,整个屏幕包括 fix 的 header 也会被重绘。
固定不变的区域,我们期望其并不会被重绘,因此可以将其提升为独立的合成层。减少绘制区域,需要仔细分析页面,区分绘制区域,减少重绘区域甚至避免重绘。
如果有一个元素,它的兄弟元素在复合层中渲染,而这个兄弟元素的z-index比较小,那么这个元素(不管是不是应用了硬件加速样式)也会被放到复合层中。最可怕的是,浏览器有可能给复合层之后的所有相对或绝对定位的元素都创建一个复合层来渲染。
使用3D硬件加速提升动画性能时,最好给元素增加一个z-index属性,人为干扰复合层的排序,可以有效减少chrome创建不必要的复合层,提升渲染性能,移动端优化效果尤为明显。
在内存资源有限的设备上,合成层带来的性能改善,可能远远赶不上过多合成层开销给页面性能带来的负面影响。
有些节点被改变时,会需要重新布局(这也意味着需要重新计算其他被影响的节点的位置和大小)。这种情况下,被影响的DOM树越大(可见节点),重绘所需要的时间就会越长,而渲染一帧动画的时间也相应变长,所以需要尽力避免这些属性。
Reflow 的成本比 Repaint 的成本高得多的多。一个结点的 Reflow 很有可能导致子结点,甚至父点以及同级结点的 Reflow 。在一些高性能的电脑上也许还没什么,但是如果 Reflow 发生在手机上,那么这个过程是延慢加载和耗电的。
一些常用的改变时会触发重布局的属性:
盒模型相关属性会触发重布局:
width
height
padding
margin
display
border-width
border
min-height
定位属性及浮动也会触发重布局:
position
float
clear
top
bottom
left
right
改变节点内部文字结构也会触发重布局:
text-align
vertival-align
overflow-y
overflow
font-weight
font-family
font-size
line-height
white-space
CSS3 动画(animation)和过渡(transition): 动画的每一 frame 都会触发 Reflow。
使用 offsetWidth 和 offsetHeight:读一个 DOM 的 offsetWidth 和 offsetHeight 属性同样会触发一下 Reflow,因为这两个属性需要依赖一些元素去计算。
修改时只触发重绘的属性有:
color
border-style
border-radius
visibility
text-decoration
background
background-image
background-position
background-repeat
background-size
outline-color
outline
outline-style
outline-width
box-shadow
这些属性都不会修改节点的大小和位置,自然不会触发重布局,但是节点内部的渲染效果进行了改变,所以只需要重绘就可以了。
尽可能的为产生动画的元素使用fixed或absolute的position(独立渲染层,脱离文档流,不去影响其他的元素)
通过GPU进行渲染从而使动画变得更加顺滑。
(translate、rotate、scale、opacity会获得GPU加速)
慎用硬件加速,虽然它带来的提升是显而易见的,但是其对内存的影响也是巨大的。GPU渲染会影响字体的抗锯齿效果,文本会变得模糊。
使用3D硬件加速提升动画性能时,最好给元素增加一个z-index属性,人为干扰复合层的排序,可以有效减少chrome创建不必要的复合层,提升渲染性能,移动端优化效果尤为明显
。
优先选择transform,尽量不要使用height,width,margin和padding,因为相较于其它属性transtion可以直接减少主线程的计算量。
Canvas作为浏览器提供的2D图形绘制API本身有一定的复杂度,优化的方法非常多,仅介绍几种比较主流的优化方式。
setTimeout 和 setInterval并非是专为连续循环产生的 API,使用setInterval定时器来完成js动画循环在单线程的js环境下并不可靠,并不能保证严格按照开发者的设置来进行动画循环,因此很多时候setInterval会引起掉帧的情况。因此requestAnimationFrame的优势就体现出来了:
简单动画每一帧擦除并重绘画布上所有内容是可取的操作,但如果背景比较复杂,那么可以使用剪辑区域clip,通过每帧较少的绘制来获得更好的性能。
离屏渲染的原理是把离屏 canvas当成一个缓存区。把需要重复绘制的画面数据进行缓存起来,减少调用 canvas的 API的消耗。
利用 canvas进行动画绘制时,如果计算出来的坐标是浮点数,可能会出现 CSS Sub-pixel的问题,也就是会自动将浮点数值四舍五入转为整数。在动画的过程中,由于元素实际运动的轨迹并不是严格按照计算公式得到,可能出现抖动的情况,同时也可能让元素的边缘出现抗锯齿失真。这也是可能影响性能的一方面,因为一直在做不必要的取证运算。
css的transform性能优于canvas的transform API,transform优先使用css。
如果使用画布不需要透明,当使用 HTMLCanvasElement.getContext() 创建一个绘图上下文时把alpha 选项设置为 false ,这个选项可以帮助浏览器进行内部优化
const ctx = canvas.getContext('2d', { alpha: false })
例如:
shadow相关 API,此类 API包括 shadowOffsetX、shadowOffsetY、shadowBlur、shadowColor;
绘图相关的 API,例如 drawImage、putImageData,在绘制时进行缩放操作也会增加耗时时间。
canvas也是通过操纵 js来绘制的,但是相比于正常的 js操作,调用 canvas API将更加消耗资源,所以在绘制之前做好规划,通过适量js原生计算减少canvas API的调用是一件比较划算的事情。
(如果减少一行 canvas API调用的代价是增加十行 js计算,那这事可能就没必要做了)
比如,作粒子效果时,尽量少使用圆,最好使用方形,因为粒子太小,所以方形看上去也跟圆差不多。至于原因,很容易理解,我们画一个圆需要三个步骤:先beginPath,然后用arc画弧,再用fill进行填充才能产生一个圆。但是画方形,只需要一个fillRect就可以了。虽然只是差了两个调用,当粒子对象数量达到一定时,这性能差距就会显示出来了。
渲染绘制的 api,例如 stroke()、fill、drawImage,都是将 ctx状态机里面的状态真实绘制到画布上,这种操作也比较耗费性能。
例如,如果你要绘制十条线段,那么先在 ctx状态机中绘制出十天线段的状态机,再进行一次性的绘制,这将比每条线段都绘制一次要高效得多
ctx可以看做是一个状态机,例如 fillStyle、globalAlpha、beginPath,这些API 都会改变 ctx里面对应的状态,频繁改变状态机的状态是影响性能的。
可以通过对操作进行更好的规划,减少状态机的改变,从而得到更加的性能,例如在一个画布上绘制几行文字,最上面和最下面文字的字体都是 30px,颜色都是 yellowgreen,中间文字是 20px pink,那么可以先绘制最上面和最下面的文字,再绘制中间的文字,而非必须从上往下依次绘制,因为前者减少了一次状态机的状态改变。
前端javascript是单线程工作的。如果有计算密集性、高延时的任务,前端通常会特定的调度机制通过settimeout、promise等实现,但是本质上还是在一个线程中进行工作,仍然会阻塞主Javascript线程,为了能够更好的执行,前端实现了一个web api接口—web worker。
在进行某些耗时操作,例如计算大量数据,一帧中包含了太多的绘制状态,大规模的 DOM操作等,可能会导致页面卡顿,影响用户体验Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。
(Worker无法读取主线程所在网页的 DOM 对象,也无法使用document、window、parent这些对象。但是可以navigator对象和location对象)
将一段大的任务过程分解成数个小型任务,使用定时器轮询进行,想要对一段任务进行分解操作,此任务需要满足以下情况:
Canvas性能优化 ——清夜(掘金)
再谈前端性能优化 ——白槐佳人(博客园)