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 加载完成

PWA

创建manifest.webmanifest文件

清单文件可以具有任何名称,但通常是manifest.webmanifest从根目录(您网站的顶级目录)来命名 和提供的。

{
  "short_name": "Weather",
  "name": "Weather: Do I need an umbrella?",
  "description": "Weather forecast information",
  "icons": [
    {
      "src": "/images/icons-192.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "/images/icons-512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ],
  "start_url": "/?source=pwa",
  "background_color": "#3367D6",
  "display": "standalone",
  "scope": "/",
  "theme_color": "#3367D6",
  "shortcuts": [
    {
      "name": "How's weather today?",
      "short_name": "Today",
      "description": "View weather information for today",
      "url": "/today?source=pwa",
      "icons": [{ "src": "/images/today.png", "sizes": "192x192" }]
    },
    {
      "name": "How's weather tomorrow?",
      "short_name": "Tomorrow",
      "description": "View weather information for tomorrow",
      "url": "/tomorrow?source=pwa",
      "icons": [{ "src": "/images/tomorrow.png", "sizes": "192x192" }]
    }
  ]
}

short_name或name

您必须至少提供short_name或name属性。如果同时提供了两者,short_name则在用户的主屏幕,启动器或其他空间可能受限的地方使用。name在安装应用程序时使用。

icons

对于Chrome,您必须至少提供192×192像素的图标和512×512像素的图标

要使用 可屏蔽图标(有时在Android上称为自适应图标),您还需要添加”purpose”: “any maskable”到 icon属性

start_url

并告诉在那里,当它启动你的应用程序应该启动浏览器

display

  • fullscreen 在没有任何浏览器UI的情况下打开Web应用程序,并占用了整个可用显示区域
  • standalone 打开Web应用程序,使其看起来和感觉都像一个独立的本机应用程序
  • minimal-ui 此模式类似于standalone,但是为用户提供了用于控制导航
  • browser

scope

注意: 如果用户单击应用程序中位于之外 scope的链接,则该链接将在现有PWA窗口中打开并呈现。如果要在浏览器选项卡中打开链接,则必须添加target=”_blank” 到 a 标签中。在Android设备上,与的链接target="_blank"将在Chrome自定义标签中打开

将网络应用清单添加到您的页面

标签添加到Progressive Web App的所有页面

<link rel="manifest" href="/manifest.webmanifest">

sendBeacon 信标传输

1. 用户卸载网页的时候,有时需要向服务器发一些数据。很自然的做法是在unload事件或beforeunload事件的监听函数里面,使用XMLHttpRequest对象发送数据。但是,这样做不是很可靠,因为XMLHttpRequest对象是异步发送,很可能在它即将发送的时候,页面已经卸载了,从而导致发送取消或者发送失败。

上报数据的方法
1. 我们可以将 ajax 请求改为同步方法,这样就能保证请求一定能发送到服务端。由于 fetch 及 axios 都不支持同步请求,所以需要通过 XMLHttpRequest 发送同步请求。这里要注意的是,将请求改为同步以后,会阻塞页面关闭或重新加载的过程,这样就会影响用户体验。
2. 动态图片
我们可以通过在 beforeunload 事件处理器中创建一个图片元素并设置它的 src 属性的方法来延迟卸载以保证数据的发送,因为绝大多数浏览器会延迟卸载以保证图片的载入,所以数据可以在卸载事件中发送。
[cc lang=”js”]const reportData = (url, data) => {
let img = document.createElement(‘img’);
const params = [];
Object.keys(data).forEach((key) => {
params.push(`${key}=${encodeURIComponent(data[key])}`);
});
img.onload = () => img = null;
img.src = `${url}?${params.join(‘&’)}`;[/cc]

此时服务端可以返回一个 1px * 1px 的图片,保证触发 img 的 onload 事件,但如果某些浏览器在实现上无法保证图片的载入,就会导致上报数据的丢失。
3. sendBeacon
为了解决上述问题,便有了 navigator.sendBeacon 方法,使用该方法发送请求,可以保证数据有效送达,且不会阻塞页面的卸载或加载,并且编码比起上述方法更加简单。
navigator.sendBeacon(url, data);

url 就是上报地址,data 可以是 ArrayBufferViewBlobDOMString 或 Formdata,根据官方规范,需要 request header 为 CORS-safelisted-request-header,在这里则需要保证 Content-Type 为以下三种之一:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

如果数据类型是 string,则可以直接上报,此时该请求会自动设置请求头的 Content-Type 为 text/plain。

[cc lang=”js”]const reportData = (url, data) => {
navigator.sendBeacon(url, data);
}[/cc]
如果用 Blob 发送数据,这时需要我们手动设置 Blob 的 MIME type,一般设置为 application/x-www-form-urlencoded。
[cc lang=”js”]const reportData = (url, data) => {
const blob = new Blob([JSON.stringify(data), {
type: ‘application/x-www-form-urlencoded’,
}]);
navigator.sendBeacon(url, blob);
};[/cc]
可以直接创建一个新的 Formdata,此时该请求会自动设置请求头的 Content-Type 为 multipart/form-data
[cc lang=”js”]const reportData = (url, data) => {
const formData = new FormData();
Object.keys(data).forEach((key) => {
let value = data[key];
if (typeof value !== ‘string’) {
// formData只能append string 或 Blob
value = JSON.stringify(value);
}
formData.append(key, value);
});
navigator.sendBeacon(url, formData);
};[/cc]

performance 前端性能监控

Performance是一个做前端性能监控离不开的API,最好在页面完全加载完成之后再使用,因为很多值必须在页面完全加载之后才能得到。最简单的办法是在window.onload事件中读取各种数据。

按触发顺序排列所有属性:(更详细标准的解释请参看:W3C Editor’s Draft)
navigationStart:在同一个浏览器上下文中,前一个网页(与当前页面不一定同域)unload 的时间戳,如果无前一个网页 unload ,则与 fetchStart 值相等
unloadEventStart:前一个网页(与当前页面同域)unload 的时间戳,如果无前一个网页 unload 或者前一个网页与当前页面不同域,则值为 0
unloadEventEnd:和 unloadEventStart 相对应,返回前一个网页 unload 事件绑定的回调函数执行完毕的时间戳
redirectStart:第一个 HTTP 重定向发生时的时间。有跳转且是同域名内的重定向才算,否则值为 0
redirectEnd:最后一个 HTTP 重定向完成时的时间。有跳转且是同域名内的重定向才算,否则值为 0
fetchStart:浏览器准备好使用 HTTP 请求抓取文档的时间,这发生在检查本地缓存之前
domainLookupStart:DNS 域名查询开始的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
domainLookupEnd:DNS 域名查询完成的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
connectStart:HTTP(TCP) 开始建立连接的时间,如果是持久连接,则与 fetchStart 值相等,如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接开始的时间
connectEnd:HTTP(TCP) 完成建立连接的时间(完成握手),如果是持久连接,则与 fetchStart 值相等,如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接完成的时间

注意:这里握手结束,包括安全连接建立完成、SOCKS 授权通过

secureConnectionStart:HTTPS 连接开始的时间,如果不是安全连接,则值为 0
requestStart:HTTP 请求读取真实文档开始的时间(完成建立连接),包括从本地读取缓存,连接错误重连时,这里显示的也是新建立连接的时间
responseStart:HTTP 开始接收响应的时间(获取到第一个字节),包括从本地读取缓存
responseEnd:HTTP 响应全部接收完成的时间(获取到最后一个字节),包括从本地读取缓存
domLoading:开始解析渲染 DOM 树的时间,此时 Document.readyState 变为 loading,并将抛出 readystatechange 相关事件
domInteractive:完成解析 DOM 树的时间,Document.readyState 变为 interactive,并将抛出 readystatechange 相关事件

!!!只是 DOM 树解析完成,这时候并没有开始加载网页内的资源

domContentLoadedEventStart:DOM 解析完成后,网页内资源加载开始的时间,文档发生 DOMContentLoaded事件的时间
domContentLoadedEventEnd:DOM 解析完成后,网页内资源加载完成的时间(如 JS 脚本加载执行完毕),文档的DOMContentLoaded 事件的结束时间
domComplete:DOM 树解析完成,且资源也准备就绪的时间,Document.readyState 变为 complete,并将抛出 readystatechange 相关事件
loadEventStart:load 事件发送给文档,也即 load 回调函数开始执行的时间,如果没有绑定 load 事件,值为 0
loadEventEnd:load 事件的回调函数执行完毕的时间,如果没有绑定 load 事件,值为 0

常用计算:

DNS查询耗时 :domainLookupEnd – domainLookupStart
TCP链接耗时 :connectEnd – connectStart
request请求耗时 :responseEnd – responseStart
解析dom树耗时 : domComplete – domInteractive
白屏时间 :responseStart – navigationStart
domready时间(用户可操作时间节点) :domContentLoadedEventEnd – navigationStart
onload时间(总下载时间) :loadEventEnd – navigationStart

now()

performance.now()是当前时间与performance.timing.navigationStart的时间差,以微秒(百万分之一秒)为单位的时间,与 Date.now()-performance.timing.navigationStart的区别是不受系统程序执行阻塞的影响,因此更加精准

chrome devtool

breakpoint

1 调试可以忽略代码

  1. Blackbox a script from the Editor pane :blackbox source 右击代码 blackbox script
  2. Blackbox a script from the Call Stack pane
  3. Blackbox提供了这个便利功能,当你把不需要调试的代码加入Blackbox时候,调试的时候便会自动绕过这些文件,断点也不会调入黑盒中
  4. restart frame : 右击 call stack里第一个函数 ,选择 restart frame 可以重新 debugger 函数
  5. breakpoint continue to here Add conditional breakpoint 设置条件断点
  6. xhr fetch breakpoints 添加 url 关键字, 有访问就会打端点
  7. Dom breakpoints Dom 属性改变 删除 内容改变 都可以打断点
  8. 在 scope 改 变量数据

performance

看每秒的帧数, 如果FPS 是红线说明 用户不到60FPS

解决问题

看summary 扇形图 看看哪个时间长 然后减少它的时间

  1. Expand the Main section. DevTools shows you a flame chart of activity on the main thread, over time. main 里每一个bar 代表一个事件,事件下面是当前bar的子事件
  2. Animation Frame Fired (估计每个screenshot 是一帧) 当你在main 里 看到 一个 动画帧里有红色三角行,就说明这个bar当前事件有警告, 点开mian 里的 bar 事件,然后点击 summary 的 reveal link, 再点击关联文件就到 source 里了
  3. 减少页面re-layout 重绘,用flex 布局
  4. 坚持仅合成器的属性和管理层计数
  • 坚持使用 transform 和 opacity 属性更改来实现动画。
  • 使用 will-change 或 translateZ 提升移动的元素。
  • 避免过度使用提升规则;各层都需要内存和管理开销。
.moving-element {
  will-change: transform;
}
.moving-element {
  transform: translateZ(0);
}

https://web.dev/rail/