Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
Godiswill opened this issue Aug 19, 2019 · 0 comments
Open

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

Godiswill opened this issue Aug 19, 2019 · 0 comments

Comments

@Godiswill
Copy link
Owner

Godiswill commented Aug 19, 2019

关键路径渲染优化

原文链接

什么是优化关键路径

  • 优化关键路径的就是尽早尽快加载解析与首屏相关的 JS、CSS,也就是常说的尽量减少白屏、灰屏时间和减少用户可交互时间。

progressive-rendering

  • 好的页面交互,即使是在服务器处理或是资源还未完全返回期间,也应该尽量渲染部分信息给用户,而不是让用户明显感知过长白屏时间,以为页面卡死。例如Google时,在服务器处理搜索时及时渲染局部信息,而后逐步显示完整信息。

  • 白屏 -> 灰屏渲染根节点(body设置了灰色的样式) -> FCP 渲染 body 内有意义的内容,如页头 -> FMP DOM节点增加最陡峭的那个点 -> 用户可交互 -> 资源加载完毕。

google-rendering

  • 接下来,主要研究下从获取页面到渲染浏览器私底下都做了哪些东西。如下图,HTML解析成DOM树,JS可能生成或编辑DOM和编辑CSSOM,然后组成渲染树渲染在屏幕上。

CRP

  • 浏览器渲染解析主要操作,解析HTML生成DOM,解析CSS生成CSSOM,有JS可能改变DOM、CSSOM,两种结合成渲染树,layout 知道了元素的大小、位置、颜色、层次等信息浏览器就可以绘制图层组合图层渲染页面啦。

render-path

  • 卡通图片更加直观。

cartoon

关键指标

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

  1. 关键资源数量
  2. 关键资源字节大小
  3. 关键路径长度(RTT:网络往返次数)
  • 关键渲染路径考虑的时间维度一般是指发起请求到 DOMContentLoaded 这段时间。

DOMContentLoaded

  • 性能监控首屏渲染信息获取上报通常使用 performance.timing 获得。
{
    "navigationStart": 1584067026326,
    "unloadEventStart": 1584067026357,
    "unloadEventEnd": 1584067026358,
    "redirectStart": 0,
    "redirectEnd": 0,
    "fetchStart": 1584067026331,
    "domainLookupStart": 1584067026331,
    "domainLookupEnd": 1584067026331,
    "connectStart": 1584067026331,
    "connectEnd": 1584067026331,
    "secureConnectionStart": 0,
    "requestStart": 1584067026331,
    "responseStart": 1584067026338,
    "responseEnd": 1584067026342,
    "domLoading": 1584067026392,
    "domInteractive": 1584067026755,
    "domContentLoadedEventStart": 1584067026755,
    "domContentLoadedEventEnd": 1584067026831,
    "domComplete": 1584067030218,
    "loadEventStart": 1584067030218,
    "loadEventEnd": 1584067030240
}
  • 白屏时间 First Paint Time = responseEnd - fetchStart
  • 灰屏时间 First ContentfulPaint
  • 首次可交互时间 Time to Interact = responseEnd - fetchStart

performance

  • 好了有了大致的时间维度的观念后,然后接着分析浏览器做了什么。

构建对象

浏览器渲染页面需要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,导致页面缩放成很小,需要手动放大,很不友好。但页面没做适配或响应式布局设计的话,还不如不加这段,至少用户通过缩放可以看到想看的部位。

DOM

body { font-size: 16px }
p { font-weight: bold }
span { color: red }
p span { display: none }
img { float: right }
  • 下面看出CSS规则会向下层叠,继承或覆盖规则。

CSSOM

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

css-tip2

  • 字节 -> 字符 -> Token 令牌/标签/选择器 -> 对象模型
  • HTML标签转化成DOM,CSS选择器转化成CSSOM
  • DOM、CSSOM是两个完全独立的数据结构
  • CSS会阻塞渲染
  • render tree 保留需要渲染元素的位置、形状大小、颜色、层级等信息

renderTree

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

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

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

parse

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

render-order

分析关键路径渲染性能

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>

no-js-css

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

  • DOMContentLoadedonLoad 区别,DOM解析完成之后会调用前者,这时对于JS来说,整个DOM元素都是可交互状态。后者是所有资源包括图片等下载完毕后会调用 onLoad

analysis-dom-no-css-js

  • 关键路径资源: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>

waterfall-dom-css-js

  • 关键路径资源:3个(html,js,css)
  • 关键路径资源大小:11KB(html,js,css)
  • 关键路径长度:2次(获取html文件最少1次。css和js并发下载取其中时间最长的,最少1次网络往返)

analysis-dom-css-js

3、内联脚本

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

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

waterfall-dom-css-js-inline

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请求,但不利于公共样式、脚本复用和缓存。

waterfall-dom-css-inline-js-inline

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次网络往返)

analysis-dom-css

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下载解析完成就能合成渲染树了。

waterfall-dom-css-js-async

  • 关键路径资源:2个(html,css)
  • 关键路径资源大小:9KB(html,css)
  • 关键路径长度:2次(获取html文件最少1次。css最少1次网络往返)

analysis-dom-css-js-async

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次)

analysis-dom-css-nb-js-async

defer async 的区别

只对外联脚本有效

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

async

async-render

Pre-loading vs Pre-fetching

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

  • 关键路径资源:4个(html,css,js x 2)
  • 关键路径长度:2次(获取html文件最少1次,css和2个JS并行下载计1次)

preLoading

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">

试题

通过以下执行记录,大家都能知道应用了那种方式吧。

which

总结

目的:学习HTML从服务器到浏览器后都发生些,知道原理的本质,优化时才能知道瓶颈所在。

better

优化:减少关键渲染路径资源个数、大小、长度

  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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant