浏览器渲染各个阶段

First Paint

浏览器从白屏第一次展现内容

FP发生在body中第一个script脚本之前的CSS解析和JS执行完成之后。换句话说就是第一脚本之前的DOM和CSSOM准备就绪之后,便会着手渲染第一脚本前的内容。

如果第一脚本前的JS和CSS加载完了,body中的脚本还未下载完成,那么浏览器就会利用构建好的局部CSSOM和DOM提前渲染第一脚本前的内容(触发FP);如果第一脚本前的JS和CSS都还没下载完成,body中的脚本就已经下载完了,那么浏览器就会在所有JS脚本都执行完之后才触发FP。

DocumentLoad

First Contentful paint

First Meaningful paint

Largest Contentful paint

相关参考

  • First Paint 和 DOMContentLoaded、load事件的触发没有绝对的关系,FP可能在他们之前,也可能在他们之后,这取决于影响他们触发的因素的各自时间(FP:第一脚本前CSSOM和DOM的构建速度;DOMContentLoaded:HTML文档自身以及HTML文档中所有JS、CSS的加载速度;load:图片、音频、视频、字体的加载速度)
  • DOMContentLoadedload事件也没有强制的先后顺序,DOMContentLoaded一般在load事件之前触发,但也可能在load事件之后触发。
  • 默认情况下,CSS外链之间是谁先加载完成谁先解析,但是JS外链之间即使先加载完成,也得按顺序执行。
  • link外链后面紧跟script外链,须先等link parse完成之后,script才会执行,即使script先下载完成。script后面紧跟link,也是一样,会等script执行完之后,link才会parse。
  • 如果script之后紧跟几个link且script比这几个link的下载时间都长,那script执行完成之后link是按顺序执行。
  • RRDL
    • R:send Request,发送资源请求
    • R:receive Response,接收到服务端响应
    • D:receive Data,开始接受服务端数据(一个资源可能执行多次)
    • L:finish Loading,完成资源下载
  • 浏览器在RRDL的时候,在D(Receive data)这个步骤可能执行多次。
  • TTFB: Time To First Byte,第一个字节返回的时间,这个是对应send Request到receive Response这段时间。
  • 浏览器会给HTML中的资源文件进行等级分类(Hightest /High /Meduim/ Low/ Lowest),一般HTML文档自身、head中的CSS都是Hightest,head中JS一般是High,而图片一般是Low,而设置了async/defer的脚本一般是Low,gif图片一般是Lowest。

reference Link

https://www.xmetal.cc/?p=1473‎(opens in a new tab)

DevTools network

选择 network tool

Capture screenshots during page load

  • network tool 勾选 Capture screenshots, 然后 reload
  • 将鼠标悬停在屏幕截图上可以查看捕获该屏幕截图的时间点。竖黄线出现在waterfall 对应的时间
  • 单击屏幕快照的缩略图以过滤掉捕获屏幕快照后发生的所有请求。

上图可以看出,

  • 第一屏展示会在app.9f73f4d2.css, 这个css 明显阻塞了第一屏渲染(first paint), 其他css js 虽然提前下载了但是加了 preload 或prefetch 不阻塞渲染
  • html 最后 的 vendors 和 app js 加载解析完成后 接着 DOMContentLoaded 事件完成, 加载完成到DOMContentLoaded 事件触发 是 Render Tree 构建过程

其中一种方法 防止解析器阻止

<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>
没使用async

JavaScript 可能会查询 CSSOM, 所以在加载 link stylesheet 解析css 时会阻止后面的js 执行, DOMContentLoaded 事件 就需要等待link完全加载才触发

使用async

效果好多了!解析 HTML 之后不久即会触发 domContentLoaded 事件;浏览器已得知不要阻止 JavaScript,并且由于没有其他阻止解析器的脚本,CSSOM 构建也可同步进行了。 不需要等到css 加载完成

重排 优化

下面是一些简单的准则,可帮助您尽可能缩短在网页中进行重排的用时:

  1. 减少不必要的 DOM 深度。在 DOM 树中的一个级别进行更改可能会致使该树的所有级别(上至根节点,下至所修改节点的子级)都随之变化。这会导致花费更多的时间来执行重排。
  2. 尽可能减少 CSS 规则的数量,并移除未使用的 CSS 规则。
  3. 如果您想进行复杂的渲染更改(例如动画),请在流程外执行此操作。您可以使用 position-absolute 或 position-fixed 来实现此目的。
  4. 避免使用不必要且复杂的 CSS 选择器(尤其是后代选择器),因为此类选择器需要耗用更多的 CPU 处理能力来执行选择器匹配。

css加载会造成阻塞吗

  1. css加载不会阻塞DOM树的解析
  2. css加载会阻塞DOM树的渲染
  3. css加载会阻塞后面js语句的执行

默认情况下,CSS 被视为阻塞渲染的资源,这意味着浏览器将不会渲染任何已处理的内容,直至 CSSOM 构建完毕。请务必精简您的 CSS,尽快提供它,并利用媒体类型和查询来解除对渲染的阻塞。

我们可以通过 CSS“媒体类型”和“媒体查询”来解决这类用例:

<link href="print.css" rel="stylesheet" media="print">
//,它只在打印内容时适用---或许您想重新安排布局、更改字体等等,因此在网页首次加载时,该样式表不需要阻塞渲染。
<link href="other.css" rel="stylesheet" media="(min-width: 40em)">
//样式表声明提供由浏览器执行的“媒体查询”:符合条件时,浏览器将阻塞渲染,直至样式表下载并处理完毕。

优化 CSS 发送过程

  1. 使用CDN(因为CDN会根据你的网络状况,替你挑选最近的一个具有缓存内容的节点为你提供资源,因此可以减少加载时间)
  2. 合理的使用缓存(设置cache-control,expires,以及E-tag都是不错的)
  3. 如果外部 CSS 资源较小,您可将它们直接插入到 HTML 文档中,这称为“内嵌样式”
  4. 如果 CSS 文件较大,您便需要确定和内嵌用于呈现首屏内容的 CSS,并暂缓加载其余样式,直到首屏内容显示出来为止。

如果原生的html 是这样

<html>
  <head>
    <link rel="stylesheet" href="small.css">
  </head>
  <body>
    <div class="blue">
      Hello, world!
    </div>
  </body>
</html>

那么把 本页用到的写class 写到 style, 其余的 延后加载

// inline critical CSS as follows:
<html>
  <head>
    <style>
      .blue{color:blue;}
    </style>
    </head>
  <body>
    <div class="blue">
      Hello, world!
    </div>
    <noscript id="deferred-styles">
      <link rel="stylesheet" type="text/css" href="small.css"/>
    </noscript>
    <script>
      var loadDeferredStyles = function() {
        var addStylesNode = document.getElementById("deferred-styles");
        var replacement = document.createElement("div");
        replacement.innerHTML = addStylesNode.textContent;
        document.body.appendChild(replacement)
        addStylesNode.parentElement.removeChild(addStylesNode);
      };
      var raf = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
          window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
      if (raf) raf(function() { window.setTimeout(loadDeferredStyles, 0); });
      else window.addEventListener('load', loadDeferredStyles);
    </script>
  </body>
</html>

不要写 行内样式

例如 <p style=…>

 Further, inline CSS on HTML elements is blocked by default with Content Security Policy (CSP)

为什么不完全用 内嵌样式

内嵌样式 使html 变得很大 浪费网络资源,而且没有缓存, 浪费宽带

web PageSpeed 优化

google 建议 资源下载管理

webpagetest 网站优化

网站各种报告 google console

自动化测试 speed

如果您只是尝试使用PageSpeed Insights API,则不需要API密钥。如果您计划以自动化方式使用API​​并每秒进行多个查询,则需要一个API密钥。

https://developers.google.com/speed/docs/insights/v5/get-started

  1. 打开一个终端。
  2. 运行以下命令。如果您使用的是API密钥,则追加。 &key=yourAPIKey
  3. 响应是一个JSON对象
curl https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=https://developers.google.com

首屏优化

组件化分治思想:

  • 将各模块拆分为组件粒度
  • 将组件依赖的资源全部封装在组件内部进行调用

加载优先级

  • 优先加载首屏可见模块
  • 其余不可见模块懒加载,待可见或即将可见时加载

如何解决判断可见性问题?

从前我们都是通过监听滚动事件、resize 事件来判断模块是否可见,代码不仅繁琐,而且一不小心没有函数去抖就又可能导致严重的性能问题。

现在我们有了更好的选择—— IntersectionObserver API ,IntersectionObserver 允许你配置一个回调函数,每当 target ,元素和设备视口或者其他指定元素发生交集的时候该回调函数将会被执行。这个 API 的设计是异步的,而且保证你的回调执行次数是非常有限的,而且回调是会在主线程空闲时才执行,在性能方面表现更优,使用起来也更简单。

如何尽可能懒的条件渲染?

在解决了加载条件的判断之后,我们需要解决加载条件为假的情况下不去渲染、加载条件为真的时候才渲染的问题,这里的答案非常简单:使用 Vue.js 提供的 v-if 指令,就可以做到真正的惰性渲染。

如果可见后进行初始渲染,可见前如何显示?

用户体验比较差,最开始是白屏,然后突然又渲染出现内容
最致命的是我们判断可见性是需要一个目标来观察的,如果什么不都渲染,我们就无从观察。

这里引入一个骨架屏的概念,我们为真实的组件做一个在尺寸、样式上非常接近真实组件的组件,叫做骨架屏。

如何提升切换时的体验?

如何提升切换时的体验?
在真实组件开始渲染的时候,需要一定的时间和空间,时间指的是真实组件从创建到渲染的时间,包括请求接口、请求资源和渲染的时间,空间指的是页面布局中需要给真实组件留出刚好的位置,避免产生抖动。

这里我们可以使用 Vue.js 内置的 transition 组件自定义骨架组件和真实组件的进入和离开效果,通过合理的布局和定位,减少切换时的抖动,https://github.com/xunleif2e/vue-lazy-component