本文正在参加「金石计划」
大家都知道性能优化对于页面来说是非常重要的,一个网站的加载交互速度足以量化了变现速度,下面就带大家从0到1的了解和实践web性能优化
什么是 Web 性能
简单来说就是你的网站够不够快。
- 打开速度
- 动画效果
- 表单提交
- 列表滚动
- 页面切换
- ...
MDN 上的 Web 性能定义:Web 性能是网站或应用程序的客观度量和可感知的用户体验。
- 减少整体加载时间:减小文件体积、减少 HTTP 请求、使用预加载。
- 使网站尽快可用:仅加载首屏内容,其它内容根据需要进行懒加载。
- 平滑和交互性:使用 CSS 替代 JS 动画、减少 UI 重绘。
- 感知表现:你的页面可能不能做得更快,但你可以让用户感觉更快。耗时操作要给用户反馈,比如加载动画、进度条、骨架屏等提示信息。
- 性能测定:性能指标、性能测试、性能监控持续优化。
如何进行 Web 性能优化
(1)首先需要了解性能指标 - 多快才算快?
(2)使用专业的工具可量化地评估出网站或应用的性能表现;
(3)然后立足于网站页面响应的生命周期,分析出造成较差性能表现的原因;
(4)最后进行技术改造、可行性分析等具体的优化实施。
(5)迭代优化
性能指标
性能指标,下面列出了三个指标规范
- RAIL 性能模型
- 响应(Response):应该尽可能快速的响应用户, 应该在 100ms 以内响应用户输入。
- 动画(Animation):在展示动画的时候,每一帧应该以 16ms 进行渲染,这样可以保持动画效果的一致性,并且避免卡顿。
- 空闲(Idle):当使用 Javascript 主线程的时候,应该把任务划分到执行时间小于 50ms 的片段中去,这样可以释放线程以进行用户交互。如果一个任务超过50ms,该任务将被指定为长任务,在长任务执行过程中,不会响应用户的任何交互。
- 加载(Load):应该在小于 1s 的时间内加载完成你的网站,并可以进行用户交互。
- 基于用户体验的核心指标
- 新一代性能指标:Web Vitals ,规定
LCP, CLS, FID
是最为重要的指标。
测量 Web Vitals
- 性能测试工具,比如 Lighthouse
- 使用 web-vitals 库
- 使用浏览器插件 Web Vitals
一些常用的指标
- 加载进度的指标。 ttfb, contentLoaded, load
- 用户视觉体验的指标。first paint, first contentful paint, largest contentful paint, time to interactive, first input delay, first cpu idle, first meaningful paint, speed index等等。
下面就来详细介绍一下这些指标
Time to First Byte 第一字节时间 (TTFB)
TTFB 是一个衡量对资源的请求和响应的第一个字节开始和到达之间时间的指标。
First Contentful Paint(FCP)
首次内容绘制,浏览器首次绘制来自 DOM 的内容的时间,内容必须是文本、图片(包含背景图)、非白色的 canvas 或 SVG,也包括带有正在加载中的 Web 字体的文本。
这是用户第一次开始看到页面内容,但仅仅有内容,并不意味着它是有用的内容(例如 Header、导航栏等),也不意味着有用户要消费的内容。
为了提供良好的用户体验,网站应该努力将首次内容绘制控制在1.8 秒或以内。
速度指标
FCP 时间(以秒为单位) | 颜色编码 | FCP分数(HTTP存档百分位数) |
---|---|---|
0–2 | 绿色(快速) | 75–100 |
2–4 | 橙色(中等) | 50–74 |
超过4 | 红色(慢) | 0–49 |
Largest Contentful Paint(LCP)
LCP(Largest Contentful Paint)最大内容绘制,可视区域中最大的内容元素呈现到屏幕上的时间,用以估算页面的主要内容对用户可见时间。
LCP 考虑的元素:
元素。计算的大小是图片原始大小和设置的大小的较小的那一个。
元素内的 元素
- 元素(封面图)
- 通过
url()
函数加载背景图片的元素 - 包含文本节点或其他内联级文本元素子级的块级元素。
元素溢出的部分不会被计算在内, 通过 CSS 设置的任何边距、填充或边框都不在考量范围内
以下是一些示例:
在以上两个时间轴中,最大的元素随内容加载而变化。在第一个示例中,新内容被添加到DOM中,并且更改了最大的元素。在第二个示例中,布局发生更改,以前最大的内容从视口中删除。
通常情况下,延迟加载的内容要比页面上已有的内容大,但不一定是这种情况。接下来的两个示例显示了在页面完全加载之前发生的最大内容绘画。
在第一个示例中,Instagram 徽标相对较早地加载,即使逐渐显示其他内容,它仍然是最大的元素。在 Google 搜索结果页面示例中,最大的元素是一段文本,该文本在任何图像或徽标加载完成之前显示。由于所有单个图像均小于此段,因此在整个加载过程中,它始终是最大的元素。
在 Instagram 时间轴的第一帧中,您可能会注意到相机徽标周围没有绿色框。那是因为它是一个 元素,并且 元素当前不被视为 LCP 候选对象。
为了提供良好的用户体验,网站应力争使用2.5 秒或更短的“最大内容绘画” 。
速度指标
LCP 时间(以秒为单位) | 颜色编码 |
---|---|
0-2.5 | 绿色(快速) |
2.5-4 | 橙色(中等) |
超过4 | 红色(慢) |
First Input Delay(FID)
FID 测量从用户第一次与页面交互(例如当他们单击链接、点按按钮或使用由 JavaScript 驱动的自定义控件)直到浏览器对交互作出响应,并实际能够开始处理事件处理程序所经过的时间。
输入延迟是因为浏览器的主线程正忙于做其他事情,所以不能响应用户。发生这种情况的一个常见原因是浏览器正忙于解析和执行应用程序加载的大量计算的 JavaScript。
第一次输入延迟通常发生在第一次内容绘制(FCP)和可持续交互时间(TTI)之间,因为页面已经呈现了一些内容,但还不能可靠地交互。
为了提供良好的用户体验,网站应该努力将首次输入延迟设控制在100 毫秒或以内。
如上图所示,浏览器接收到用户输入操作时,主线程正在忙于执行一个耗时比较长的任务,只有当这个任务执行完成后,浏览器才能响应用户的输入操作。它必须等待的时间就此页面上该用户的 FID 值。
速度指标
Time to Interactive(TTI) 可持续交互时间或可流畅交互时间
表示网页第一次 完全达到可交互状态 的时间点,浏览器已经可以持续性的响应用户的输入。完全达到可交互状态的时间点是在最后一个长任务(Long Task)完成的时间, 并且在随后的 5 秒内网络和主线程是空闲的。
长任务是需要 50 毫秒以上才能完成的任务。
速度指标
TTI指标(以秒为单位) | 颜色编码 |
---|---|
0–3.8 | 绿色(快速) |
3.9–7.3 | 橙色(中等) |
7.3以上 | 红色(慢) |
Total Block Time(TBT)
Total Block Time(TBT)总阻塞时间,度量了 FCP 和 TTI 之间的总时间,在该时间范围内,主线程被阻塞的时间过长,无法作出输入响应。
只要存在长任务,该主线程就会被视为“阻塞”,该任务在主线程上运行超过50毫秒(ms)。我们说主线程“被阻止”是因为浏览器无法中断正在进行的任务。因此,如果用户确实在较长的任务中间与页面进行交互,则浏览器必须等待任务完成才能响应。
给定的长任务的阻止时间是其持续时间超过50毫秒。页面的总阻塞时间是FCP和TTI之间发生的每个长任务的阻塞时间的总和。
例如,考虑页面加载期间浏览器主线程的下图:
上方的时间轴上有五个任务,其中三个是长任务,因为这些任务的持续时间超过 50 毫秒。下图显示了各个长任务的阻塞时间:
因此,虽然在主线程上运行任务的总时间为 560 毫秒,但其中只有 345 毫秒被视为阻塞时间。
速度指标
TBT时间 (以毫秒为单位) | 颜色编码 |
---|---|
0–300 | 绿色(快速) |
300-600 | 橙色(中等) |
超过600 | 红色(慢) |
Cumulative Layout Shift(CLS)
Cumulative Layout Shift(CLS)累计布局偏移,CLS 会测量在页面整个生命周期中发生的每个意外的布局移位的所有单独布局移位分数的总和,它是一种保证页面的视觉稳定性从而提升用户体验的指标方案。
页面内容的意外移动通常是由于异步加载资源或将 DOM 元素动态添加到现有内容上方的页面而发生的。罪魁祸首可能是尺寸未知的图像或视频,呈现比其后备更大或更小的字体,或者是动态调整自身大小的第三方广告或小部件。
为了提供良好的用户体验,网站应该努力将 CLS 分数控制在0.1 或以下。
速度指标
CLS 时间(以毫秒为单位) | 颜色编码 |
---|---|
0–0.1 | 绿色(快速) |
0.1-0.25 | 橙色(中等) |
超过0.25 | 红色(慢) |
Speed Index
Speed Index(速度指数)是一个表示页面可视区域中内容的填充速度的指标,可以通过计算页面可见区域内容显示的平均时间来衡量。
速度指标
速度指数 (以秒为单位) | 颜色编码 | 速度指数得分 |
---|---|---|
0–4.3 | 绿色(快速) | 75–100 |
4.4–5.8 | 橙色(中等) | 50–74 |
5.8以上 | 红色(慢) | 0–49 |
性能测量
我们知道,如果想要优化网站性能,我们首先需要测量出该网站存在的性能问题,所以我们可以通过以下工具来进行测试。
性能测量工具
- 浏览器 DevTools 调试工具 ,
ctrl + shift + p
:调出搜索框,查询各个面板- 浏览器任务管理器
- Network 面板
- Coverage 面板
- Memory 面板
- Performance 面板
- Performance monitor 面板
- 灯塔(Lighthouse),网站整体质量评估,并给出优化建议
- WebPageTest
- 多测试地点
- 全面的性能报告
本地部署webpagetest进行测量测试环境下项目的性能
- windows安装docker
- 拉取镜像
docker pull webpagetest/server
docker pull webpagetest/agent
- 运行实例
docker run -d -p 4000:80 --rm webpagetest/server
docker run -d -p 4001:80 --network="host" -e "SERVER_URL=http://localhost:4000/work/" -e "LOCATION=Test" webpagetest/agent
性能测量节点api
下方列出性能测量工具一些事件测量的api
DNS 解析耗时: domainLookupEnd - domainLookupStart
TCP 连接耗时: connectEnd - connectStart
SSL 安全连接耗时: connectEnd - secureConnectionStart
网络请求耗时 (TTFB): responseStart - requestStart
数据传输耗时: responseEnd - responseStart
DOM 解析耗时: domInteractive - responseEnd
资源加载耗时: loadEventStart - domContentLoadedEventEnd
First Byte时间: responseStart - domainLookupStart
白屏时间: responseEnd - fetchStart
首次可交互时间: domInteractive - fetchStart
DOM Ready 时间: domContentLoadEventEnd - fetchStart
页面完全加载时间: loadEventStart - fetchStart
http 头部大小: transferSize - encodedBodySize
重定向次数:performance.navigation.redirectCount
重定向耗时: redirectEnd - redirectStart
计算一些关键的性能指标
- 计算TTI,用户可持续交互时间。
window.addEventListener('load', (event) => {
// Time to Interactive
let timing = performance.getEntriesByType('navigation')[0];
console.log(timing.domInteractive);
console.log(timing.fetchStart);
let diff = timing.domInteractive - timing.fetchStart;
console.log("TTI: " + diff);
})
- 观察长任务
// 观察长任务
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry)
}
})
observer.observe({entryTypes: ['longtask']})
- 页面显示隐藏的监听,当监听到页面切换时,我们可以做一些事情。
// 见面可见性的状态监听
let vEvent = 'visibilitychange';
if (document.webkitHidden != undefined) {
// webkit prefix detected
vEvent = 'webkitvisibilitychange';
}
function visibilityChanged() {
if (document.hidden || document.webkitHidden) {
console.log("Web page is hidden.")
} else {
console.log("Web page is visible.")
}
}
document.addEventListener(vEvent, visibilityChanged, false);
- 监控用户网络状态
var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
var type = connection.effectiveType;
function updateConnectionStatus() {
console.log("Connection type changed from " + type + " to " + connection.effectiveType);
type = connection.effectiveType;
}
connection.addEventListener('change', updateConnectionStatus);
测量工具的具体使用
lighthouse
我们可以保存上一次报告的信息,用于对比。
webpagetest
devtools
network
网络请求阻止,可以查看该次请求对页面加载的影响。
- 打开方式:Ctrl+ Shift + P -> block
coverage
- 打开方式:Ctrl+ Shift + P -> coverage
可以看出每个文件代码执行覆盖率。用于事后代码分割,分包使用。
memory
查看内存占用情况。
- 打开方式:Ctrl+ Shift + P -> memory
performance
点击火焰图后,wsad几个键可以帮助我们定位快照执行情况。放大缩小左移右移这些。
FPS 计数器
查看浏览器渲染帧率。
- 打开方式:Ctrl+ Shift + P -> fps
Performance monitor
通过它让我们可以实时监控网站应用运行过程中,诸如 CPU 占用率、JavaScript 内存使用大小、内存中挂的 DOM 节点数、JavaScript 事件监听次数及页面发生重绘与重排的处理时间等信息。
- 打开方式:Ctrl+ Shift + P -> performance monitor
查看是否发生重绘 rendering
- 打开方式:Ctrl+ Shift + P -> render
优化方案
网站页面的生命周期(以前写的一篇文章),通俗地讲就是从我们在浏览器的地址栏中输入一个 URL 后,到整个页面渲染出来的过程。整个过程包括域名解析,建立 TCP 连接,前后端通过 HTTP 进行会话,压缩与解压缩,以及前端的关键渲染路径等。
- 从发出请求到收到响应的优化,比如 DNS 查询、HTTP 长连接、HTTP 2、HTTP 压缩、HTTP 缓存等。
- 关键渲染路径优化,比如是否存在不必要的重绘和回流。
- 加载过程的优化,比如延迟加载,是否有不需要在首屏展示的非关键信息,占用了页面加载的时间。
- 资源优化,比如图片、视频等不同的格式类型会有不同的使用场景,在使用的过程中是否恰当。
- 构建优化,比如压缩合并、基于 webpack 构建优化方案等。
请求响应优化
dns优化
DNS 通常需要 20-120 毫秒来查找给定主机名的IP地址。在 DNS 查找完成之前,浏览器无法从该主机名下载任何内容。
- 减少域名数量
所以我们可以减少域名的数量,从而减少域名查询的时间。但是这样又出现了一个问题,我们增加不同的域名(一个域名最多可以建立6个tcp连接),目的是增大资源的并行加载数量。所以原则是将这些资源划分为至少两个但不超过四个域名。 - dns预解析 DNS-prefetch (DNS 预获取) 是尝试在请求资源之前解析域名。这可能是后面要加载的文件,也可能是用户尝试打开的链接目标。域名解析和内容载入是串行的网络操作,所以这个方式能减少用户的等待时间,提升用户体验 。
HTML 元素通过 dns-prefetch 的 rel 属性值提供此功能。然后在 href 属性中指要跨域的域名:
持久连接
HTTP 1.1默认开始持久连接,这样请求就可以复用上一次的tcp的连接,持久连接的好处在于减少了 TCP 连接的重复建立和断开所造成的额外开销,减轻了服务器端的负载。另外,减少开销的那部分时间,使 HTTP 请求和响应能够更早的结束,这样 Web 页面的显示速度也就相应提高了。
客户端和服务器发现对方一段时间没有活动,就可以主动关闭连接。不过,规范的做法是,客户端在最后一个请求时,发送 Connection: close
,明确要求服务器关闭 TCP 连接。
HTTP 2
这是一个对比 HTTP1 和 HTTP2 资源加载的在线示例
压缩传输的数据资源
使用文本压缩,可以达到 70% 的压缩比率,大大减低对于带宽的需求。使用Accept-Encoding, Content-Encoding来表示可以使用哪些压缩方式或者使用了哪个压缩方式。
注意这种压缩方式是动态压缩,在传输的时候进行压缩。所以不适合图片等资源的压缩。
对于请求的压缩,http2自动会使用HPACK算法压缩头部,对于请求体的压缩,如果需要我们可以自己通过相关类库进行,但是需要后端也做相同的解压处理,才能获取到真实的信息。
下面是一个简单示例。
压缩请求正文数据
const rawBody = 'content=test';
let rawLen = rawBody.length;
const bufBody = new Uint8Array(rawLen);
for(let i = 0; i < rawLen; i++) {
bufBody[i] = rawBody.charCodeAt(i);
}
const format = 'gzip'; // gzip | deflate | deflate-raw
let buf;
switch(format) {
case 'gzip':
buf = window.pako.gzip(bufBody);
break;
case 'deflate':
buf = window.pako.deflate(bufBody);
break;
case 'deflate-raw':
buf = window.pako.deflateRaw(bufBody);
break;
}
const xhr = new XMLHttpRequest();
xhr.open('POST', '/node/');
xhr.setRequestHeader('Content-Encoding', format);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
xhr.send(buf);
在 Node 中解压请求正文中的数据
const http = require('http');
const zlib = require('zlib');
http.createServer(function (req, res) {
let zlibStream;
const encoding = req.headers['content-encoding'];
switch(encoding) {
case 'gzip':
zlibStream = zlib.createGunzip();
break;
case 'deflate':
zlibStream = zlib.createInflate();
break;
case 'deflate-raw':
zlibStream = zlib.createInflateRaw();
break;
}
res.writeHead(200, {'Content-Type': 'text/plain'});
req.pipe(zlibStream).pipe(res);
}).listen(8888, '127.0.0.1');
http缓存
对于静态资源重复加载有着重要的优化。
强制缓存
- expires, 指定一个绝对时间。缺点当服务器和客户端时间不一致可能会造成资源不能及时更新。
- cache-control(既可以实现强制缓存也可以实现协商缓存(no-cache))
协商缓存
- last-modified(指定文件最后修改时间), 指定一个utc时间,需要配合cache-control: no-cache才生效。
- etag,文件唯一标识。
缓存建议
首先 HTML 在这里属于包含其他文件的主文件,为保证当其内容发生修改时能及时更新,应当将其设置为协商缓存,即为 cache-control 字段添加 no-cache 属性值;
其次是图片文件,因为网站对图片的修改基本都是更换修改,同时考虑到图片文件的数量及大小可能对客户端缓存空间造成不小的开销,所以可采用强制缓存且过期时间不宜过长,故可设置 cache-control 字段值为 max-age=86400。
接下来需要考虑的是样式表文件,由于其属于文本文件,可能存在内容的不定期修改,又想使用强制缓存来提高重用效率,故可以考虑在样式表文件的命名中增加文件指纹或版本号(比如添加文件指纹后的样式表文件名变为了 style.51ad84f7.css),这样当发生文件修改后,不同的文件便会有不同的文件指纹,即需要请求的文件 URL 不同了,因此必然会发生对资源的重新请求。同时考虑到网络中浏览器与 CDN 等中间代理的缓存,其过期时间可适当延长到一年,即 cache-control:max-age=31536000。
最后是 JavaScript 脚本文件,其可类似于样式表文件的设置,采取文件指纹和较长的过期时间,如果 JavaScript 中包含了用户的私人信息而不想让中间代理缓存,则可为 cache-control 添加 private 属性值。
CDN
未使用 CDN 网络进行缓存加速,那么通过浏览器访问网站获取资源的大致过程如图所示。
如果使用了 CDN 网络,则资源获取的大致过程是这样的。
- 由于 DNS 服务器将对 CDN 的域名解析权交给了 CNAME 指向的专用 DNS 服务器,所以对用户输入域名的解析最终是在 CDN 专用的 DNS 服务器上完成的。
- 解析出的结果 IP 地址并非确定的 CDN 缓存服务器地址,而是 CDN 的负载均衡器的地址。
- 浏览器会重新向该负载均衡器发起请求,经过对用户 IP 地址的距离、所请求资源内容的位置及各个服务器复杂状况的综合计算,返回给用户确定的缓存服务器 IP 地址。
- 对目标缓存服务器请求所需资源的过程。
CDN 网络能够缓存网站资源来提升首次请求的响应速度,但并非能适用于网站所有资源类型,它往往仅被用来存放网站的静态资源文件。所谓静态资源,就是指不需要网站业务服务器
参与计算即可得到的资源,包括第三方库的 JavaScript 脚本文件、样式表文件及图片等,这些文件的特点是访问频率高、承载流量大,但更新修改频次低,且不与业务有太多耦合。
设置多个CDN服务器域名的好处
- 第一点是避免对静态资源的请求携带不必要的 Cookie 信息。
- 第二点是考虑浏览器对同一域名下并发请求的限制。
关键渲染路径优化
任何内容都可以使用文件压缩,资源传递压缩,缓存来优化。
html优化
- 缩小文件的尺寸(Minify),一般前端工程化在构建的时候会使用插件进行代码压缩。
- 使用gzip压缩(Compress)
- 使用缓存(HTTP Cache)。
css优化
- 通过style结合media属性有条件的加载媒体查询样式,使其与关键css加载分离。
<link href="other.css" rel="stylesheet" media="(min-width: 375px)">
-
将一些关键 CSS 直接内联到 HTML 文档内。实现同html文件一同加载,由html解析器解析。可能会出现闪屏。
-
避免在 CSS 中使用
@import
, 内部的css加载将变成串行加载。style引入的css加载完成后,解析css时遇见@import
时就会去请求另外的css,加载时间就是2者之和了。 -
减少要计算样式的元素数量,避免过多选择器堆积,导致查找元素时的消耗。这里建议使用BEM规范命名css类选择器。
- 中画线(-):仅作为连字符使用,表示某个块或子元素的多个单词之间的连接符。
- 单下画线(_):作为描述一个块或其子元素的一种状态。
- 双下画线(__):作为连接块与块的子元素。
.mylist__item_size-10 {}
-
使用contain属性做优化。
提供一个例子,当我们点击按钮的时候,最上面的元素发生了重排,但是并没有影响整个页面的重排。contain属性主要就是让影响到有限的 DOM 区域。
-
使用
font-display
属性,让文字更早的显示在页面上。减少文字闪动问题。
js优化
由于js加载会阻塞dom解析和渲染,所以我们会将js文件放在整个html文档后面,虽然这样整体的加载时长不会改变,但是这样会让页面中的元素提早渲染出来。但是这样的话就会让js加载执行延迟,如果需要解决我们可以使用这些方式
- 异步加载 JavaScript (async, defer)
- preload, 预加载js资源,但是js文件依旧放在整个文档之后。注意这里不能使用
refetch
,否则会重复加载。他主要是预加载其他页面的资源。
<!-- 利用空闲时间预加载指定的资源 -->
<link rel="preload" href="index.js">
<link rel="preload" href="index2.js">
我们需要避免运行时间长的 JavaScript,方式执行时间大于50ms,被定义为长任务,导致页面性能下降,对于复杂的计算,我们应该使用web worker
来开启一个线程计算。
使用web worker的注意事项
- DOM限制:Worker 无法读取主线程所处理网页的 DOM 对象,也就无法使用 document、window 和 parent 等对象,只能访问 navigator 和 location 对象。
- 文件读取限制:Worker 子线程无法访问本地文件系统,这就要求所加载的脚本来自网络。
- 通信限制:主线程和 Worker 子线程不在同一个上下文内,所以它们无法直接进行通信,只能通过消息来完成。
- 脚本执行限制:虽然 Worker 可以通过 XMLHTTPRequest 对象发起 ajax 请求,但不能使用 alert() 方法和 confirm() 方法在页面弹出提示。
- 同源限制:Worker 子线程执行的代码文件需要与主线程的代码文件同源。
使用raf来代替定时器执行动画函数
function run () {
if (box.offsetLeft >= 200) {
box.style.left = '200px'
return
}
box.style.left = `${box.offsetLeft + 1}px`
window.requestAnimationFrame(run)
}
window.requestAnimationFrame(run)
对于一些高频事件的触发,我们可以使用防抖和节流来做一些优化,降低执行频率。 防抖节流函数的实现
重绘重排的优化
使用 requestAnimationFrame 方法控制渲染帧,他是间接获取上一次页面更新后的数据的,所以不会造成重排。
requestAnimationFrame(queryDivHeight)
function queryDivHeight () {
const div = document.getElementById('div')
console.log(div.offsetHeight)
}
合成处理
因为页面的绘制和重排都是以图层为单位的,所以当处理该次dom时,会影响很多的dom元素,那么就可以考虑单独放在一个图层中。然后再通过合成线程进行处理。开启一个图层
- 拥有具有3D变换的CSS属性
- 使用加速视频解码的
<video>
节点 <canvas>
节点- CSS3动画的节点
- 拥有CSS加速属性的元素(will-change)
透明度 opacity 和图层变换 transform,他们在合成处理阶段就能完成,则将会节省大量的性能开销。不会触发重排和重绘。
图片优化
图片适配
通过img srcset
来设置适配不同屏幕的图片集合。
<img src="photo.jpg" srcset="photo@2x.jpg 2x,photo@3x.jpg 3x,photo@4x.jpg 4x" alt="photo">
图片懒加载
图片懒加载,还可以通过promise来处理。
// 可以通过done来表示,通过其他低质量图片来占位
let done = false
function isImgLoaded() {
const img = new Image()
img.src = imgUrl
img.onload = () => {
done = true
}
}
const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
});
};
img标签原生也支持懒加载。
<img loading="lazy" />
图片格式的选择
对于图片的优化我们还需要选择相应的合适的格式。 深入探讨15种主流图片格式及其优缺点
可以通过一些库对图片进行压缩,比如 imagemini, 压缩jpg,imagemin-pngcrush, 压缩png
其他优化
-
避免layout thrashing。读写分离。fastdom库。
-
尽量通过css变化来实现动画,不会触发重排重绘,直接在合成线程中完成。
-
函数优化 对于立刻调用的函数,采用eager parse。让其解析到定义的位置时,就开始创建函数的上下文环境。通过将函数使用
()
包裹即可。
export default () => {
const add = (a, b) => a * b; // lazy parsing
// const add = ((a, b) => a * b); // eager parsing
const num1 = 1;
const num2 = 2;
add(num1, num2);
}
- 避免读取超过数组的长度,读取为undefined后将会向原型链查找,造成资源浪费。
function foo(array) {
for (let i = 0; i <= array.length; i++) { // 越界比较
if(array[i] > 1000) { // 1.沿原型链的查找 2.造成undefined与数进行比较
console.log(array[i]); // 业务上无效、出错
}
}
}
-
避免js类型转换。如果是相同类型的元素,编译器会进行优化,加快转型速度。
-
以相同的顺序初始化对象成员,避免隐藏类的调整。这就可以使用类来统一构造对象了。
-
将array-like转为数组后再进行操作。
-
ssr,加速首屏渲染(所有的内容都提前在服务端渲染好了),更好的SEO
-
页面预渲染,在我们项目打包构建完成后,将首屏内容替换到页面中。
在构建完毕后,将首页预渲染出来。使用
react-snap
。就是通过爬虫事先将内容爬取,然后再将内容替换到index.html页面中。大型单页应用的性能瓶颈: JS下载+解析+执行。
SSR的主要问题:牺牲TTFB来补救First Paint ;实现复杂。
Pre—rendering 打包时提前渲染页面,没有服务端参与。
webpack应用大小监控分析检测工具
一个在线的分析工具,webpack --profile --json > stats.json
,通过执行该指令,生成stats.json,然后上传即可。
基于source-map文件分析指定的文件。
analyze: source-map-explorer build/*.js
打包完毕,执行命令就会自动开启一个分析网页。
webpack-bundle-analyzer, 一个类似于webpack-chart
的包依赖分析工具。
speed-measure-webpack-plugin,可以输出插件,loader的处理时间啥的。
参考文档
-
...
如果觉得有用,欢迎留下star~~~