性能优化——关键路径渲染优化

关键路径渲染优化


什么是优化关键路径

  • 优化关键路径的就是优先显示与用户操作相关的内容,也就是常说的尽量减少白屏时间或是减少首屏渲染时间。
    在这里插入图片描述
  • 好的页面交互,即使是在服务器处理或是资源还未完全返回期间,也应该尽量渲染部分信息给用户,而不是让用户明显感知过长白屏时间,以为页面卡死。例如Google时,在服务器处理搜索时及时渲染局部信息,而后逐步显示完整信息。
    在这里插入图片描述
  • 接下来,主要研究下从获取页面到渲染浏览器私底下都做了哪些东西。如下图,HTML解析成DOM树,JS可能生成或编辑DOM和编辑CSSOM,然后组成渲染树渲染在屏幕上。
    在这里插入图片描述
  • 浏览器渲染解析主要操作,解析HTML生成DOM,解析CSS生成CSSOM,有JS可能改变DOM、CSSOM,两种结合成渲染树layout,知道了元素的大小、位置、颜色、层次等信息浏览器就可以绘制图层组合图层渲染页面啦。
    在这里插入图片描述
  • 卡通图片更加直观。
    在这里插入图片描述

关键指标

宗旨:你不需要去优化你无法测量的东西

  1. 关键资源数量
  2. 关键资源字节大小
  3. 关键路径长度(RTT:网络往返次数)
  • 关键渲染路径考虑的时间维度一般是指发起请求到 DOMContentLoaded 这段时间。
    在这里插入图片描述
  • 性能监控首屏渲染信息获取上报通常使用 performance.getEntriesByType('navigation')[0] 获得。
{
    
    
    "name": "https://mp.csdn.net/mdeditor",
    "entryType": "navigation",
    "startTime": 0,
    "duration": 1929.0599999949336,
    "initiatorType": "navigation",
    "nextHopProtocol": "h2",
    "workerStart": 0,
    "redirectStart": 30.985000004875474,
    "redirectEnd": 258.7400000047637,
    "fetchStart": 258.7400000047637,
    "domainLookupStart": 258.7400000047637,
    "domainLookupEnd": 258.7400000047637,
    "connectStart": 258.7400000047637,
    "connectEnd": 258.7400000047637,
    "secureConnectionStart": 0,
    "requestStart": 262.220000004163,
    "responseStart": 316.87500000407454,
    "responseEnd": 319.18999999470543,
    "transferSize": 2113,
    "encodedBodySize": 1834,
    "decodedBodySize": 4430,
    "serverTiming": [],
    "unloadEventStart": 0,
    "unloadEventEnd": 0,
    "domInteractive": 1289.1050000034738,
    "domContentLoadedEventStart": 1289.9250000045868,
    "domContentLoadedEventEnd": 1296.4300000021467,
    "domComplete": 1928.7900000053924,
    "loadEventStart": 1928.859999999986,
    "loadEventEnd": 1929.0599999949336,
    "type": "navigate",
    "redirectCount": 1
}
  • 一般以 domComplete 完成时间为止,也就说这段时间影响渲染主要的资源和操作才是重点需要关注的。
    在这里插入图片描述
  • 好了有了大致的时间维度的观念后,然后接着分析浏览器做了什么。

构建对象

浏览器渲染页面需要DOM、CSSOM,所有应该尽快把HTML、CSS给到浏览器。

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
    <title>Critical Path</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
  </body>
</html>
  • 这里稍微提一下,<meta name="viewport" content="width=device-width,initial-scale=1"> 告诉浏览器视口宽度等于设备宽度,初始缩放1:1,去掉这段浏览器会默认窗口980px,导致页面缩放成很小,需要手动放大,很不友好。但页面没做适配或响应式布局设计的话,还不如不加这段,至少用户通过缩放可以看到想看的部位。
    在这里插入图片描述
body {
    
     font-size: 16px }
p {
    
     font-weight: bold }
span {
    
     color: red }
p span {
    
     display: none }
img {
    
     float: right }
  • 下面看出CSS规则会向下层叠,继承或覆盖规则。
    在这里插入图片描述

  • 和DOM构建不同,DOM可以逐步构建并结合完整的CSSOM渲染。DOM不能和部分CSSOM组成渲染树 ,你不能为了提前渲染而渲染出错误的样式信息给用户,宁愿不做也不能做错。这也就是为什么常听到优化点 CSS尽量放在头部尽快加载解析在这里插入图片描述

  • 字节 -> 字符 -> Token 令牌/标签/选择器 -> 对象模型

  • HTML标签转化成DOM,CSS选择器转化成CSSOM

  • DOM、CSSOM是两个完全独立的数据结构

  • CSS会阻塞渲染

  • render tree 保留需要渲染元素的位置、形状大小、颜色、层级等信息。
    在这里插入图片描述

打开Chrome 开发工具 performance 分析中可以看到(PS:即使HTML中没有style 或 link 样式标签,浏览器依然有默认样式。)。

  • Parse HTML 表示DOM构建过程
  • Recalculate Style 表示 CSSOM 构建过程
  • Layout 获取元素的位置、尺寸信息,更新渲染树信息。
  • Update Layer Tree 更新元素层级信息
  • Paint、Composite 转化成屏幕上的像素

优化关键渲染路径就是优化以上过程的总时间。

在这里插入图片描述

  • 一个典型的关键路径渲染顺序。PS:浏览器可能为了加速性能,在CSS未解析前,提前执行app.js内容,但执行到CSSOM的操作时会暂停JS引擎执行等待CSS加载构建完毕。
    在这里插入图片描述

分析关键路径渲染性能

1、无JS、CSS。链接(PS:以下图片只为举例说明原理,实际数据不一定对的上,包括浏览器更新,实际呈现未必一样。)

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Critical Path: No Style</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
  </body>
</html>

在这里插入图片描述
查看chrome 开发工具 -> network 。HTML文件很小,获取文件一次网络往返即可,服务器处理和网络传输大约200ms,DOM完全解析没有被阻塞仅仅花费了几毫秒。图片等某些资源并不会阻塞渲染、DOMContentLoaded 事件,但会影响 onLoad 事件 。(T0到T1的时间是网络往返和服务器处理时间)

  • DOMContentLoadedonLoad 区别,DOM解析完成之后会调用前者,这时对于JS来说,整个DOM元素都是可交互状态。后者是所有资源包括图片等下载完毕后会调用 onLoad
    在这里插入图片描述
  • 关键路径资源:1个(html)
  • 关键路径资源大小:5KB(html)
  • 关键路径长度:1次(获取html文件最少网络往返)

2、有 JS 和 CSS。链接

<!DOCTYPE html>
<html>
  <head>
    <title>Critical Path: Measure Script</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
  </head>
  <body onload="measureCRP()">
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
    <script src="timing.js"></script>
  </body>
</html>
  • 对比1,增加了 DOMContentLoaded 触发时间,和 onLoad 很接近了。CSS下载不会阻塞DOM解析,CSS、JS几乎同时下载。JS会阻塞DOM构建,外部脚本且要等待下载执行完后DOM才能继续解析。常见的优化是把脚本尽量紧邻</body>
    在这里插入图片描述
  • 关键路径资源:3个(html,js,css)
  • 关键路径资源大小:11KB(html,js,css)
  • 关键路径长度:2次(获取html文件最少1次。css和js并发下载取其中时间最长的,最少1次网络往返)
    在这里插入图片描述

3、内联脚本

虽然减少了网络资源请求,但没有获得太大提升,而且不利于公共代码分享和缓存。CSS样式下载解析完成之前会阻塞内联脚本的执行。

  • 关键路径资源:2个(html,css)
  • 关键路径资源大小:11KB(html+内联js,css)
  • 关键路径长度:2次(获取html文件最少1次。css最少1次网络往返)
    在这里插入图片描述

4、同时内联样式和脚本。链接

<!DOCTYPE html>
<html>
  <head>
    <title>Critical Path: Measure Inlined</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <style>
      p {
     
      font-weight: bold }
      span {
     
      color: red }
      p span {
     
      display: none }
      img {
     
      float: right }
    </style>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
    <script>
      var span = document.getElementsByTagName('span')[0];
      span.textContent = 'interactive'; // change DOM text content
      span.style.display = 'inline';  // change CSSOM property
      // create a new element, style it, and append it to the DOM
      var loadTime = document.createElement('div');
      loadTime.textContent = 'You loaded this page on: ' + new Date();
      loadTime.style.color = 'blue';
      document.body.appendChild(loadTime);
    </script>
  </body>
</html>
  • 无阻塞,但HTML文件变大,同样虽然减少了关键资源个数、HTTP请求,但不利于公共样式、脚本复用和缓存。
    在这里插入图片描述

5、有CSS,无JS

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
  </body>
</html>

这里再次说明外联样式会阻塞渲染树生成,CSS应该尽早解析生成CSSOM。

  • 关键路径资源:2个(html,css)
  • 关键路径资源大小:9KB(html,css)
  • 关键路径长度:2次(获取html文件最少1次。css最少1次网络往返)在这里插入图片描述

6、异步脚本 script

<!DOCTYPE html>
<html>
  <head>
    <title>Critical Path: Measure Async</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
  </head>
  <body onload="measureCRP()">
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
    <script async src="timing.js"></script>
  </body>
</html>
  • DOMContentLoaded 触发时间明显减少,async 表示脚本不阻塞DOM解析,下载完后安排执行。在这里HTML较轻量,DOM在脚本下载完成之前就解析完毕。等CSS下载解析完成就能合成渲染树了。
    在这里插入图片描述
  • 关键路径资源:2个(html,css)
  • 关键路径资源大小:9KB(html,css)
  • 关键路径长度:2次(获取html文件最少1次。css最少1次网络往返)
    在这里插入图片描述

7、media CSS、async JS

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet" media="print">
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
    <script src="app.js" async></script>
  </body>
</html>

<link href="style.css" rel="stylesheet" media="print"> 表示用于打印时渲染的样式。此时浏览器在非打印状态仍然会下载,但不会阻塞渲染树生成。这里由于 HTML DOM树结构较简单解析得比较快,async 脚本也没有阻塞当前DOM构建和渲染。

  • 关键路径资源:1个(html)
  • 关键路径资源大小:5KB(html)
  • 关键路径长度:1次(获取html文件最少1次)
    在这里插入图片描述

defer async 的区别

只对外联脚本有效

  • defer 下载时不阻塞 HTML 解析成 DOM,下载完成会在DOM解析完毕DOMContentLoaded 事件触发之前执行,并且保证脚本执行顺序。
  • async 下载时不阻塞 HTML 解析成 DOM,下载完毕后尽量安排JS执行。意思说执行时间不确定,早下载早执行。如果 HTML 文件复杂在脚本下载完成还未解析完毕,脚本可能会在 DOMContentLoaded 之前执行阻塞DOM构建。
    在这里插入图片描述
    在这里插入图片描述

Pre-loading vs Pre-fetching

1、pre-loading
预加载程序不会干等着 JS 执行阻塞 HTML 解析构建 DOM,会另起线程扫描并下载后面的资源。

  • 关键路径资源:4个(html,css,js x 2)
  • 关键路径长度:2次(获取html文件最少1次,css和2个JS并行下载计1次)
    在这里插入图片描述

2、pre-fetching

  • 预解析域名:
<link rel="dns-prefetch" href="other.hostname.com">
  • 告诉浏览器,即将使用资源,要求提高下载优先级:
<link rel="subresource" href="/some_other_resource.js">
  • 获取下个页面用到的资源:
<link rel="prefetch" href="/some_other_resource.jpeg">
  • 获取并渲染页面:
<link rel="prerender" href="//domain.com/next_page.html">

试题

通过以下执行记录,大家都能知道应用了那种方式吧。
在这里插入图片描述

总结

目的:学习HTML从服务器到浏览器后都发生些,知道原理的本质,优化时才能知道瓶颈所在。
在这里插入图片描述
优化:减少关键渲染路径资源个数、大小、长度

  1. 压缩资源减少资源字节数,服务器传输开启GZIP,减少网络传输往返次数和时间。
  2. 样式阻塞渲染。可以考虑内联非公共样式;设置media查询属性减少特定功能样式阻塞关键路径;减少 @import 方式引入,@import 只有下载解析后才会去获取样式文件,不能并发下载,影响性能。
  3. 非构建DOM脚本,使用 async 避免下载阻塞渲染,延迟脚本执行。

参考

视频课程

分析关键渲染路径性能

w3c performance

预加载

script async defer

Performance Optimization

Inside a super fast CSS engine: Quantum CSS (aka Stylo)

猜你喜欢

转载自blog.csdn.net/guduyibeizi/article/details/98731221