曾经看到有篇文章写了关于用户对于网站的性能延迟的感知,其中列出了几个关键性的指标:
延迟时间 | 指标介绍 |
---|---|
0 ~ 16ms | 新帧更新时间,每秒渲染 60 个新帧保证动画很流畅 |
0 ~ 100ms | 响应用户操作时间,在此时间窗口内用户会感觉到响应被立即执行 |
100 ~ 1000ms | 加载页面或更新视图时间,在这个窗口内用户会感觉任务进展是自然的、连续的 |
>= 1000ms (1s) | 用户会失去对他们正在执行的任务的注意力 |
>= 10000ms (10s) | 用户感到沮丧并可能放弃任务,并且以后可能会也可能不会回来 |
由此可见,性能对于一个应用非常重要。
为了能够对 web 应用的性能优化有一个整体的认知,本文对 google 的 web 前端性能优化指南进行了翻译整理。整个性能优化的内容分为三个部分:加载性能、渲染性能和性能评估。
一、加载性能
加载性能主要是受用户的网络条件、要加载的资源多少以及资源内容的大小的影响。用户的网络条件我们无法控制,所以页面资源的加载数量以及资源内容的大小是我们优化的方向,另一方面我们也可以在我们依赖的传输协议上作文章,比如使用 http 缓存等。
1.1 资源加载性能优化
不同的资源类型使用的优化手段会稍微有些不同:
文本资源
文本资源主要包括:HTML、JavaScript、CSS 等,主要优化手段有:
- 减小内容:minify
- 压缩内容:比如使用 gzip 压缩
- 减少第三方库的使用
- JavaScript 特有的优化
- Tree Shaking
- Code Split
- CSS 特有的优化
- 内嵌较小的 CSS 文件
- 请勿内嵌较大数据 URI
- 请勿内嵌 CSS 属性
图片资源
主要优化手段有:
- 去掉不必要的图片
- 选择合适的图片格式
- PNG: 剪贴画、线条画、需要透明度的
- JPG: 照片
- GIF: 动画
- WebP
- 若不确定,同时保存所有格式对比大小
- 去掉 metadata
- 调整图片尺寸
- 合理的大小
- 裁切重要的部分,其余留白
- 减小图片质量: 主要是 JPG
- 使用压缩工具: 比如 tinyPNG,支持 .png 和 .jpg
- 使用 video 代替大的 GIF
- Lazy load
1.2 HTTP 协议及缓存优化
缓存对于资源加载优化的优势非常明显,对于同一个资源在内容没有改变的情况下不需要做二次加载,极大的提升了加载效率。
使用 CDN
CDN 的优点是就近访问,通过缓存代理将资源内容缓存在 CDN 的边缘节点,当用户请求资源的时候如果命中缓存就直接返回缓存的内容,否则就回源到资源服务器拉取内容再返回,同时对资源进行缓存
优化 HTTP 请求体
- 合并文本内容
- 合并图片
- scripts 文件位置: 放在 </body> 前
- 内嵌 scripts: 少量代码时
- 同一资源避免多次加载
- 懒加载
- 使用 HTTP/2 的特性(二进制分帧层、请求与响应复用、数据流优先级、每个来源一个连接、流控制、服务器推送、标头压缩-、HPACK 的安全性和性能)
使用 HTTP 缓存头
- Last-Modified / If-Modified-Since:数据从上一次请求的 Last-Modified 来;服务端返回 200 和内容,表示内容有更新;服务端返回 304 无内容,表示内容没有更新;只能用在 GET 和 HEAD;遇到 If-None-Match 无效,除非服务端不支持 If-None-Match
- Etag / If -None-Match:数据从上一次请求的 Last-Modified 来;服务端返回 200 和内容,表示内容有更新;服务端返回 304 无内容,表示内容没有更新
- cache-control
- no-cache:可以缓存,但每次强制客户端检查更新情况
- no-store:不能缓存
- public:允许浏览器和中间缓存
- private:允许浏览器缓存,不允许中间缓存
- max-age:缓存的最大时长,单位:秒,最长一年:31536000
- expires:设置一个资源过期的时间;过了那个时间点后,向服务器请求新内容;作为 cache-control 的备选方案;最长时间同样不能超过一年;If-Modified-Since、If-Unmodified-Since、Last-Modified
1.3 PRPL 模式
PRPL 是一个首字母缩写词,它描述了一种用于使网页加载并变得更快的交互模式:
- Push (or preload):推送或预加载最重要的资源
- Render:尽快渲染初始路径
- Pre-cache:预缓存剩余资源
- Lazy load:延迟加载其他路由和非关键资源
二、渲染性能
渲染性能指的是浏览器对网页内容的渲染性能。该性能主要受用户设备的性能、网络条件以及影响渲染性能的代码逻辑的影响。同样,用户设备的性能和网络条件我们无法控制,所以我们只能从影响渲染性能的代码逻辑入手。
2.1 浏览器关键渲染路径
关键渲染路径也就是浏览器将 HTML、CSS 和 JavaScript 转换成实际运作的网站必须采取的一系列步骤。通过优化关键渲染路径,我们可以显著缩短首次渲染页面的时间。
关于具体的知识这里不作过多介绍,感兴趣的同学可以到文章底部参考资源部分找到相关资料,这里主要介绍优化方面的内容。
2.2 渲染性能优化
优化 JavaScript 执行
- requestAnimationFrame:对于动画效果的实现,避免使用 setTimeout 或 setInterval,请使用 requestAnimationFrame。
- 降低复杂性或使用 Web Worker:将长时间运行的 JavaScript 从主线程移到 Web Worker。
- 了解 JavaScript 的“帧税”:使用微任务来执行对多个帧的 DOM 更改。
- 避免微优化 JavaScript:使用 Chrome DevTools 的 Timeline 和 JavaScript 分析器来评估 JavaScript 的影响。
缩小样式计算的范围并降低其复杂性
- 降低选择器的复杂性
- 减少要计算样式的元素数量
- 测量样式重新计算的开销
- 使用块、元素、修饰符(BEM)编写样式
避免大型、复杂的布局和布局抖动
- 尽可能避免布局操作:对“几何属性”(如宽度、高度、左侧或顶部)的更改
- 使用 flexbox 而不是较早的布局模型
- 避免强制同步布局:始终先批量读取样式并执行(浏览器可以使用上一帧的布局值),然后执行任何写操作
- 避免布局抖动:不要在循环中读取样式值
简化绘制的复杂度、减小绘制区域
- 触发布局与绘制
- 触发布局必然触发绘制
- 更改非几何属性,例如背景、文本或阴影,会触发绘制
- 使用 Chrome DevTools 快速确定绘制瓶颈
- 提升移动或淡出的元素
- will-change
- 3D 变形: transform: translateZ(0);
- 减少绘制区域:编排动画和变换使其不过多重叠
- 降低绘制的复杂性:绘制任何涉及模糊(例如阴影)的元素所花的时间将比(例如)绘制一个红框的时间要长
坚持仅合成器的属性和管理层计数
- 坚持使用 transform 和 opacity 属性更改来实现动画
- 提升您打算设置动画的元素
- 管理层(layer)并避免层数激增
- 使用 Chrome DevTools 来了解应用中的层
使输入处理程序去除抖动
- 避免长时间运行输入处理程序
- 避免在输入处理程序中更改样式
- 使滚动处理程序去除抖动
三、性能评估
评估一个网站的性能之前,首先要确定衡量性能好坏的指标,通过指标数据才能客观评估哪些方面做得好,哪些方面(以及如何)可以改进。
3.1 指标类型
- Perceived load speed: 加载并渲染可视区域内容的速度
- Load responsiveness: 加载并执行引用的 JavaScript 代码以响应用户交互的速度
- Runtime responsiveness: 加载完成后,页面响应用户交互的速度
- Visual stability: 页面上的元素是否以用户不期望的方式移动并可能干扰他们的交互?
- Smoothness: 过渡和动画渲染桢率是否稳定,状态变化是否流畅?
3.2 重要的衡量指标
- First contentful paint (FCP): 测量从页面开始加载到屏幕上呈现页面内容的任何部分的时间
- Largest contentful paint (LCP): 测量从页面开始加载到最大的文本块或图像元素在屏幕上呈现的时间
- First input delay (FID): 衡量从用户首次与您的网站进行交互(即当他们单击链接,点击按钮或使用自定义的 JavaScript 驱动的控件)到浏览器实际能够响应该交互之间的时间
- Time to Interactive (TTI): 测量从页面开始加载到可视化呈现之间的时间,页面的初始脚本(如果有)已经加载,并且能够可靠地快速响应用户输入的时间
- Total blocking time (TBT): 测量主线程被阻塞足够长时间以防止输入响应的 FCP 和 TTI 之间的总时间
- Cumulative layout shift (CLS): 测量页面开始加载到页面生命周期状态变为隐藏之间发生的所有意外布局转换的累积分数