• 再谈前端性能优化


     
    好的设计应该简单,而且始终可以提供最佳性能。
     

    一、优化原因

      谷歌的数据表明,一个有10条数据0.4秒可以加载完的页面,在变成30条数据加载时间为0.9秒后,流量和广告收入减少了20%。当谷歌地图的首页文件大小从100kb减少到70~80kb时,流量在第一周涨了10%,接下来的三周涨了25%。

    腾讯的前端工程师根据长期的数据监控也发现页面的一秒钟延迟会造成9.4%的PV的下降,8.3%跳出率的增加以及3.5%转化率的下降。

    可以看出,性能优化商业上来说很重要。

    但是,更重要的还是屏幕前我们的用户,让用户在使用产品时有更快更舒适的浏览体验,这算是一种前端工程师的自我修养。

     
     

    二、优化目标

    以让用户满意为目标呈现网页是终极的目标。当然页面速度要快,不过快只是构成满意的一个因素。

    我们使用Google团队提出的RAIL模型作为优化的目标。

    目前国内包括腾讯在内的团队都在使用这个评估方案。

    Response:100ms内响应;

    Animation:10ms内生成一帧;

    Idle:最大程度增加空闲时间;

    Load:1000ms内呈现内容;

     
     

    1)以用户为中心 User-centered

    用户是性能优化的中心,一切性能优化皆是为了用户获得更佳的体验。

    那么,我们的用户如何评价性能延迟:

     
    0-16ms 用户可以感知每秒渲染 60 帧的平滑动画转场。也就是每帧 16 毫秒
    留给应用大约 10 毫秒的时间来生成一帧。
    0-100ms 在此时间窗口内响应用户操作,他们会觉得可以立即获得结果。时间再长,操作与反应之间的连接就会中断。
    100-300ms 轻微可觉察的延迟
    300-1000ms 延迟感觉像是任务自然和持续发展的一部分(用户觉得这是正常流,但不会觉得快)
    1000+ms  (>1s) 用户的注意力将离开他们正在执行的任务。
    10,000+ms  (>10s) 用户感到失望,可能会放弃任务;之后他们或许不会再回来。
    注:1s=1000ms
     

    2)Response:100ms内响应

    用户大多数输入,不管他们是在点击按钮、切换表单控件还是启动动画,如果未得到响应,操作与反应之间的连接就会中断。用户会注意到这个延迟,这将会造成不完美的体验。

    在用户注意到滞后之前,我们有 100 ms的时间可以响应用户输入。

    对于需要超过 500 毫秒才能完成的操作,始终提供反馈,则用户不会陷入困惑。这也是一些交互设计师一直坚持处处操作有反馈的原因。

     

    3)Animation: 10ms内生成一帧

    在数学上来说,人眼感受到的帧数为60帧/s,则会认为是流畅的动画。

    1s/60 = 1000ms/60 = 16ms/帧;

    也就是说加上每一帧的预算是16ms,减去浏览器绘制帧的时间,留给我们的大约只有10ms/帧。 如果超过这个时间,用户眼中动画的流畅度就会降低。

     

    4)Idle:最大程度增加空闲时间

    利用空闲的时间完成推迟的工作。

    例如,尽可能减少预加载数据,以便您的应用快速加载,并利用空闲时间加载剩余数据。推迟的工作应分成每个耗时约 50 毫秒的多个块。如果用户开始交互,优先级最高的事项是响应用户。

     

    5)Load: 1000ms内呈现内容

    在 1 秒钟内网站加载完毕。

    否则用户的注意力会分散,他们处理任务的感觉会中断。启用渐进式渲染和在后台执行一些工作。将非必需的加载推迟到空闲时间段。

     

    总结

    上述的RAIL指标,我们可以用chrome自带的timeline工具检测:

     
    RAIL步骤 关键指标 用户操作
    Response 输入延迟时间(从按下到绘制)小于 100 毫秒。 用户点按按钮(例如打开导航)。
    Animation 每个帧的工作(从 JS 到绘制)完成时间小于 16 毫秒。 用户滚动页面,拖动手指或看到动画。
     拖动时,应用的响应与手指位置有关。
    Idle 主线程 JS 工作分成不大于 50 毫秒的块。 用户没有与页面交互,但主线程应足够用于处理下一个用户输入。
    Load 页面可以在 1000 毫秒内就绪。 用户加载页面并看到关键路径内容。
     
     
     

    三、优化方法

    1.加载性能优化

    1)优化内容效率

    评估每个资产的表现:其价值及其技术性能。

    网页往往会包含一些不必要的资源;或者更糟的是,包含的某些资源会影响网页性能,同时还无法给访问者或所处的网站带来太大价值。

     

    网页上一直包含资源 A(例如某个轮播图):

    它提供给用户的价值能否抵消下载并显示它的开销呢?

    是否能够评估并证明其价值?

    该资源(特别是第三方资源)能否保持稳定的性能?

    该资源是否处于或是否需要处于关键路径中?

    如果该资源不可用,是否会影响网页的性能和用户体验?

    不断地去对资源做检查,以给用户展示想看到的高性能、有价值信息。

     

    2)图片优化

    图片是网页中的必不可少的元素,烘托氛围,传达信息,作用很广泛。同时,图片也占据大量字节,有极大优化的潜力。

    2.1 减少图片资源

    是否真的需要用图片去表达,如果不需要可以尽量去减少使用。这是最直接的优化方式(嗯 我已经看见设计师提着刀过来了)。站在用户和对方的角度思考一下,相信是可以达到共识的。

     
    2.2选择合适的图片格式
    目前的图片大致可以分为两种:光栅图和矢量图。
     

    矢量图形使用线、点和多边形来表示图像,放大后仍然可以保持清晰,最适用于高分辨率屏幕和需要以不同尺寸显示的场景。

    光栅图形通过对矩形格栅内的每个像素的值进行编码来表示图像,适合场景丰富的情况。

    如果可以用css3等替代图片,那是极好的。

    如果不能被替代,可以根据场景复杂度进行选择;场景简单,用矢量图;场景复杂,用光栅图。

     

    光栅图有很多格式,常见而熟知的有gif,png,jpg,webp等,每种格式都有自己独特的编码方式。

     
    2.3深度优化

    现今电子设备分辨率层出不穷,对图片也有不一样的要求。

    CSS中1px实则是一个虚拟的相对像素,往往 1css像素 = ?设备像素。这里的问号根据分辨率不同有着多种情况。如下图:

    100 x 100 像素的图像由 10,000 个像素组成。浏览器为每个通道分配 256 个值(色阶),也就是每个通道 8 位 (2 ^ 8 = 256),每个像素 4 个字节(4 个通道 x 8 位 = 32 位 = 4 个字节)。

    10,000 个像素 x 4 个字节 = 40,000 个字节

    40,000 个字节/1024 = 39 KB

    由图可知,图片尺寸的增加,图片所占用的内存越大。物理屏幕的分辨率加倍,所需的像素数不只是加倍,而是增加到原来的四倍。

    因此,选择合适尺寸的图片对于性能很重要。为合适的对象选择合适尺寸从的图片是有利于性能优化的。

    上面提到,光栅图有多种图片格式,但是需要根据浏览器和需求选择合适的格式:

     

    GIF支持各种格式,但是画质不高。png支持透明度和所有浏览器但是体积较大。jpg不支持透明度和动画,但是支持所有浏览器而且体积小。后起新秀XR和Webp对于浏览器的支持不足,但是Webp体积小,比jpg还要小30%,可以考虑向下兼容。

    而png的可塑性也可圈可点,只要降低通道颜色的数量,体积可以小到惊喜。画质的改变见仁见智。

    从左至右 (PNG):32 位(256 色)、7 位(128 色)、5 位(32 色)

     

    国外前端从业者总结出这样的使用方法:

      需要动画:gif

      不需要动画

        需要高画质细节清晰

          需要>256色图片:png24

          不需要>256色:png8

        不需要高画质细节清晰:jpg

    【tips】

    图片优化指南

    优化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

     
    2.4提供缩放的图像

    提供多余像素的开销只会让浏览器代替我们重新缩放图像,减少并优化渲染网页所需总字节数的大好机会因此被错过。还要注意的是,尺寸调整不仅受图像缩减像素数的影响,还受其自然尺寸的影响。

     

    应确保多余像素数最少,并确保特别是较大资产以尽可能接近其显示尺寸的尺寸提供。

     
    2.5合并图像

    优化原理来于web优化中的减少http请求数量,通过减少页面图片的数量来实现。

    合并图片后,可以通过css的background-image、background-size、background-position属性定位使用单个图片。

    使用cssgaga合并后的图片
     

    合并图片后的图片总大小会变得更小,但是需要注意:

    a.合并主要用于图标和按钮等小而多的元素,复杂的图像尽量不合并,尤其是jpg格式。

    b.logo和内容图片不要合并,不能破坏html本身的语义结构。

    c.尽可能让颜色值相近的图片合并到同一张雪碧图里面。

    d.空白也要占用空间,控制图片之前的空隙。

    e.追求优化度可以手动合并,追求速度可以使用工具合并,例如cssGaga等。

     
     
    cssgaga目前使用的团队有:

    CSSGAGA的开发者是前腾讯的前端工程师,可以看出目前腾讯基本是使用了cssgaga 进行图片优化。

    关于cssgaga: http://www.99css.com/cssgaga/

     

    3)字体优化

    如果对字体进行优化,再通过制定明智的策略对字体在网页上的加载和应用方式作出规定,就可以帮助减小网页总大小,并缩短网页渲染时间.

    3.1选取字体

    重要的是考虑它支持的字符集。

    目前字体格式有四种:WOFF2、WOFF、EOT 和 TTF。

    WOFF2 许多浏览器都未支持
    WOFF 支持广泛,较旧的浏览器不支持
    EOT 只有IE支持
    TTF 部分IE支持

    所以字体的选用可以参照:

      • 将 WOFF 2.0 变体提供给支持它的浏览器。

      • 将 WOFF 变体提供给大多数浏览器。

      • 将 TTF 变体提供给旧 Android(4.4 版以下)浏览器。

      • 将 EOT 变体提供给旧 IE(IE9 版以下)浏览器。

     

    3.2压缩字体大小

    字体是字形的集合;

    每个字形都是一组描述字母形状的路径。

    各个字形不同,但它们仍然包含大量相似信息,这些信息可通过 GZIP 或兼容的压缩工具进行压缩。

    考虑使用 Zopfli 压缩处理 EOT、TTF 和 WOFF 格式。Zopfli 是一种兼容 zlib 的压缩工具,提供的文件大小压缩率比 gzip 高大约 5%。

     
    3.3 浏览器寻找字体顺序
    字体变体的指定顺序很重要。浏览器将选取其支持的第一种格式。
     

    如果希望较新的浏览器使用 WOFF2,则应将 WOFF2 声明置于 WOFF 之上,依此类推。

     
    3.4 字体渲染

    字体请在css里面,所以将远远滞后于其他关键资源请求的处理,并且在获取资源之前,可能会阻止浏览器渲染文本。

      • Safari 会在字体下载完成之前延迟文本渲染。

      • Chrome 和 Firefox 会将字体渲染暂停最多 3 秒,之后他们会使用一种后备字体。并且字体下载完成后,他们会使用下载的字体重新渲染一次文本。

      • IE 会在请求字体尚不可用时立即使用后备字体进行渲染,然后在字体下载完成后进行重新渲染。

     
     
     

    4)关键渲染路径优化

    从收到 HTML、CSS 和 JavaScript 字节到对其进行必需的处理,从而将它们转变成渲染的像素这一过程中有一些中间步骤,优化性能其实就是了解这些步骤中发生了什么 - 即关键渲染路径。

    4.1 对象模型

    HTML 标记转换成文档对象模型 (DOM);CSS 标记转换成 CSS 对象模型 (CSSOM)。DOM 和 CSSOM 是独立的数据结构。

     
     
     
    字节 → 字符 → 标记 → 节点 → 对象模型
     
     
    dom树
     

    浏览器根据字节 → 字符 → 标记 → 节点 → 对象模型的顺序,逐步创建了dom树。

     
     
    4.2 CSS对象模型

    浏览器构建页面的 DOM 时,在文档的 head 部分遇到了一个 link 标记,该标记引用一个外部 CSS 样式表:style.css。由于预见到需要利用该资源来渲染页面,它会立即发出对该资源的请求,并返回样式的内容:

     

    与处理 HTML 时一样,我们需要将收到的 CSS 规则转换成某种浏览器能够理解和处理的东西。因此,我们会重复 HTML 过程,不过是为 CSS 而不是 HTML。

    cssdom树

    页面上的任何对象计算最后一组样式时,浏览器都会先从适用于该节点的最通用规则开始(例如,如果该节点是 body 元素的子项,则应用所有 body 样式),然后通过应用更具体的规则(即规则“向下级联”)以递归方式优化计算的样式。所以,cssdom树有树的形状。

    每个浏览器都提供一组默认样式(也称为“User Agent 样式”),即我们不提供任何自定义样式时所看到的样式。我们的样式只是替换这些默认样式。

    DevTools 中记录时间线并寻找“Recalculate Style”事件:与 DOM 解析不同,该时间线不显示单独的“Parse CSS”条目,而是在这一个事件下一同捕获解析和 CSSOM 树构建,以及计算的样式的递归计算。

     
    4.3 渲染树建设、布局和绘制

    CSSOM 树和 DOM 树合并成渲染树,然后用于计算每个可见元素的布局,并输出给绘制流程,将像素渲染到屏幕上。

    4.3.1 DOM和CSSOM合并为渲染树

    遍历网页上所有可见的(除了display:none) DOM 内容,以及每个节点的所有 CSSOM 样式信息。

    过程:

    从 DOM 树的根节点开始遍历每个可见节点;

    对于每个可见节点,为其找到适配的 CSSOM 规则并应用它们;

    发射可见节点,连同其内容和计算的样式。

     

    注: visibility: hidden 与 display: none 是不一样的。前者隐藏元素,但元素仍占据着布局空间(即将其渲染成一个空框),而后者 (display: none) 将元素从渲染树中完全移除,元素既不可见,也不是布局的组成部分。

     

     

     
    4.3.2 布局(自动重排)
    我们计算每个对象在设备视口内的确切位置和大小---这就是“布局”阶段。

    布局流程的输出是一个“盒模型”,它会精确地捕获每个元素在视口内的确切位置和尺寸:所有相对测量值都转换为屏幕上的绝对像素。

     
    4.3.3 绘制(栅格化)
    将渲染树中的每个节点转换成屏幕上的实际像素。
     
      • “Layout”事件在时间线中捕获渲染树构建以及位置和尺寸计算。
      • 布局完成后,浏览器会立即发出“Paint Setup”和“Paint”事件,将渲染树转换成屏幕上的像素

    执行渲染树构建、布局和绘制所需的时间将取决于文档大小、应用的样式,以及运行文档的设备:文档越大,浏览器需要完成的工作就越多;样式越复杂,绘制需要的时间就越长。

     
    4.3.4 渲染

    关键渲染路径要求我们同时具有 DOM 和 CSSOM 才能构建渲染树。这会给性能造成严重影响:HTML 和 CSS 都是阻塞渲染的资源。 HTML 显然是必需的,因为如果没有 DOM,我们就没有可渲染的内容,但 CSS 的必要性可能就不太明显。

    右侧的情况通常称为“内容样式短暂失效”(FOUC)
     
    CSS 是阻塞渲染的资源。需要将它尽早、尽快地下载到客户端,以便缩短首次渲染的时间。

    第一个声明阻塞渲染,适用于所有情况。

    第二个声明同样阻塞渲染:“all”是默认类型,如果您不指定任何类型,则隐式设置为“all”。因此,第一个声明和第二个声明实际上是等效的。

    第三个声明具有动态媒体查询,将在网页加载时计算。根据网页加载时设备的方向,portrait.css 可能阻塞渲染,也可能不阻塞渲染。

    最后一个声明只在打印网页时应用,因此网页首次在浏览器中加载时,它不会阻塞渲染。

    “阻塞渲染”仅是指浏览器是否需要暂停网页的首次渲染,直至该资源准备就绪。无论哪一种情况,浏览器仍会下载 CSS资源,只不过不阻塞渲染的资源优先级较低罢了。

     
    4.3.5 使用js增加交互性

    我们的脚本在文档的何处插入,就在何处执行。

    当 HTML 解析器遇到一个 script 标记时,它会暂停构建 DOM,将控制权移交给 JavaScript 引擎;等 JavaScript 引擎运行完毕,浏览器会从中断的地方恢复 DOM 构建,也就延缓了首次渲染。

    “优化关键渲染路径”在很大程度上是指了解和优化 HTML、CSS 和 JavaScript 之间的依赖关系谱。

    无论我们使用 <script> 标记还是内联 JavaScript 代码段,浏览器都会先暂停并执行脚本,然后才会处理剩余文档。不过,如果是外部 JavaScript 文件,浏览器必须停下来,等待从磁盘、缓存或远程服务器获取脚本,这就可能给关键渲染路径增加数十至数千毫秒的延迟。

    为此,我们可以将脚本标记为——异步:

     
    向 script 标记添加异步关键字可以指示浏览器在等待脚本可用期间不阻止 DOM 构建,这样可以显著提升性能。
     
     
    4.3.6 评估关键渲染路径

    每个可靠性能策略的基础,准确的评估和检测必不可少。

    Lighthouse 是一个网络应用审核工具,可以对特定页面运行一系列测试,然后在汇总报告中显示页面的结果。

     
    也可以安装成浏览器插件使用。
     

    2.渲染性能优化

     

    2.1 优化js执行

    测量 JavaScript 开销和性能情况的最佳方法是使用 Chrome DevTools

    如果发现有长时间运行的 JavaScript,则可以在 DevTools 用户界面的顶部启用 JavaScript 分析器:

     
     
    有了这些信息之后,可以评估 JavaScript 对应用性能的影响,并开始找出和修正函数运行时间过长的热点。如前所述,应当设法移除长时间运行的 JavaScript,或者若不能移除,则将其移到 Web Worker 中,腾出主线程继续执行其他任务。
     
     

    2.2 避免大型复杂的布局

    与样式计算相似,布局开销的直接考虑因素如下:

    需要布局的元素数量。

    这些布局的复杂性。

     
    2.2.1尽可能避免布局操作

    当您更改样式时,浏览器会检查任何更改是否需要计算布局,以及是否需要更新渲染树。对“几何属性”(如宽度、高度、左侧或顶部)的更改都需要布局计算。

     
     
     
     
    2.2.2 使用 flexbox 而不是较早的布局模型
     
    1,300 个框上使用浮动的布局开销
     使用 Flexbox(Web 平台的新模型)
     
     

    2.3 Composite:渲染层合并

    利用合成层对于提升页面性能方面有很大的作用。

    合成层的位图,会交由 GPU 合成,比 CPU 处理要快;当需要 repaint 时,只需要 repaint 本身,不会影响到其他的层;对于 transform 和 opacity 效果,不会触发 layout 和 paint。

     
    2.3.1合成层好处
    a.提升动画效果

    合成层的好处是不会影响到其他元素的绘制,可以减少动画元素对其他元素的影响,从而减少 paint。

    使用 CSS 的 will-change 属性:

    will-change 设置为 opacity、transform、top、left、bottom、right 可以将元素提升为合成层。

     
     
    对于那些目前还不支持 will-change 属性的浏览器,目前常用的是使用一个 3D transform 属性来强制提升为合成层。
    使用 Timeline 来确认新的合成层是否真的改进了渲染性能。不能盲目提升合成层,一定要分析其实际性能表现。
     
    b.减少绘制区域

    对于不需要重新绘制的区域应尽量避免绘制,以减少绘制区域。

    比如一个 fix 在页面顶部的固定不变的导航 header,在页面内容某个区域 repaint 时,整个屏幕包括 fix 的 header 也会被重绘。

    固定不变的区域,我们期望其并不会被重绘,因此可以将其提升为独立的合成层。减少绘制区域,需要仔细分析页面,区分绘制区域,减少重绘区域甚至避免重绘。
    c.合理管理合成层

    创建一个新的合成层并不是免费的,它得消耗额外的内存和管理资源。实际上,在内存资源有限的设备上,合成层带来的性能改善,可能远远赶不上过多合成层开销给页面性能带来的负面影响。

    下面的例子中,创建了2000个相同的div元素,第一个未提升为合成层,第二个提升为合成层:

     
    内存消耗结果显示:提升为合成层后反而消耗更多。
     
    2.3.2合成层原理
    一个 Web 页面的展示,简单来说可以认为经历了以下下几个步骤。
     
    对页面中 DOM 元素的绘制是在多个层上进行的。在每个层上完成绘制过程之后,浏览器会将所有层按照合理的顺序合并成一个图层,然后显示在屏幕上。

    DOM 树中得每个 Node 节点都有一个对应的 LayoutObject ;

    一般来说,拥有相同的坐标空间的 LayoutObjects,属于同一个PaintLayer,特殊情况会为一些特殊的 LayoutObjects 创建一个新的渲染层;

    合成层拥有单独的 GraphicsLayer,而其他不是合成层的渲染层,则和其第一个拥有 GraphicsLayer 父层公用一个。

     
     
    PaintLayer为 SelfPaintingLayer时可以提升为合成层,也就是必须满足以下之一的条件才能提升为合成层。
      • 根元素(HTML)
      • 有明确的定位属性(relative、fixed、sticky、absolute)
      • 透明的(opacity 小于 1)
      • 有 CSS 滤镜(fliter)
      • 有 CSS mask 属性
      • 有 CSS mix-blend-mode 属性(不为 normal)
      • 有 CSS transform 属性(不为 none)
      • backface-visibility 属性为 hidden
      • 有 CSS reflection 属性
      • 有 CSS column-count 属性(不为 auto)或者 有 CSS column-width 属性(不为 auto)
      • 当前有对于 opacity、transform、fliter、backdrop-filter 应用动画

    满足基本条件后,达到下面的条件是才可能被提升为合成层:

    硬件加速的 iframe 元素(比如 iframe 嵌入的页面中有合成层);

    video 元素;

    对 opacity、transform、fliter、backdropfilter 应用了 animation 或者 transition;

    will-change 设置为 opacity、transform、top、left、bottom、right。

    在实际使用时可以参考这些做法。

     

    四、优化检测

    检测性能的工具目前比较流行的是chrome浏览器的开发者工具,使用广泛,这里就不再赘述。主要介绍两款插件:lighthouse和pagespeed。

    测试页面使用最近的一个落地页。

     

    1.lighthouse

    lighthouse是chrome浏览器的一个扩展插件,安装简单。
     
    安装完毕后右上角有这个标志:
     
     
    点开测试页面,点击lighthouse图标:
     
    大约7s后会自动打开一个报告页面:

    这里看到perfomance是86分(我们只关心在这里的性能结果)

    点击查看详细的性能报告:

    打开的时间以及造成延迟的原因都会详细的标识。
    查看标红的需要优化的部分:
     

    可以看到原因是由于两张较大的图片引起的。

    并且给出建议:使用webp图片,可以更快的打开页面。

    十分详尽,值得参考。

     

    2.pagespeed

    pagespeed也是chrome浏览器的一个扩展插件。

    这个验证需要已经可以公共互联网访问的页面,所以选用了一个已经上可以公共访问的地址。

     
    安装成功后右上角有这个标志:
     
    点开测试页面,点击pagespeed图标:

    进入分析阶段,pagespeed比lighthouse的用时稍微久一些。

    得到大致结果后,可以点击“ need more " 查看详细:

    可以得到十分详尽的优化建议和已经做好的优化。点开其中的一个优化措施:

     

     

    这是对于图片优化的一个建议,精确到每一张图片。十分有效。

    以上就是推荐的两款插件,使用方便,结果清晰准确。

     
     

    总结

    性能优化的意义在于给用户提供舒适快速的网页浏览体验。

    通过RAIL目标,结合加载和渲染性能优化,用检测工具加以监测和修正,以达到最佳的性能。

     
     
     
     
     
     
     
     

     

     

     

  • 相关阅读:
    JavaScript函数节流与函数去抖
    AngularJS 中文资料+工具+库+Demo 大搜集
    一个意想不到的Javascript内存泄漏
    mac添加Chromedriver
    selenium3+python自动化1——input标签上传文件
    python笔记1——xml文件的创建,读写,与增删改查
    C# 单例模式
    java的byte数组转换成在[0,255]范围内
    【转载】Stack Overflow: The Architecture
    C# 号码归属地查询算法(根据Android来电归属地二进制文件查询修改)
  • 原文地址:https://www.cnblogs.com/cherryblossom/p/7866324.html
Copyright © 2020-2023  润新知