优化内容通过以下两种方式:
- 避免不必要的下载。
- 通过各种压缩技术优化每个资源的传送编码以及尽可能利用缓存来避免多余的下载。
避免不必要的下载
- 梳理网页上的自有资源和第三方资源。
- 评估每个资源的表现:其价值及其技术性能。
- 确定这些资源是否提供了足够的价值。
可以提前假设一些问题并定期检验:
- 网页上一直包含资源 X,但它提供给用户的价值能否抵消下载并显示它的开销呢?是否能够评估并证明其价值?
- 该资源(特别是第三方资源)能否保持稳定的性能?该资源是否处于或是否需要处于关键路径中?如果该资源处于关键路径中,是否会成为我们网站的单点故障?也就是说,如果该资源不可用,是否会影响网页的性能和用户体验?
- 该资源是否遵循性能最佳做法:压缩、缓存等?
文本资源优化
通过 GZIP 压缩文本
- GZIP 对基于文本的资源的压缩效果最好:CSS、JavaScript 和 HTML。
- 所有现代浏览器都支持 GZIP 压缩,并且会自动请求该压缩。
- 某些 CDN 需要特别注意以确保 GZIP 已启用。
特别注意:将文件压缩源码后(产生文件名中包含“.min”的文件),再使用 GZIP 进行压缩,可进一步提高压缩率。
- 先应用内容特定优化:CSS、JS 和 HTML 压缩源码程序。
- 采用 GZIP 对压缩源码后的输出进行压缩。
图像资源优化
消除和替换图像
- 消除多余的图像资源
- 尽可能利用 CSS3 效果
- 使用网页字体取代在图像中进行文本编码
矢量与光栅图像
- 矢量图像最适用于包含几何形状的图像
- 矢量图像与缩放和分辨率无关
- 光栅图像应用于包含大量不规则形状和细节的复杂场景
矢量图放大
光栅图放大
- 矢量图形使用线、点和多边形来表示图像。
- 光栅图形通过对矩形格栅内的每个像素的值进行编码来表示图像。
矢量格式最适用于包含简单几何形状(例如徽标、文本、图标等)的图像,能够在任何分辨率和缩放设置下呈现清晰的效果,因此这种格式最适用于高分辨率屏幕和需要以不同尺寸显示的资源。 如果场景复杂(例如照片),矢量格式就不能满足要求了:描述所有形状所需的 SVG 标记量可能高得离谱,即便如此,输出效果可能仍然无法达到“照片级真实感”。 如果出现这种情况,应该使用光栅图片格式(例如 GIF、PNG、JPEG 或 JPEG-XR 和 WebP 等格式)。 光栅图像没有与分辨率或缩放无关这么好的属性,当放大光栅图像时,图形会出现锯齿并且模糊不清。 因此,可能需要在不同分辨率下保存多个版本的光栅图像。
高分辨率屏幕的含义
首先需要分清不同类型的像素:CSS 像素和设备像素。 单个 CSS 像素可能包含多个设备像素。例如,单个 CSS 像素可能直接对应于单个设备像素,也可能依托于多个设备像素。 这是什么意思?那就是,设备像素越多,屏幕上所显示内容的细节就越丰富。
因为光栅图像是以像素为单位编码图像数据,因此,像素数越大,光栅图像的文件大小就越大。 例如,让我们看一看以 100x100 (CSS) 像素显示的照片资源之间的差异:
屏幕分辨率 | 总像素数 | 未压缩文件大小(每像素 4 字节) |
---|---|---|
1x | 100 x 100 = 10,000 | 40,000 字节 |
2x | 100 x 100 x 4 = 40,000 | 160,000 字节 |
3x | 100 x 100 x 9 = 90,000 | 360,000 字节 |
如果我们将物理屏幕的分辨率加倍,总像素数将成平方级增长。
但是对于矢量图来说,是与分辨率无关的。
总结: 尽可能优先使用矢量图像,因为这类图像与分辨率无关,并且能够始终提供清晰的效果,而如果需要使用光栅图像,需要提供多个分辨率下的图像。
优化矢量图像
- SVG 是一种基于 XML 的图像格式
- SVG 文件应进行缩减,以减小其大小
- SVG 文件应使用 GZIP 进行压缩
优化光栅图像
- 光栅图像是像素栅格
- 每个像素都编码了颜色和透明度信息
- 图像压缩程序使用各种方法来减少每个像素所需的位数,以减小图像的文件大小
光栅图像就是一个 2 维“像素”栅格,例如,100x100 像素的图像是 10,000 个像素的序列, 而每个像素又存储有“RGBA”值:(R) 红色通道、(G) 绿色通道、(B) 蓝色通道和 (A) alpha(透明度)通道。
无损图像压缩与有损图像压缩
- 由于人眼的工作方式的缘故,可以对图像进行有损压缩。
- 图像优化依赖有损和无损压缩来实现。
- 图像格式上的差异是由于优化图像时使用的有损和无损算法的差异和使用方式的差异所致。
- 并不存在任何适用于所有图像的最佳格式或“质量设置”:每个特定压缩程序与图像内容的组合都会产生独特的输出。
典型的图像优化过程由两个步骤组成:
- 使用“有损”过滤器处理图像,去除某些像素数据
- 使用“无损”过滤器处理图像,对像素数据进行压缩
第一步是可选步骤,具体算法将取决于特定的图像格式,但一定要了解,任何图像都可通过有损压缩步骤来减小其大小。
由于对图像进行编码所使用的算法不同,不同图片格式的质量级别无法直接进行比较:质量级别为 90 的 JPEG 与质量级别为 90 的 WebP 产生的效果截然不同。 实际上,即使是同一图像格式质量级别,也可能因压缩程序实现方法不同而产生明显不同的效果!
选择正确的图像格式
获得普遍支持的图片格式有三种:GIF、PNG 和 JPEG。 除了这些格式外,一些浏览器还支持较新的格式(例如 WebP 和 JPEG XR),它们的总体压缩率更高,提供的功能也更多。 可以根据以下策略选取图像格式:
图像优化总结
- 首选矢量格式:矢量图像与分辨率和缩放无关,这使其成为多设备和高分辨率情况的完美选择。
- 和压缩 SVG 资源:大多数绘图应用生成的 XML 标记往往包含可以移除的多余元数据;确保服务器配置对 SVG 资源的 GZIP 压缩。
- 挑选最佳光栅图像格式:根据不同功能选择适合每个特定资源的格式。
- 通过试验为光栅格式找到最佳质量设置:调整无损和有损压缩的比例。
- 移除多余的图像元数据:许多光栅图像都包含多余的资产元数据:地理信息、相机信息等。
- 提供缩放的图像:调整服务器上的图像尺寸,并确保图像的“显示”尺寸尽可能接近其“显示”尺寸。
- 自动化:可以确保所有图像资产始终得到优化。(具体可查看gulp或webpack图像处理插件)
JavaScript 启动优化
需要从两方面考虑javascript启动优化:
- 下载
- 执行成本
网络传输
可以通过以下方法降低 JavaScript 的网络传输成本:
- 仅发送用户所需的代码。
- 使用代码拆分将 JavaScript 分解成关键部分和非关键部分。 webpack - 等模块捆绑程序支持代码拆分。
- 延迟加载非关键代码。
- 源码压缩
- 使用 UglifyJS 来压缩 ES5 代码。
- 使用 babel-minify 或 uglify-es 来压缩 ES2015+。
- 压缩
- 至少使用 gzip 来压缩基于文本的资源。
- 移除未使用的代码。
- 识别可以使用 DevTools 查看代码覆盖来移除或延迟加载代码。
- 缓存代码以最大限度减少网络往返次数。
- 使用 HTTP 缓存来确保浏览器缓存有效响应。 确定脚本的最佳缓存时间 (max-age),并提供验证 (ETag) 以避免传输未更改的字节。
- 使用长期缓存以避免重新获取尚未更改的资源。 如果使用 Webpack,根据webpack缓存配置进行优化。
解析/编译
下载 JavaScript 之后,JS 引擎解析/编译此代码的是js启动最大的耗时。 如下图所示:
花费很长时间来解析/编译代码会严重推迟用户与网站交互的时间。就字节而言,浏览器处理 JavaScript 的成本高于 相同大小的图像或网页字体 - Tom Dale
与 JavaScript 相比,处理相同大小的图像涉及多项处理(仍要对其进行解码!),但在普通移动硬件上,JS 更有可能对页面的交互性产生负面影响。
JavaScript 与图像字节的成本大不相同。 在解码和光栅化图像的过程中,通常不会阻止主线程或界面交互。 但是,JS 可能会因为解析、编译和执行成本而延迟交互。总结:
- 从页面中移除非关键 JavaScript 可以减少传输时间、CPU 密集型解析和编译以及潜在的内存开销。 这也有助于加快页面的交互速度。
- 可以将 JavaScript 分成小型代码段,以避免锁定主线程。
其他因素
- 内存。 页面可能会因为 GC(垃圾回收)而出现卡顿或频繁暂停现象。 当浏览器收回内存时,就会暂停执行 JS,因此频繁的垃圾回收浏览器会导致任务暂停执行。 应避免内存泄漏和频繁的 GC 暂停,以消除页面卡顿。
- 在运行时,长时间运行的 JavaScript 可能会阻塞主线程,从而导致页面无响应。 将工作分为较小的块(使用 requestAnimationFrame() 或 requestIdleCallback() 排程)可以最大限度减少无响应问题。
第三方JavaScript库优化
三方库可能存在以下问题:
- 它们可能是性能问题
- 它们可能是隐私问题
- 它们可能是安全问题
- 它们可能在无法预测的情况下发生变化
- 他们可能会产生意想不到的后果
第三方脚本的包括:
- 分享按钮(例如 微博,qq分享)
- 嵌入视频播放器
- 广告iframe
- 分析和日志脚本
- A/B Test测试脚本
- 工具库 (例如日期格式,动画等)
第三方脚本导致性能下降的原因:
- 向多个服务器发出过多的网络请求。网站提出的请求越多,加载时间就越长。
- 发送太多的JavaScript ,使主线程忙。太多的JavaScript会阻止DOM的构建,从而延迟了页面呈现的速度。占用大量CPU资源的脚本的解析和执行可能会导致用户交互延迟。
- 发送未经优化的大型图像文件或视频。
- 随意加载第三方脚本可能是单点故障
- HTTP缓存不足,经常迫使从网络中获取资源
- 服务器未开启压缩
- 阻止内容显示,直到它们完成处理。
- 使用已知对用户体验有害的旧版API(例如 document.write())
- 过多的DOM元素或昂贵的CSS选择器。
- 包含多个第三方嵌入会导致多个框架和库被拉多次。这是浪费的,并且加剧了性能问题。
- 第三方脚本通常嵌入页面, 如果它们的服务器响应缓慢,即使它们使用的是异步或延迟,它们也会阻止 window.onload。
有效地加载第三方脚本
- 使用async或defer属性加载脚本,以避免阻止文档解析。
- 使用async,浏览器将在继续解析HTML文档的同时异步下载脚本。脚本完成下载后,脚本执行时将阻止解析。
- 使用defer,浏览器在继续解析HTML文档的同时异步下载脚本。该脚本在解析完成之前不会运行。
-
如果第三方服务器运行缓慢,请考虑自托管脚本。
-
或对托管第三方脚本的域执行DNS查找。
-
对托管第三方脚本的域执行DNS查找。最终提出请求后,由于已经执行了DNS查找,因此可以节省时间。
<link rel="dns-prefetch" href="http://example.com">
-
如果引用的第三方域使用HTTPS,可以考虑如下操作,因为这将执行DNS查找、解析TCP并处理TLS协商。这些步骤可能很慢,因为它们涉及SSL证书进行验证。
<link rel="preconnect" href="https://cdn.example.com">
-
-
使用iframe加载脚本 可以将第三方脚本直接加载到iframe中。通过将此类脚本限制为iframe,它们将不会阻止主页的执行。但是,这种方法仍会阻止onload事件,因此不要将关键功能附加到onload。
-
通过检测元素位置加载脚本 利用IntersectionObserver检测元素何时进入或退出浏览器的视口并加载脚本。
避免使用的第三方脚本
- 避免document.write
- 避免使用脚本全局变量污染