一、 解析
1-1 为什么做优化?
经典问题:白屏时间长,用户体验差
产生原因:网络问题、关键路径渲染(CRP)问题
- 用户响应时间分布
- 10% ~ 20% — 从服务器端获取HTML文档(后端优化内容)
- 80% ~ 90% — 下载页面中的所有组件(前端优化内容)
- 根据二八原则,我们更应该关注前端优化
1-2 怎么作优化
根据文章总结,优化无非就是网络优化、静态资源(html、js、css、image)优化。撇开网络方面的优化,静态资源的优化关键在于你要去深入理解 关键渲染路径(CRP) 的运行原则和规则。
1-2 -1 了解浏览器关键渲染路径(html加载过程)
面试必问:
1、描述一下从url输入到页面展示的全过程?
2、描述一下html加载全过程?
渲染的关键路径分为5步:
- 构建DOM树
构建过程:Bytes -> Characters -> Tokens -> Nodes -> DOM
- 构建CSSOM树
构建过程:Bytes -> Characters -> Tokens -> Nodes -> CSSOM
- 合并DOM树和CSSOM树构建渲染树
1、过滤掉不可见节点(脚本标记、元标记)
2、过滤掉样式隐藏的节点(display:none)
- 根据渲染树来布局,计算节点的几何信息(layout)
- 将各个节点绘制在屏幕上(paint)
CSS的影响:
首先从上面的五步中看出,只有当DOM树和CSSOM树都构建完成之后才可以进行渲染树的构建,所以这两步是对整体渲染起阻塞作用的,当然了DOM树是必须的,它提供给页面内容,**而CSSOM的必要性并不是太明显,所以在CSSOM构建的过程中可以做一些优化。**在做优化前先要了解这几个知识点。
1、默认情况下,CSS是阻塞渲染的资源
2、我们可以通过媒体查询和媒体类型把一部分CSS标记为不阻塞渲染(媒体查询的不足就是会严重影响关键渲染路径的性能)
3、浏览器会下载所有CSS资源、无论它阻塞还是不阻塞
根据上面三个知识点,会让你很清晰的知道,CSS优化可以做的事情就是,根据不同CSS使用场景和优先级的不同进行不阻塞标记。如果是必要的CSS就请尽早加载(1. 引用位置靠前,2. 减小文件体积)到客户端,这样就减少了对首次渲染的阻塞
JavaScript的影响:
JavaScript 可以修改网页的方方面面,内容、样式以及响应用户的交互。不过,JavaScript 也会阻止DOM的构建和延缓网页渲染。我们简单了解一下JavaScript 和DOM、CSSDOM的依赖关系。
- JavaScript 能修改内容和样式
无论(内联javascript还是外部javascript文件)都会阻止DOM的构建
DOM构建过程中如果遇到(非异步加载async)的javascript标签,浏览器将会终止DOM的构建,立即执行javascript。
这就是为什么非异步执行的javascript要放在尾部或者将可执行代码要放在DOMContentLoaded回调中
因为如果该javascript代码操作了未构建完的DOM节点就会因为无法获取该节点而无法执行响应的操作。
csSOM的构建影响javascript的执行
- CSSOM的构建影响JS的执行
如果在浏览器尚未完成CSSOM的下载和构建时,去运行javascript脚本,那么浏览器会延迟脚本的执行和DOM的构建,直至完成CSSOM的下载和构建
未优化 – JavaScript正常加载
优化后 – JavaScript异步加载
根据分析可知,非必要优先加载的JS,选择异步加载是最优选择。
image的影响:
图像不会阻止首屏的渲染,但是它们会导致较差的LCP分数,而且对于它们所消耗的设备来说,它们往往过于沉重和过大。因此,为了增加用户体验我们应该考虑加载适当大小的图片,加速图片的呈现。
1-3 几个解析性能的方向:
- 分析图片
从运营方在上传图片时限制图片大小到用户端对图片进行压缩处理
-
CPU相关方面的消耗 TTI<5s —减少资源下载时间、延迟执行
-
资源用http2下载,查看网络中的资源是否有使用http1的,要统一协议
-
配置cdn引入解决未使用js
-
查看network,点开看看有没有未压缩的资源文件,对其文件进行压缩处理
-
服务端渲染方面的处理操作??
next.js(具体实现??) 或react18新特性
-
着重针对LCP做一些对应的处理
-
针对网络 低中高端分情况,再结合实际面试厂的消费情况等方面做对应优化改进
例如:针对淘宝,拼多多等,他们的主要消费人群是中低层阶级,因此网络肯定不会一直特别好,那么就可以针对网络这方面着重做一些准备
-
很多平台都有滚动方面的需求,针对这个做一些对应优化处理
-
字体也是常见项,需要做针对处理。(预加载等方案)
面试注意:
说解决方案以及为什么这么做
说一下可以快速解决的点、再详细讲几个点的很闲
1-4 解析工具
Lighthouse
Lighthouse自动化网络审查工具:是一个开源的自动化工具,用于改进网络应用的质量。当用Lighthouse提供一个要审查的网址,它将针对此页面运行一连串的测试,然后生成一个有关页面性能的报告。
**使用:**1)作为Chrome扩展程序运行,或作为命令行工具运行。 2)在Lighthouse测试网站进行测试https://www.webpagetest.org/lighthouse
运行Lighthouse能自动生成报告,主要有以下6项指标:
性能优化指标
1. FP – 浏览器第一次绘制时间
FP(First Paint): 浏览器第一次绘制时间,第一个像素时间
1. FCP – 首次内容绘制:
FCP(Fisrt Contentful Paint):这个指标用于记录页面首次绘制文本、图片、非空白 Canvas 或 SVG 的时间。
我觉得FCP指标既然是从用户输入url开始,那么其实就包括了 1)用户到HTML落地页所在服务器的时间,这里受用户网速的影响 2)服务器的nginx路由配置指向我们所要访问页面的时间,这里受路由匹配,限制等影响; 3)服务器处理HTML落地页的逻辑时间,比如是否需要在HTML落地页渲染一些变量等; 4)服务器返回HTML落地页的时间,这里受用户网速,HTML体积大小的影响(因为HTTP2有gzip压缩,一个200K的HTML落地页,被返回前压缩到了50K,等用户浏览器下载下来后,其实下载传输的是50K,但之后又需要解压缩到200K然后被浏览器所识别); 5)HTML落地页head头内容,我们知道HTML是从上向下被渲染执行的,那么head头里的无用dns预解析代码,多余的css js代码,都会是一个执行的过程。那么一直到body部分,我们设置了骨架屏,然后被用户看到。整个这个过程被认为是FCP。 作为前端开发人员,服务端那一块我们只能起到推动作用,很难去改变,真正了解掌控的还是从下载HTML这段内容开始。
其实如果说前端FCP这块呢,如果只是做一个静态HTML页面,或者是前后端还没有分离的太严重的时候,FCP这个指标值是很小的,因为后来vue和react等框架的盛行后,大家习惯于在HTML落地页写一个
根据分析,在FCP阶段,页面已经加载了loading动画。
**注意:**相比于FCP,LCP更能体现页面性能的好坏。FCP仅仅记录内容开始加载的那一刻,但那时用户希望看到的内容并未呈现,而LCP持续更新,可以告诉我们页面的主要内容何时呈现出来。
2. TTI – 首次可交互时间:
TTI(Time to Interactive):这个表示首次可交互可流畅的交互时间。
该指标计算略微复杂,需要满足以下三个条件:
- 从FCP指标后开始计算
- 持续5秒内无长任务(执行时间大于50ms)且无两个以上正在进行中的GET请求
- 往前回溯至5秒前的最后一个长任务结束的时间
其实这个指标和FCP一样都很重要,FCP可能意味着我们的页面开始渲染,但是渲染出页面不代表可以交互,很多时候我们的页面渲染出来了,但是会比较的卡顿
3. Speed Index:
速度指数衡量的是内容在页面加载过程中的视觉显示速度,得分越低越好。Lighthouse首先会在浏览器中捕获一段页面加载的视频,并计算出各帧之间的视觉进度。
4. TBT – 总阻塞时间:
TBT(Total Blocking Time):表示从FCP到TTI阶段中长任务总阻塞时间。TBT越小说明页面能够更好的快速响应用户事件。
上图表示主线程所做的任务时间线,其中任务分为长任务和短任务(长短任务的依据为执行时间是否大于50ms)
那么超过50ms的地方实际上就是阻塞时间,上面实际上的阻塞时间就是345ms。当然阻塞导致浏览器无法中断正在进行的任务,从而使得用户无法立即进行交互,从而影响体验。
5. LCP – 最大内容绘制:
LCP(Largest Contentful Paint): LCP指标测量页面的加载,并报告在视口中可见的最大图像或文本块的渲染时间。因此,LCP会受到延迟重要信息渲染的所有因素的影响,比如说服务器响应速度慢、CSS阻塞、JS阻塞、web字体加载、耗费性能的绘制和渲染、懒加载的图片、骨架屏或者客户端渲染等等。
一个好的用户体验,LCP 应该在页面第一次开始加载的 2.5 秒内发生。这意味着我们需要尽早渲染页面的第一个可见部分。
LCP 评分低的主要原因通常是图像。如果需要在 fast 3G 上提供小于 2.5s 的 LCP,基础环境是在一个优化的服务器上,所有的静态图像都不是由客服的渲染,并且图像来自于专用的 CDN,这意味着理论上的最大图像大小只有 144KB 左右,这就是为什么响应式图像很重要,以及 早期要使用 preload 预加载关键图像
LCP目前并不会计算所有元素,因为这样会使这个指标变得非常复杂,目前LCP仅关注下面的元素:
<img>元素
<image>元素内的<svg>元素
<video>元素
通过 url() 函数加载背景图片的元素
包含文本节点或其他内联文本元素子级的块级元素。
6. CLS – 累计布局偏移:
CLS(Cumulative Layout Shift):表示页面累计布局偏移。
它测量了整个页面生命周期内发生的所有意外布局偏移分数的总和。当已经可见的元素改变其在页面上的位置时,就会发生单独的布局偏移。它的得分基于内容的大小和移动的距离。
这个指标强调了用户在访问网站时遇到意料之外的布局变化的频率,它检查不稳定元素对整体体验的影响,分数越低越好。
良好体验的推荐值是 CLS<0.1。
每次出现改变,比如说后备字体和网页字体有不同的字体指标,或者广告、嵌入或后来加入的 iframe,或者图像/视频尺寸没有被保留,或者后期 CSS 强制重新绘制,后者后期 JavaScript 注入了更改,这些都会对 CLS 得分产生影响。
布局偏移分数计算:
布局偏移分数 = 影响分数 * 距离分数
影响分数:前一帧和当前帧的所有不稳定元素的可见区域集合(占总可视区域的部分),即影响分数。
距离分数(位移距离):任何不稳定元素在一帧中位移的最大距离(水平或垂直)除以可视区域的最大尺寸维度(宽度或高度,以较大者为准)。
上图中布局偏移分数=0.75 * 0.25 = 0.1875
注意,并不是所有布局移动都是不好的,很多web网站都会改变元素的开始位置,只有当布局移动是非用户预期的,才是不好的。
换句话说,当用户点击了按钮,布局进行了改动,这是ok的,CLS的JS API中有一个字段hadRecentInput,用来标识500ms内是否有用户数据,视情况而定,可以忽略这个计算。
7. FID – 交互响应时间
First Input Delay (FID):交互响应时间,可以很好的补充 TTI
衡量 UI 的响应能力,即浏览器在对一个离散的用户输入时间(如点击)做出反应之前,忙着处理其它任务所花费的时间。它的设计目的是统计由于主线程忙碌而导致的延迟,尤其是在页面加载期间。
我们的目标是 每次交互 保持在 50-100ms 以内
为了达到这个目标,我们需要做的是
- 识别出长任务(阻塞大于 50ms)并将其分解
- 进行 code-split
- 减少 js 执行时间
- 优化数据获取
- 延迟第三方脚本的执行
- 使用 Web workers 将 JavaScript 移动到后台线程
- 使用 progressive hydration 减少 SPA 中的渲染代价
获取 FID 分数的可靠策略是将主线程上的任务最小化,将较大的包拆分成较小的包,并在用户需要的时候提供相应的服务,这样用户交互就不会被延迟。
8. CPU 时间消耗
显示主线程阻塞的频率和时间,用于 绘制、渲染、脚本和加载。如果用户的操作和响应之间存在明显的延迟,则可以使用 WebPageTest 来进行测试。
9. Ad Weight Impact:广告占比的影响
谷歌提出了Core Web Vitals,是用于衡量用户体验的新指标,指标将被纳入谷歌搜索引擎的网页排名。
Core Web Vitals是用户体验和SEO的重要指标。
关键的指标包括
- LCP,用来衡量页面加载性能,为了提供良好的用户体验,LCP应该在页面首次开始加载后的2.5秒内发生
- FID,衡量交互性能,为了提供良好的用户体验,页面的FID应该小于100毫秒。
- CLS,测量视觉稳定性。为了提供良好的用户体验,页面应该保持CLS小于0.1。
注意: FID 和 TTI 都不考虑滚动行为; 滚动可以独立发生,因为它是非主线程的,所以对于许多内容消费网站来说,这些指标可能不那么重要
1-5 解析结果
第一次页面解析:
- LCP(最大内容绘制):18.7s
- **TTI(交互时间):**19.2s
- CLS(累积布局偏移):0.12
- FCP:1.1s
第二次页面解析:
第三次页面解析:
第四次页面解析:
1-5-1 如何收集数据
为了收集准确的数据,我们需要仔细的选择需要测试的设备。对于大部分公司来说,这意味着研究分析并基于最常见的设备类型创建用户配置文件。然而大部分情况下,单独的分析无法提供一个完整的场景。很大一部分目标用户可能会因为体验太慢而放弃该网站并且不再返回,而他们的设备也不太可能因此成为分析中最受欢迎的设备。所以,在目标群体中去选择一个常用的设备是更好的。 我们需要考虑一个典型的测试设备应该符合:至少使用了两年、运行缓慢的3G,RTT 在 400ms 左右和约 400kb 的传输(悲观一点)(可能跟中国没有那么符合)
在现有条件下,首先测试性能最好的选择一定是公司内部的性能监控设备;其次是浏览器上的一些免费的网络审查工具。其次,测试时使用一个干净的网站配置也是十分重要的,能够排除变量。用浏览器的无痕模式进行测试就是一个较好的选择。
1-5-2 性能优化可能存在的问题如下:
- 服务器响应速度慢
- CSS阻塞
- JS阻塞
- web字体加载
- 耗费性能的绘制和渲染
- 懒加载的图片
- 骨架屏
- 客户端渲染
针对CLS:
- 后备字体和网页字体有不同的字体指标
- 广告、嵌入或后来加入的 iframe
- 图像/视频尺寸没有被保留
- 后期 CSS 强制重新绘制
- 后期 JavaScript 注入了更改
- 等等
1-5-3 设定现实改进目标:
1、100ms 的响应时间,60fps
了让交互感觉更加流畅,页面有 100ms 来反应用户的输入事件。超过这个时间,用户就会感觉应用是滞后的。因此,每一帧动画应该在 16ms 以内完成,从而达到 60 帧率/s(1s/60 = 16.6 ms),即最好在 10ms 以内。因为浏览器需要时间在屏幕上绘制新的帧,所以你的代码应该在 16.6ms 之前完成执行。目前已经开始讨论 120 帧/s(比如 ipad 的屏幕是 120hz),Surma已经介绍了一些 120 帧/s 的渲染性能解决方案,但这还不是我们目前需要考虑的目标。
2、FID < 100ms, LCP < 2.5s, TTI < 5s on 3G, 关键文件大小 < 170KB
虽然这可能很难实现,但一个好的最终目标是 TTI 在 5s 以下,对于重复性的访问,目标在 2s 以下。还有就是 LCP 在 2.5s 以下,TBT 和 CLS 的指标尽量最小化,一个可接受的 FID 在 100ms 到 70ms(考虑的基准是 200 美元左右的安卓手机,在 3G 网络下,模拟 400msRTT 和 400kbps 的传输速度)。
二、 解决方案
图片优化(针对LCP)
webP不支持像JPEG这样的渐进渲染。使用WebP,您将减少有效负载,而使用JPEG,您将提高感知的性能
什么是呈现图像的最佳方式呢?对于插图和矢量图,(压缩的)SVG无疑是最佳选择。对于照片,我们使用图片元素的内容协商方法。如果支持AVIF,我们发送一个AVIF图像;如果不是这样,我们首先退回到WebP,如果WebP也不支持,就切换到JPEG 或 PNG 作为退回(如果需要,应用@media 条件)
(1). 响应式图片
方法一:通过 @media 实现
使用 @media 查询,你可以针对不同的媒体类型定义不同的样式,也可以针对不同的屏幕尺寸设置不同的样式。
可以在媒体查询的 CSS @media 规则 查看详情。
@media (min-width: 769px) {
.bg {
background-image: url(bg1080.jpg);
}
}
@media (max-width: 768px) {
.bg {
background-image: url(bg768.jpg);
}
}
方法二:通过 picture 实现
在 标签中使用 来设置不同屏幕显示的图片:
<picture>
<source srcset="banner_w1000.jpg" media="(min-width: 801px)">
<source srcset="banner_w800.jpg" media="(max-width: 800px)">
<img src="banner_w800.jpg" alt="">
</picture>
srcset
属性是必须的,定义了图片资源。
media
属性是可选的,可以在媒体查询的 CSS @media 规则 查看详情。
对于不支持 元素的浏览器你也可以定义 元素来替代。
方法三:srcset配合sizes,灵活使用<source>
标签
属性 | 值 | 描述 |
---|---|---|
media | media_query | 规定媒体资源的类型,供浏览器决定是否下载。 |
src | URL | 规定媒体文件的 URL。 该属性使得此代码向不支持 srcset 和 sizes 属性的浏览器提供帮助。如果浏览器不支持这些属性,则会回退到加载src 属性指定的资源。 |
type | MIME_type | 规定媒体资源的 MIME 类型。 |
sizes | 不同页面布局设置不同图片大小。 明确定义了图片在不同的media conditions下应该显示的尺寸。若不设置sizes,其默认值为sizes=“100vw”,vw(viewport width)是代表视口宽度的相对单位。这里的100代表的是百分比。sizes="100vw"表示改变图片阈值是以百分之百的视口宽度为参考,不管图片外层容器大小和图片实际宽度,都是根据百分之百的视口宽度大小来选择显示哪张图片。 | |
srcset | URL | 应用于 标签时需要使用到。 指定在不同情况下使用的图像 URL,以逗号分隔。 除了URL之外,还可以输入宽度描述。下面示例中,320W、640W都是宽度描述符,告诉浏览器对应的图片分别是320像素宽,640像素宽 |
<picture>
<source
sizes="(max-width: 608px) 100vw, 608px"
srcset="
/img/Z1s3TKV-1920w.avif 1920w,
/img/Z1s3TKV-1280w.avif 1280w,
/img/Z1s3TKV-640w.avif 640w,
/img/Z1s3TKV-320w.avif 320w
"
type="image/avif"
/>
<source
sizes="(max-width: 608px) 100vw, 608px"
srcset="
/img/Z1s3TKV-1920w.webp 1920w,
/img/Z1s3TKV-1280w.webp 1280w,
/img/Z1s3TKV-640w.webp 640w,
/img/Z1s3TKV-320w.webp 320w
"
type="image/webp"
/>
<source
sizes="(max-width: 608px) 100vw, 608px"
srcset="
/img/Z1s3TKV-1920w.jpg 1920w,
/img/Z1s3TKV-1280w.jpg 1280w,
/img/Z1s3TKV-640w.jpg 640w,
/img/Z1s3TKV-320w.jpg 320w
"
type="image/jpeg"
/>
<img src="fallback-image.jpg" alt="Photo" width="450" height="350" />
</picture>
在设置外层容器为50%的案例中,若想图片切换是根据图片尺寸为标准,需要修改如下:
<div style="width:50%;">
<img style="width:100%;" src="img/480.png" srcset="img/480.png 480w, img/800.png 800w, img/1600.png 1600w" sizes="50vw">
</div>
在 标签中使用 来设置视频:
<video width="320" height="240" controls>
<source src="movie.mp4" type="video/mp4">
<source src="movie.ogg" type="video/ogg">
Your browser does not support the video tag.
</video>
针对FID的优化
目标是 每次交互 保持在50 - 100ms 以内。
- 识别出长任务(阻塞大于 50ms)并将其分解
- 进行 code-split
- 减少 js 执行时间
- 优化数据获取
- 延迟第三方脚本的执行
- 使用 Web workers 将 JavaScript 移动到后台线程
- 使用 progressive hydration 减少 SPA 中的渲染代价
WebFont文本优化(针对FCP)
根据上述分析可知,页面内容的第一次绘制与对字体资源的请求之间的 ”竞争“ 导致了 ”空白文本问题“
1)如何避免显示不可见的文字
避免在加载自定义字体时显示不可见文本的最简单方法是临时显示系统字体。通过包含 font-display: swap
在您的@font-face
风格中,您可以在大多数现代浏览器中避免不可见文本:
@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;
}
swap
告诉浏览器使用该字体的文本应该立即使用系统字体显示。自定义字体准备就绪后,它将替换系统字体。
2)预加载网络字体
用于<link rel="preload" as="font">
更早地获取您的字体文件。
将 <link rel="preload">
与 font-display: optional
组合是保证渲染自定义字体时没有布局卡顿的最有效方法。
3)谷歌字体
将&display=swap
参数添加到您的 Google 字体 URL 的末尾:
<link href="https://fonts.googleapis.com/css?family=Roboto:400,700&display=swap" rel="stylesheet">
2.2.2 针对FCP — 减少未使用的css
- 内联关键 CSS
- 减少样式表中未使用的规则
- 延迟未用于首屏内容的 CSS,以减少网络活动消耗的字节数。
类似于内联<script>
标签中的代码,在 HTML 页面 head
的<style>
块中内联首次绘制所需的关键样式。然后使用 preload
链接异步加载其余样式。
2.2.3 针对CLS
1)始终在您的图像和视频元素上包含尺寸属性,或者通过使用CSS 长宽比容器之类的方式预留所需的空间。这种方法可以确保浏览器能够在加载图像期间在文档中分配正确的空间大小。
2)除非是对用户交互做出响应,否则切勿在现有内容的上方插入内容。这样能够确保发生的任何布局偏移都在预期之内。
3)首选转换动画,而不是触发布局偏移的属性动画。动画过渡的目标是提供状态与状态之间的上下文连续性。
CSS transform属性使您能够在不触发布局偏移的情况下为元素设置动画:
- 用transform:scale()来替代和调整height和width属性
- 如需使元素能够四处移动,可以用transform: translate()来替代和调整top、right、bottom或left属性。
2.2.4 减少未使用的JavaScript
通过运行Chrome DevTools的“Coverage(覆盖率)”选项卡,可以发现关键与非关键的JS文件、红色代表未利用,绿色代表利用。
2.2.5 图片优化
(1). 图片延迟加载
在页面中,先不给图片设置路径,只有当图片出现在浏览器的可视区域时,才去加载真正的图片,这就是延迟加载。对于图片很多的网站来说,一次性加载全部图片,会对用户体验造成很大的影响,所以需要使用图片延迟加载。
首先可以将图片这样设置,在页面不可见时图片不会加载:
<img data-src="https://avatars0.githubusercontent.com/u/22117876?s=460&u=7bd8f32788df6988833da6bd155c3cfbebc68006&v=4">
等页面可见时,使用 JS 加载图片:
const img = document.querySelector('img')
img.src = img.dataset.src
这样图片就加载出来了,完整的代码可以看一下参考资料。
参考资料:
(2). 响应式图片
方法一:srcset配合sizes
之前 | 后 |
---|---|
![]() | ![]() |
标签的 src、srcset和 sizes 属性都相互作用来实现这一最终结果。
src 属性:
该属性使得此代码向不支持srcset
和 sizes
属性的浏览器提供帮助。如果浏览器不支持这些属性,则会回退到加载src
属性指定的资源。
srcset 属性:
是以逗号分隔的图像文件名及其宽度或密度描述符的列表。此示例使用宽度描述符。480w 是一个宽度描述符,告诉浏览器 flower-small.jpg 是 480 像素宽;1080w 是一个宽度描述符,告诉浏览器 flower-large.jpg 是 1080 像素宽。
sizes 属性:
设置图片的尺寸临界点,明确定义了图片在不同的media conditions下应该显示的尺寸。若不设置sizes,其默认值为sizes=“100vw”,vw(viewport width)是代表视口宽度的相对单位。这里的100代表的是百分比。sizes="100vw"表示改变图片阈值是以百分之百的视口宽度为参考,不管图片外层容器大小和图片实际宽度,都是根据百分之百的视口宽度大小来选择显示哪张图片。
在设置外层容器为50%的案例中,若想图片切换是根据图片尺寸为标准,需要修改如下:
<div style="width:50%;">
<img style="width:100%;" src="img/480.png" srcset="img/480.png 480w, img/800.png 800w, img/1600.png 1600w" sizes="50vw">
</div>
方法二:通过 picture 实现
<picture>
<source srcset="banner_w1000.jpg" media="(min-width: 801px)">
<source srcset="banner_w800.jpg" media="(max-width: 800px)">
<img src="banner_w800.jpg" alt="">
</picture>
方法三:通过 @media 实现
@media (min-width: 769px) {
.bg {
background-image: url(bg1080.jpg);
}
}
@media (max-width: 768px) {
.bg {
background-image: url(bg768.jpg);
}
}
(3). 调整图片大小
例如,你有一个 1920 * 1080 大小的图片,用缩略图的方式展示给用户,并且当用户鼠标悬停在上面时才展示全图。如果用户从未真正将鼠标悬停在缩略图上,则浪费了下载图片的时间。
所以,我们可以用两张图片来实行优化。一开始,只加载缩略图,当用户悬停在图片上时,才加载大图。还有一种办法,即对大图进行延迟加载,在所有元素都加载完成后手动更改大图的 src 进行下载。
(4). 降低图片质量
例如 JPG 格式的图片,100% 的质量和 90% 质量的通常看不出来区别,尤其是用来当背景图的时候。我经常用 PS 切背景图时, 将图片切成 JPG 格式,并且将它压缩到 60% 的质量,基本上看不出来区别。
压缩方法有两种,一是通过 webpack 插件 image-webpack-loader,二是通过在线网站进行压缩。
以下附上 webpack 插件 image-webpack-loader 的用法。
npm i -D image-webpack-loader
webpack 配置
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use:[
{
loader: 'url-loader',
options: {
limit: 10000, /* 图片大小小于1000字节限制时会自动转成 base64 码引用*/
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
/*对图片进行压缩*/
{
loader: 'image-webpack-loader',
options: {
bypassOnDebug: true,
}
}
]
}
(5). 尽可能利用 CSS3 效果代替图片
有很多图片使用 CSS 效果(渐变、阴影等)就能画出来,这种情况选择 CSS3 效果更好。因为代码大小通常是图片大小的几分之一甚至几十分之一。
参考资料:
(6). 调整图片大小
WebP 的优势体现在它具有更优的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量;同时具备了无损和有损的压缩模式、Alpha 透明以及动画的特性,在 JPEG 和 PNG 上的转化效果都相当优秀、稳定和统一。
参考资料:
(7). 用SVG替换复杂的图标
对于更复杂的图标要求,SVG 图标通常是轻量级的、易于使用的,并且可以使用 CSS 进行样式设置。与光栅图像相比,SVG 有许多优点:
- 它们是可以无限缩放的矢量图形。
- 颜色、阴影、透明度和动画等 CSS 效果很简单。
- SVG 图像可以直接内联在文档中。
- 它们是语义的。
- 它们通过适当的属性提供更好的可访问性。
2.2.6 善用缓存,不重复加载相同的资源
为了避免用户每次访问网站都得请求文件,我们可以通过添加 Expires 或 max-age 来控制这一行为。Expires 设置了一个时间,只要在这个时间之前,浏览器都不会请求文件,而是直接使用缓存。而 max-age 是一个相对时间,建议使用 max-age 代替 Expires 。
不过这样会产生一个问题,当文件更新了怎么办?怎么通知浏览器重新请求文件?
可以通过更新页面中引用的资源链接地址,让浏览器主动放弃缓存,加载新资源。
具体做法是把资源地址 URL 的修改与文件内容关联起来,也就是说,只有文件内容变化,才会导致相应 URL 的变更,从而实现文件级别的精确缓存控制。什么东西与文件内容相关呢?我们会很自然的联想到利用数据摘要要算法对文件求摘要信息,摘要信息与文件内容一一对应,就有了一种可以精确到单个文件粒度的缓存控制依据了。
参考资料:
2.2.7. 压缩文件
压缩文件可以减少文件下载时间,让用户体验性更好。
得益于 webpack 和 node 的发展,现在压缩文件已经非常方便了。
在 webpack 可以使用如下插件进行压缩:
- JavaScript:UglifyPlugin
- CSS :MiniCssExtractPlugin
- HTML:HtmlWebpackPlugin
其实,我们还可以做得更好。那就是使用 gzip 压缩。
首先,安装compression-webpack-plugin
cnmp i compression-webpack-plugin -D
// 在vue.congig.js中引入并修改webpack配置
const CompressionPlugin = require('compression-webpack-plugin')
configureWebpack: (config) => {
if (process.env.NODE_ENV === 'production') {
// 为生产环境修改配置...
config.mode = 'production'
return {
plugins: [new CompressionPlugin({
test: /\.js$|\.html$|\.css/, //匹配文件名
threshold: 10240, //对超过10k的数据进行压缩
deleteOriginalAssets: false //是否删除原文件
})]
}
}
在服务器我们也要做相应的配置
如果发送请求的浏览器支持gzip,就发送给它gzip格式的文件
# 开启gzip
gzip on;
# 启用gzip压缩的最小文件,小于设置值的文件将不会压缩
gzip_min_length 1k;
# gzip 压缩级别,1-9,数字越大压缩的越好,也越占用CPU时间,后面会有详细说明
gzip_comp_level 5;
# 进行压缩的文件类型。javascript有多种形式,后面的图片压缩不需要的可以自行删除
gzip_types application/atom+xml application/geo+json application/javascript application/x-javascript application/json application/ld+json application/manifest+json application/rdf+xml application/rss+xml application/xhtml+xml application/xml font/eot font/otf font/ttf image/svg+xml text/css text/javascript text/plain text/xml;
# 是否在http header中添加Vary: Accept-Encoding,建议开启
gzip_vary on;
# Nginx作为反向代理的时候启用
gzip_proxied any;
# 将接收压缩文件的浏览器中排除Internet Explorer 6,因为IE6根本不支持gzip
gzip_disable "msie6";
# 设置系统获取几个单位的缓存用于存储gzip的压缩结果数据流。例如 4 4k 代表以4k为单位,按照原始数据大小以4k为单位的4倍申请内存,4 8k 代表以8k为单位,按照原始数据大小以8k为单位的4倍申请内存。如果没有设置,默认值是申请跟原始数据相同大小的内存空间去存储gzip压缩结果。
gzip_buffers 16 8k;
# 识别http的协议版本。由于早期的一些浏览器或者http客户端,可能不支持gzip自解压,用户就会看到乱码,所以做一些判断还是有必要的。
gzip_http_version 1.1;
# 设置压缩所需要的缓冲区大小
gzip_buffers 4 16k;
# 重启nginx
systemctl restart nginx
此外,附上 webpack 和 node 配置 gzip 的使用方法。
下载插件
npm install compression-webpack-plugin --save-dev
npm install compression
webpack 配置
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
plugins: [new CompressionPlugin()],
}
node 配置
const compression = require('compression')
// 在其他中间件前使用
app.use(compression())
2.2.8. 设置productionSourceMap:false
修改vue.config.js中的配置
module.exports = {
outputDir: `${srcFile}`, // 在npm run build时 生成文件的目录 type:string, default:'dist'
productionSourceMap: false, // 是否在构建生产包时生成 sourceMap 文件,false将提高构建速度
}
如果不加false,则在打包的过程中会出现map文件,该文件的主要作用是让我们打包后的文件像未加密的代码一样,可以准确的输出相关的错误信息。默认情况下productionSourceMap为true。设置为false后,项目打包过后,代码都是经过压缩加密的,如果运行时报错,输出的错误信息无法准确得知代码具体哪里出错。
2.2.9. 关闭Prefetch
因为vuecli 3默认开启prefetch(预先加载模块),提前获取用户未来可能会访问的内容
在首屏会把这十几个路由文件,都一口气下载了。
**注意:**prefetch其实并不会影响首页的加载速度,只是优化子页面
使用场景:当对流量有限制时可以使用,比如移动端,只用查看首页或者其它并不是全部页面的时候,使用perfetch可能会导致流量的不必要损耗。
所以我们要关闭这个功能,在vue.config.js中设置
// vue.config.js
module.exports = {
chainWebpack: config => {
// 移除 prefetch 插件
config.plugins.delete('prefetch')
// 或者
// 修改它的选项:
config.plugin('prefetch').tap(options => {
options[0].fileBlacklist = options[0].fileBlacklist || []
options[0].fileBlacklist.push(/myasyncRoute(.)+?\.js$/)
return options
})
}
}
2.2.10. element-ui组件按需加载
项目在打包时大家经常可以发现element-ui占用了很大一部分,因此如果在已知不会使用太多组件的情况下,可以考虑对组件进行element-ui按需加载。
2.2.11 使用CDN外部加载资源 vue, vuex, vue-router,axios
一般小公司可能为了方便直接把CSS、JS、图片等文件直接传到服务器上进行访问,使用CDN的优势在于CDN是位于全球不同地方的高性能网络服务,全国的各个地方都会有服务节点,而且CDN也会缓存文件,所以通过CDN访问静态文件比直接访问服务器文件要快上几倍。
淘宝的图片访问,有98%的流量都走了CDN缓存。只有2%会回源到源站,节省了大量的服务器资源。
对于vue, vuex, vue-router,axios等我们可以利用wenpack的externals参数来配置,这里我们设定只需要在生产环境中才需要使用:
// vue.config.js中的核心代码
const isProduction = process.env.NODE_ENV === 'production';
const cdn = {
css: [],
js: [
'https://cdn.bootcss.com/vue/2.5.17/vue.runtime.min.js',
'https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js',
'https://cdn.bootcss.com/vuex/3.0.1/vuex.min.js',
'https://cdn.bootcss.com/axios/0.18.0/axios.min.js',
]
}
module.exports = {
chainWebpack: config => {
// 生产环境配置
if (isProduction) {
// 生产环境注入cdn
config.plugin('html')
.tap(args => {
args[0].cdn = cdn;
return args;
});
}
},
configureWebpack: config => {
if (isProduction) {
// 用cdn方式引入
config.externals = {
'vue': 'Vue',
'vuex': 'Vuex',
'vue-router': 'VueRouter',
'axios': 'axios'
}
}
},
}
index.html里的相关核心代码
<!-- 使用CDN的CSS文件 -->
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
<link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="preload" as="style">
<link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet">
<% } %>
<!-- 使用CDN的JS文件 -->
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
<link href="<%= htmlWebpackPlugin.options.cdn.js[i] %>" rel="preload" as="script">
<% } %>
2.2.12 利用浏览器缓存
浏览器缓存是浏览器保存数据用于快速读取或避免请求重复资源,这有助于访客再次访问时,直接从缓存中读取内容而不必重新加载,提升了网页加载速度。有以下常用缓存方法:
1)localstorage
localStorage 是HTML5的一种新的本地缓存方案,目前使用比较多,一般存储ajax返回的数据,存储特点主要有:
- 数据可以长久保存,没有有效期,直到手动删除为止。
- 存储的数据量大,一般5M以内。
- 存储的数据可以在同一个浏览器的多个窗口使用。
- 存储的数据不会发送到服务器。
localStroage常用API如下:
localStorage.setItem(key,value) // 保存数据
localStorage.getItem(key) // 获取数据
localStorage.removeItem(key) // 删除单个数据
localStorage.clear() // 删除全部
2)sessionstorage
sessionStorage与上述localStroage类似,它的特点主要有:
- 存储的数据在浏览器关闭后删除,与网页窗口具有相同的生命周期。
- 可以存储的数据大小5M。
- 存储的数据不会发送到服务器。
sessionStorage常用API如下:
sessionStorage.setItem(key,value) // 保存数据
sessionStorage.getItem(key) // 获取数据
sessionStorage.removeItem(key) // 删除单个数据
sessionStorage.clear() // 删除全部
3)cacheStorage
cacheStorage 表示 cache对象的存储。该接口提供 serviceWorker 或其他类型的工作线程或window范围访问的所有命名缓存的主目录。
CacheStorage常见方法:
CacheStorage.match() - 检查给定的 Request 对象是否是 CacheStorage 对象跟踪的 Cache 对象中的键,返回Promise
CacheStorage.has() - 返回一个 Promise,它解析为与 cacheName 相匹配的 Cache 对象。
CacheStorage.delete() - 删除cache对象
CacheStorage.keys() - 含有keys中字符串的任意一个,则返回一个promise对象。
cacheStorage.has() - 如果包含cache对象,则返回一个promise对象。
4)cookie
cookie指的就是会话跟踪技术。一般指网站为了辨别用户身份,进行session跟踪而而存储在用户本地终端上的数据,cookie一般通过http请求头发送到服务器。cookie主要特点有:
- 跨域限制,同一个域名下可多个网页内使用。
- cookie可以设置有效期,超出有效期自动清除。
- cookie存储大小在4K以内。
- cookie的存储不能超过50个cookie。
- 只能存储字符串类型。
cookie常用操作:
setMaxAge - 设置cookie的有效期,时间单位是秒,负值时表示关闭浏览器后就失效,默认值为-1。
setDomain - 用于指定,只有请求指定域名才会带上该cookie。
setPath - 只有访问该域名下的cookieDemo的这个路径地址才会带cookie。
setValue - 重置 value 。
此外,还有http缓存,websql,indexDB,flash缓存,application cache等。具体查看 前端性能优化(三)——浏览器九大缓存方法