前端性能优化体系搭建

1,427 阅读16分钟

概览

  • Lighthouse性能指标
  • performance面板
  • Lighthouse优化
  • 性能优化分类
    • js的加载和执行
    • 浏览器渲染层面
    • 网络
    • DOM操作
    • 框架
    • 构建5

1. Lighthouse性能指标

  • lighthouse是一个开源的自动化工具,用于改进网络应用的质量
  • Lighthouse报告分析了加载页面生命周期中的各种性能指标

image.png

指标名称解释
FPFirst-Paint 首次渲染表示浏览器从开始请求网站到屏幕渲染第一个像素点的时间
FCPFirst-Contentful-Paint 首次内容渲染表示浏览器渲染出第一个内容的时间,这个内容可以是文本、图片或SVG元素等等,不包括iframe和白色背景的canvas元素
SISpeed Index 速度指数表明了网页内容的可见填充速度
LCPLargest Contentful Paint 最大内容绘制标记了渲染出最大文本或图片的时间
TTITime to Interactive 可交互时间页面从开始加载到主要子资源完成渲染,并能够快速、可靠的响应用户输入所需的时间
TBTTotal Blocking Time 总阻塞时间测量 FCP 与 TTI 之间的总时间,这期间,主线程被阻塞的时间过长,无法作出输入响应
FIDFirst Input Delay 首次输入延迟测量加载响应度的一个以用户为中心的重要指标
CLSCumulative Layout Shift 累积布局偏移测量的是整个页面生命周期内发生的所有意外布局偏移中最大一连串的布局偏移分数
DCLDOMContentLoaded当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完成加载
LLoad检测一个完全加载的页面,页面的html、css、js、图片等资源都已经加载完之后才会触发 load 事件

image.png

1.1 FP和FCP

  • First Paint(首次渲染) 表示了浏览器从开始请求网站到屏幕渲染第一个像素点的时间
  • First Contentful Paint(首次内容渲染) 表示浏览器渲染出第一个内容的时间,这个内容可以是文本、图片或SVG元素等,不包括iframe和白色背景的canvas元素

1.1.1 记录FP和FCP

  • new PerformanceObserver(XXXX).observe({ type: "paint", buffered: true });
  • const entries = entryList.getEntries() || [];
  • entry.name === "first-paint"
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>LightHouse</title>
  </head>
  <body>
    <div>LightHouse</div>
    <script>
      function perf() {
        var data = {
          url: window.location.href,
          FP: 0,
          FCP: 0,
        };
        new PerformanceObserver(function (entryList) {
          const entries = entryList.getEntries() || [];
          entries.forEach(function (entry) {
            if (entry.name === "first-paint") {
              data.FP = entry.startTime;
              console.log("记录FP: " + data.FP);
            } else if (entry.name === "first-contentful-paint") {
              data.FCP = entry.startTime;
              console.log("记录FCP: " + data.FCP);
            }
          });
        }).observe({ type: "paint", buffered: true });
      }
      if (
        document.readyState === "complete" ||
        document.readyState === "interactive"
      ) {
        perf();
      } else {
        document.addEventListener("readystatechange", function () {
          if (document.readyState === "complete") {
            perf();
          }
        });
      }
    </script>
  </body>
</html>

1.1.2 改进FP和FCP

主要是网络层面

  • 加快服务器响应速度
    • 升级服务器配置
    • 合理设置缓存
    • 优化数据库索引
  • 加大服务器带宽
  • 服务器开启 gzip 压缩
  • 开启服务器缓存(redis)
  • 避免重定向操作
  • 使用 dns-prefetch 进行DNS进行预解析
  • 采用 域名分片技术 突破同域6个TCP连接限制或者采用HTTP2
  • 使用 CDN 减少网络跳转
  • 压缩 JS和CSS和图片等资源
  • 减少HTTP请求,合并JS和CSS,合理内嵌JS和CSS

1.2 SI

  • Speed Index(速度指数)表明了网页内容的可见填充速度
  • 速度指数衡量页面加载期间内容的视觉显示速度

是一种衡量页面可视区域加载速度的指标。它只关注页面的可视区域(下图为同一网络,不同分辨率的得分情况,意思是可视区域大小会影响评分)。

image.png

1.2.1 如何改进SI

1.2.1.1 最小化主线程工作
1.2.1.2 减少js执行时间
1.2.1.3 确保文本在 webfont 加载期间保持可见
  • 确保文本在 webfont 加载期间保持可见
  • 字体通常是需要一段时间才能加载的大文件。一些浏览器在字体加载之前隐藏文本,导致不可见文本 (FOIT) 闪烁。
  • 通过包含font-display: swap在您的@font-face风格中,您可以在大多数现代浏览器中避免 FOIT
@font-face {
  font-family: 'Pacifico';
  font-style: normal;
  font-weight: 400;
  src: local('Pacifico Regular'), local('Pacifico-Regular'), url(https://fonts.gstatic.com/s/pacifico/v12/FwZY7-Qmy14u9lezJ-6H6MmBp0u-.woff2) format('woff2');
  font-display: swap;
}
1.2.1.4 web worker
// 主线程代码
function start() {
    const worker = new Worker('/worker.js');
    worker.postMessage(100000000); 
    worker.addEventListener('message', function (event) {
        console.log('sum:', event.data);
    });
}
// worker线程
self.addEventListener('message', function (event) {
    let total = 0;
    for (let i = 0; i < event.data; i++) {
        total += i;
    }
    self.postMessage(total);
});
1.2.1.5 避免强制同步布局和布局抖动

image.png

强制同步布局

  • 改变一个元素的特性或者修改其内容有时不仅影响该元素,有时候会导致级联的变化,可能影响该元素的子节点、兄弟节点、父节点的改变,所以每次进行修改时,浏览器都必须重新计算这些改变的影响
  • 如果我们编写的代码不能让浏览器有足够的时间和空间来进行优化,强制浏览器执行大量重新计算,就会造成布局抖动
接口对象属性名
ElementclientHeight, clientLeft, clientTop, clientWidth, focus, getBoundingClientRect, getClientRects, innerText, offsetHeight, offsetLeft, offsetParent, offsetTop, offsetTop, offsetWidth, outerText, scrollByLines, scrollByPages, scrollLeft, scrollHeight, scrollIntoView, scrollIntoViewIfNeeded, scrollTop, scrollWidth
MouseEventlayerX layerY offsetX offsetY
WindowgetComputedStyle scrollBy scrollTo scroll scrollY
Frame,Document,Imageheight width
  • 每次修改DOM,浏览器必须在读取任何布局信息之前先重新计算布局,对性能的损耗十分巨大
  • 避免布局抖动的一种方法就是使用不会导致浏览器重排的方式编写代码 比如批量的读取和写入等

1.3 LCP

最大内容绘制,指页面上最大的图片或文字绘制的时间点。以下两张图中最大元素分别是 Instagram 标志和 Google 搜索页面中的一段文本,因此 LCP 为这两个元素加载的时间点。

image.png

image.png

最大内容绘制 (LCP) 是测量感知加载速度的一个以用户为中心的重要指标

1.3.1 记录LCP

new PerformanceObserver(function (entryList) {
  var entries = entryList.getEntries() || [];
  entries.forEach(function (entry) {
    if (entry.startTime > data.LCP) {
      console.log("记录LCP: " + (data.LCP = entry.startTime));
    }
  });
}).observe({ type: "largest-contentful-paint", buffered: true });

1.3.2 改进LCP

<link rel="preload" as="style" href="css/style.css">

1.4 TTI

Time to Interactive(可交互时间)指标测量页面从开始加载到主要子资源完成渲染,并能够快速、可靠地响应用户输入所需的时间

计算方式:

  1. 从 FCP 开始
  2. 找到第一个连续 5s 安静窗口(安静窗口指没有长任务且不超过两个 Get 请求)
  3. 从安静窗口反推,找到最后一个长任务
  4. 这个长任务结束的时间点就是 TTI

image.png

虽然 TTI 可以在实际情况下进行测量,但我们不建议这样做,因为用户交互会影响您网页的 TTI,从而导致您的报告中出现大量差异。如需了解页面在实际情况中的交互性,您应该测量First Input Delay 首次输入延迟 (FID)

1.4.1 改进TTI

1.5 TBT

total Blocking Time(总阻塞时间) 指标测量First Contentful Paint 首次内容绘制 (FCP)与Time to Interactive 可交互时间 (TTI)之间的总时间,这期间,主线程被阻塞的时间过长,无法作出输入响应

计算方式:

总阻塞时长,指的是 FCP 与 TTI 之间主线程被阻塞的总时长。

单个长任务阻塞时间 = 长任务时间 - 50ms,TBT 为这些阻塞时间的总和。

例如,下图为主线程任务执行的时间轴:

image.png

时间轴上有五个任务,其中三个是长任务,因为这些任务的持续时间超过 50ms。下图显示了各个长任务的阻塞时间:

image.png

因此,虽然在主线程上运行任务的总时间为 560ms,但其中只有 345ms 被视为阻塞时间(TBT)。

虽然 TBT 可以在实际情况下进行测量,但我们不建议这样做,因为用户交互会影响您网页的 TBT,从而导致您的报告中出现大量差异。如需了解页面在实际情况中的交互性,您应该测量First Input Delay 首次输入延迟 (FID)

1.5.1 如何改进TBT

1.6 FID

  • 首次输入延迟 (FID) 是测量加载响应度的一个以用户为中心的重要指标
  • 因为该项指标将用户尝试与无响应页面进行交互时的体验进行了量化,低 FID 有助于让用户确信页面是有效的
  • 首次输入延迟 (FID) 指标有助于衡量您的用户对网站交互性和响应度的第一印象
  • FID 测量从用户第一次与页面交互(例如当他们单击链接、点按按钮或使用由 JavaScript 驱动的自定义控件)直到浏览器对交互作出响应,并实际能够开始处理事件处理程序所经过的时间

1.6.1 记录FID

new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    const FID = entry.processingStart - entry.startTime;
    console.log("FID:", FID, entry);
  }
}).observe({ type: "first-input", buffered: true });

1.6.2 改进 FID

1.7 CLS

  • CLS 是测量视觉稳定性的一个以用户为中心的重要指标
  • CLS 测量整个页面生命周期内发生的所有意外布局偏移中最大一连串的布局偏移分数

1.7.1 记录CLS

new PerformanceObserver((entryList) => {
  var entries = entryList.getEntries() || [];
  entries.forEach(function (entry) {
    console.log("entry", entry);
    if (!entry.hadRecentInput) {
      data.CLS += entry.value;
      console.log("CLS: " + data.CLS);
    }
  });
}).observe({ type: "layout-shift", buffered: true });

1.7.2 如何改进CLS

  • 始终在您的图像和视频元素上包含尺寸属性
  • 首选转换动画,而不是触发布局偏移的属性动画
  • 除非是对用户交互做出响应,否则切勿在现有内容的上方插入内容

2. performance面板

image.png

2.1 核心流程

2.1.1 导航阶段

事件含义
beforeunload事件触发于 window、document 和它们的资源即将卸载时
navigationstart相同的浏览环境下卸载前一个文档结束之时
pagehide当浏览器在显示与会话历史记录不同的页面的过程中隐藏当前页面时, pagehide(页面隐藏)事件会被发送到一个Window
visibilitychange当浏览器的某个标签页切换到后台,或从后台切换到前台时就会触发该消息
unload当文档或一个子资源正在被卸载时, 触发 unload事件
unloadEventEnd事件处理程序结束之时
send request发送请求
receive data接收响应
commitNavigationEnd提交本次导航结束
domLoading解析器开始工作时

2.1.2 解析HTML阶段

事件含义
receive data接收数据
complete loading完成加载
parseHTML解析HTML
recalculateStyle重新计算样式
layout布局
update layer tree更新图层树
paint绘制
rasterGPU光栅化
compositor复合图层
display显示
dominteractive主文档的解析器结束工作时
readystatechangeinteractive(可交互)
domContentLoadedEventStart所有的需要被运行的脚本已经被解析之时
DOMContentLoaded当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完全加载
domContentLoadedEventEnd这个时刻为所有需要尽早执行的脚本不管是否按顺序,都已经执行完毕
domComplete主文档的解析器结束工作
readystatechangecomplete(完成)
loadEventStartload事件被现在的文档触发之时
load当整个页面及所有依赖资源如样式表和图片都已完成加载时,将触发load事件
loadEventEndload事件处理程序被终止
pageshow当一条会话历史记录被执行的时候将会触发页面显示(pageshow)事件

3. Lighthouse优化

3.1 减少未使用的 JavaScript

  • 请减少未使用的 JavaScript,并等到需要使用时再加载脚本,以减少网络活动耗用的字节数

3.2 采用新一代格式提供图片

  • WebP 和 AVIF 等图片格式的压缩效果通常优于 PNG 或 JPEG,因而下载速度更快,消耗的数据流量更少

3.3 适当调整图片大小

  • 提供大小合适的图片可节省移动数据网络流量并缩短加载用时

3.4 推迟加载屏幕外图片

  • 建议您在所有关键资源加载完毕后推迟加载屏幕外图片和处于隐藏状态的图片,从而缩短可交互前的耗时

3.5 移除阻塞渲染的资源

  • 资源阻止了系统对您网页的首次渲染。建议以内嵌方式提供关键的 JS/CSS,并推迟提供所有非关键的 JS/样式

3.6 减少未使用的 CSS

  • 请从样式表中减少未使用的规则,并延迟加载首屏内容未用到的 CSS,以减少网络活动耗用的字节数

3.7 使用视频格式提供动画内容

  • 使用大型 GIF 提供动画内容会导致效率低下。建议您改用 MPEG4/WebM 视频(来提供动画)和 PNG/WebP(来提供静态图片)以减少网络活动消耗的字节数

3.8 预先连接到必要的来源

  • 建议添加 preconnect 或 dns-prefetch 资源提示,以尽早与重要的第三方来源建立连接

3.9 应避免向新型浏览器提供旧版JavaScript

  • Polyfill 和 transform 让旧版浏览器能够使用新的 JavaScript 功能。不过,其中的很多函数对新型浏览器而言并非必需。对于打包的 JavaScript,请采用现代脚本部署策略,以便利用 module/nomodule 功能检测机制来减少传送到新型浏览器的代码量,同时保留对旧版浏览器的支持

3.10 确保文本在网页字体加载期间保持可见状态

  • 利用 font-display 这项 CSS 功能,确保文本在网页字体加载期间始终对用户可见

3.11 未使用被动式监听器来提高滚动性能

  • 建议您将触摸和滚轮事件监听器标记为 passive,以提高页面的滚动性能,passive不会对事件的默认行为说 no

3.12 图片元素没有明确的width和height

  • 请为图片元素设置明确的宽度值和高度值,以减少布局偏移并改善 CLS

3.13 注册“unload”事件监听器

  • unload事件不会可靠地触发,而且监听该事件可能会妨碍系统实施“往返缓存”之类的浏览器优化策略。建议您改用pagehidevisibilitychange事件

3.14 最大限度地减少主线程工作

  • 建议您减少为解析、编译和执行 JS 而花费的时间。您可能会发现,提供较小的 JS 负载有助于实现此目标

3.15 采用高效的缓存策略提供静态资源

  • 延长缓存期限可加快重访您网页的速度

3.16 缩短 JavaScript 执行用时

  • 建议您减少为解析、编译和执行 JS 而花费的时间。您可能会发现,提供较小的 JS 负载有助于实现此目标

3.17 避免链接关键请求

  • 下面的关键请求链显示了以高优先级加载的资源。请考虑缩短链长、缩减资源的下载文件大小,或者推迟下载不必要的资源,从而提高网页加载速度

3.18 请保持较低的请求数量和较小的传输大小

  • 若要设置页面资源数量和大小的预算,请添加 budget.json 文件

3.19 最大内容渲染时间元素

  • 这是此视口内渲染的最大内容元素

3.20 请避免出现大幅度的布局偏移

  • 这些 DOM 元素对该网页的 CLS 影响最大

3.21 应避免出现长时间运行的主线程任务

  • 列出了主线程中运行时间最长的任务,有助于识别出导致输入延迟的最主要原因

3.22 避免使用未合成的动画

  • 未合成的动画可能会卡顿并增加 CLS

3.23 缩减 CSS

  • 缩减 CSS 文件大小可缩减网络负载规模

3.24 缩减 JavaScript

  • 如果缩减 JavaScript 文件大小,则既能缩减负载规模,又能缩短脚本解析用时

3.25 对图片进行高效编码

  • 如果图片经过了优化,则加载速度会更快,且消耗的移动数据网络流量会更少

3.26 启用文本压缩

  • 对于文本资源,应先压缩(gzip、deflate 或 brotli),然后再提供,以最大限度地减少网络活动消耗的字节总数

3.27 初始服务器响应用时较短

  • 请确保服务器响应主文档的用时较短,因为这会影响到所有其他请求的响应时间

3.28 避免多次网页重定向

  • 重定向会在网页可加载前引入更多延迟

3.29 预加载关键请求

  • 建议使用 <link rel=preload> 来优先提取当前在网页加载后期请求的资源

3.30 使用 HTTP/2

  • HTTP/2 提供了许多优于 HTTP/1.1 的益处,包括二进制标头和多路复用

3.31 请移除 JavaScript 软件包中的重复模块

  • 从软件包中移除重复的大型 JavaScript 模块可减少网络传输时不必要的流量消耗

3.32 预加载 LCP 元素所用图片

  • 请预加载 Largest Contentful Paint (LCP) 元素所用的图片,以缩短您的 LCP 用时

3.33 避免网络负载过大

  • 网络负载过大不仅会让用户付出真金白银,还极有可能会延长加载用时

3.34 避免 DOM 规模过大

  • 大型 DOM 会增加内存使用量、导致样式计算用时延长,并产生高昂的布局重排成本

3.35 User Timing 标记和测量结果

  • 建议使用 User Timing API 检测您的应用,从而衡量应用在关键用户体验中的实际性能

3.36 尽量减少第三方使用

  • 第三方代码可能会显著影响加载性能。请限制冗余第三方提供商的数量,并尝试在页面完成主要加载后再加载第三方代码

3.37 使用 Facade 延迟加载第三方资源

  • 您可以延迟加载某些第三方嵌入代码。不妨考虑使用 Facade 替换这些代码,直到您需要使用它们为止

3.38 Largest Contentful Paint 所对应的图片未被延迟加载

  • 被延迟加载的首屏图片会在页面生命周期内的较晚时间呈现,这可能会致使系统延迟渲染最大内容元素

3.39 请勿使用 document.write()

  • 对于连接速度较慢的用户,通过 document.write() 动态注入的外部脚本可将网页加载延迟数十秒

3.40 具有包含 width 或 initial-scale 的 标记

  • <meta name="viewport"> 不仅会针对移动设备屏幕尺寸优化您的应用,还会阻止系统在响应用户输入前出现 300 毫秒的延迟

参考文档