Web性能优化-II
关于
𝐈𝐦𝐚𝐠𝐞 功能:具有不同的文件格式、响应式图像标记、手动和自动优化、延迟加载
功能:模块化、异步延迟、延迟加载、最小化
器 功能:模块化、关键 CSS、使用 onload 和 disabled 属性。
词汇表
- 浅景深——焦点区域非常小。
- 有损和无损图像 - 有损压缩会导致质量和文件大小损失,而无损压缩不会造成质量损失,但会导致文件大小增大。
- 透明度/不透明度——图像清晰,可以吸收其后面任何图像的效果
- 渲染阻塞- JS 停止 DOM 渲染。
图像优化
图片是导致网络速度缓慢的主要原因。我们面临着两个相互冲突的需求:我们希望在线发布高质量的图片,同时也希望我们的网站和应用性能出色,而图片正是导致它们性能低下的主要原因。那么,该如何解决这个难题呢?答案是多管齐下,从压缩到精心选择图片格式,再到如何在应用程序中标记和加载图片。
图像性能取决于图像中包含的数据量以及压缩数据的难易程度。图像越复杂,显示所需的数据集就越大,压缩难度也就越大。景深越浅,性能就越好。对于产品、头像、纪录片等摄影,较浅的景深更佳。
如果您想最大限度地提升图像性能,将每张图片的尺寸缩小 87%,然后再放大 115%,实际上也会影响图像的性能。事实证明,将照片缩小 87% 后,Photoshop 会去除像素并简化图像,从而缩小图像尺寸并降低其复杂度;而将图像放大 115% 后,图像质量却保持得足够好,以至于人眼无法分辨出差异。因此,我们得到了一张大小相同但复杂度显著降低的图像。
您选择的图片格式或文件类型会直接影响性能。在网络上,我们通常使用 JPEG、PNG、GIF、SVG 和 webP 这五种格式之一。
JPG/JPEG
- 适合照片
- 可调节压缩的有损图像
- 高压缩意味着大量的伪影(失真)
- 当无法使用 WebP 时,请使用照片
巴布亚新几内亚
- 适用于图形
- 无损图像格式
- 可选透明 alpha 层
- 用于计算机生成的图形和透明度
动图
- 适用于简单的低保真图像
- 有损图像格式
- 256色
- 可以制作动画(但不要使用它们)
- SVG/视频始终是更好的选择
SVG
- 适用于高级可扩展图形
- 以标记语言编写,可包含在 HTML、CSS 中
- 优化后非常小
- 用于基于矢量的计算机生成图形和图标
webP
- 适用于基于网络的照片
- 比 JPG 尺寸小 34%
- 不支持旧版浏览器(需要回退)
- 用于照片和复杂的细节图像(带后备功能)
如何选择使用什么?
- 对于照片,请使用 webP(带 JPG 后备)
- 对于过于复杂的计算机图形,请使用 PNG 或 JPG(以较小者为准)
- 对于具有透明度的图形,请使用 PNG 或 webP
- 对于可扩展的计算机图形、图标和图形,请使用 SVG
- 尽量避免使用动画 GIF,改用视频
手动年度优化
- 确定图片在布局中的最大可见尺寸。任何图片的显示宽度都不应超过全高清显示器的 1920 像素。确保将图片的显示宽度也限制为 1920 像素,并将其居中对齐。确定图片宽度后,请缩放图片以适应该尺寸。
- 在 webP、JPG 中进行压缩实验
- 通过删除不必要的点和线来简化 SVG
- 比较计算机图形的 JPG、webP 和 PNG 文件大小
自动优化
- Imagemin是一个不错的选择。您可以使用它在 Node.js 中构建自定义优化功能,或者将自动图像优化功能添加到您首选的构建流程中。Imagemin CLI 为 JPEG、PNG 和 GIF 提供无损压缩。
- 您可以使用插件为它们每个添加专用的有损压缩:Imagemin-mozjpeg用于 JPEG。Imagemin -pngquant用于 PNG,Imagemin-webp用于 webP。
- Squoosh使用各种压缩算法来优化图片。它有一个实验性的 CLI,你可以用它来自动化这个过程。
- 夏普也可供使用。
即使是经过全面优化的图片,如果在错误的时间推送到错误的浏览器,也会降低网站的性能。响应式图像标记正是要解决这个问题。
我们有响应式图像属性:srcset和size。
源集允许您提供供浏览器选择的图像源列表,而 size 定义一组媒体条件(例如屏幕宽度)并指示在某些媒体条件为真时最好选择哪种图像尺寸。 W 表示每个图像的总像素宽度。
例如: 如果浏览器的视口宽度为 800 像素。浏览器将选择 1200 像素宽的图像,因为它是最接近的尺寸。如果您选择通过放大浏览器窗口来放大视口。如果需要,浏览器将自动下拉更大版本的图像以填充空间。但现在重要的是,通过仔细规划图像尺寸,您现在可以向所有浏览器和所有设备提供适当大小的图像文件。
但是,对于大多数图片来说,其实际显示宽度是由 CSS 和媒体查询决定的。而且,你很少会在浏览器中将所有图片全宽显示。为了解决这个问题,我们创建了 Sizes 属性。Sizes 属性保存了一系列媒体查询及其对应的宽度。
对于这张图片,如果视口宽度为 1200 像素或更宽,则图片的实际显示宽度始终为 1200 像素。我之所以仍然提供 1920 像素的图片,是为了在更高分辨率的显示器上提供更高分辨率的图片。属性 size 末尾的 100 VW 表示,在所有其他条件下,即屏幕宽度小于 1200 像素时,图片始终为全宽,因为这是一个响应式布局。
当你的设计中图片的最大尺寸小于视口宽度时,这一点尤其重要。网络上几乎所有图片都是如此。
图像延迟加载
加载用户从未滚动到的图像、视频和 iframe 一直是 Web 上的主要性能问题。我们只是在浪费不该浪费的数据。为了解决这个问题,开发人员开始添加延迟加载 JavaScript 库,这些库会等待用户滚动到元素附近后再由浏览器加载图像,这样浏览器就不会加载页面上的所有图像,而只会加载用户在视口内实际能够看到的图像。 原生延迟加载是使用相关元素上的加载属性来激活的。延迟加载意味着只有当资源靠近视口时才会加载,而急切加载意味着即使资源不在视口附近也会立即加载。这里还有一个名为 auto 的后备属性,但它尚未包含在规范中。现在,此加载属性也是非破坏性的,这意味着不理解此属性的旧版浏览器会直接忽略它并像平常一样加载所有资源。如果您还希望在旧版浏览器中支持延迟加载,则可以使用像lazysizes这样的 JavaScript 解决方案,它有一个名为 nativeloading 的扩展插件,该插件仅为不支持加载属性和新内置延迟加载功能的浏览器提供 JavaScript 解决方案。
JavaScript 优化
我们编写的代码是为人类优化的,但如果我们希望代码运行速度尽可能快并保持高性能,就需要重写代码以提高代码的大小和效率,而这会导致代码对人类来说难以阅读。现在,我们可以通过代码压缩器、打包器、捆绑器等工具来完成这项工作。至少,您需要一个开发轨道来存储人类可读的代码,以及一个生产轨道来存储高度优化和压缩的机器可读代码。
如何以及何时压缩、打包、加载、模块化和执行 JavaScript,对于提升性能正变得越来越重要。CSS 也是如此。模块化和内联 CSS、渐进式加载以及其他性能技术如今已成为确保网站或应用程序的样式不会降低其交付速度的关键。
现代 Web 平台支持 JavaScript 模块,将 JavaScript 文件拆分成独立的文件,以便导出和导入对象、函数和其他基本函数。因此,将所有 JavaScript 打包成一个大文件在现代 Web 上毫无意义。
因此,从性能角度来看,应该这样做。首先,加载所有必要的关键 JavaScript,以使应用框架启动并运行,并在首屏显示内容。完成后,用户可以看到内容,再加载所有必要的 JavaScript 模块。从现在开始,浏览器应该仅在 JavaScript 模块变得相关时才逐步加载它们。JavaScript
功能应尽可能模块化,并拆分成专用文件。
这种方法的几个直接好处是:
- React 使用组件。JavaScript 模块也完全一样。只不过它们在 Web 平台上运行,不需要打包工具就能运行。
- 模块化使得持续开发变得更容易,因为它提供了清晰的关注点分离。
- 模块化、JavaScript 和仅在需要时加载模块,在初始加载时带来显著的性能优势。
- 模块化意味着更新 JavaScript 应用中的某些功能时,浏览器无需再次下载整个应用包。它只需要下载包含其功能的更新模块文件,而这个文件体积要小得多。
浏览器何时以及如何加载遇到的每个 JavaScript 文件对性能和功能都有重大影响。
如果我们将 JavaScript 添加到 HTML 文档的头部,浏览器遇到它时它总是会立即加载并执行,而这总是发生在文档主体渲染之前。这总是会导致渲染阻塞。
为了防止这种阻塞,JavaScript 被添加到了 body 元素的最底部,但这也会导致渲染阻塞,因为浏览器一旦遇到 JavaScript 的引用,就会停止执行任何操作,下载整个脚本,然后执行该脚本,最后返回渲染。所以,基本上整个页面会在 JavaScript 加载之前就加载完毕,这只会加剧性能问题。
我们使用async和defer关键字,它们指示浏览器在 DOM 渲染过程中异步加载 JavaScript 文件,并在文件可用时立即执行;或者异步加载文件并延迟执行,直到 DOM 渲染完成。 添加 async 标签后,浏览器将异步加载 JavaScript,这意味着它会与 HTML 解析过程同时加载。脚本完全加载后,浏览器会停止 HTML 渲染,直到脚本执行完毕后再继续渲染。我们已经看到了显著的性能提升,因为在脚本下载过程中解析过程不会暂停。
在 JavaScript 和其他编程语言中,同步事件意味着一个事件接一个事件地发生,形成一个链式事件。异步事件意味着事件彼此独立发生,一个事件无需等待另一个事件完成即可发生。
在异步 JavaScript 加载的情况下,加载是异步的,但执行是同步的。
在加载 JavaScript 时使用异步,无需等待先创建整个 DOM。
Defer 略有不同。当浏览器遇到脚本时,我们仍然会异步加载它,而不会阻塞渲染。然后,我们实际上会将 JavaScript 的执行推迟到 HTML 解析完成。
这实际上与将脚本标签放在 body 元素的末尾相同,只是脚本是异步加载的,因此性能更佳,因为我们不需要渲染整个 HTML 然后再下载 JavaScript。JavaScript 已经下载好了。
如果您需要等待整个 DOM 加载完毕后再执行 JavaScript 或者 JavaScript 可以等待,请使用 defer。
以下是以性能为中心的 JavaScript 加载最佳实践。
- 通过将脚本标签放在头部来调用 JavaScript
- 任何时候在头部加载 JavaScript,总是将 async 放在那里,除非您有理由使用 defer。
- 推迟任何需要完全构建 DOM 的脚本或可以推迟的脚本,因为它们不需要立即执行。
- 当且仅当您需要支持旧版浏览器并且不能让浏览器等待时,才以旧方式在页脚中加载脚本并承受性能损失。
仅当使用import语句进行交互并且需要时,才延迟加载 JavaScript 模块及其相关资产。
例如:
import("/path/to/import-module.js")
.then((module) => {
// do something with the module
});
这样,您就不必链接事件,也不必让所有内容根据用户的行为有条件地工作。 因此,您可以为用户节省大量数据,并且只在需要时将内容推送到浏览器。
整个概念可以与任何 JavaScript 模块一起使用,包括外部ESM 模块。
要重写所有内容并将其转换为高度优化的人类不可读的代码,我们可以使用 minifiers 和 uglifiers。 所有主要的捆绑程序(包括 webpack、rollup、parcel 等)都附带内置的 minifiers。 两个最流行的 minifiers 是uglify-js和terser。
CSS 优化
衡量感知性能的首要指标是内容在浏览器视口中的加载速度。页面渲染时,所有 CSS 必须完全加载,因为 CSS 是级联的,样式表底部的规则集可能会影响更上层的规则。如果我们向浏览器提供一个包含页面所有样式的庞大样式表,那么加载该内容的样式表将需要很长时间,从而影响性能。为了解决这个问题,开发人员想出了一个巧妙的技巧,称为关键 CSS。
首先,将所有影响首屏(视口内)内容的样式作为样式标签内联到 HTML 文档的头部。然后,使用巧妙的 JavaScript 技巧,延迟加载并推迟其余 CSS 的加载,使其仅在页面完全加载后才加载。Critical
帮助我们自动化此过程,这样您就无需在每次更新时手动复制粘贴代码。
Critical 读取 HTML 和 CSS 文件,找出需要内联的规则集,并自动将 CSS 内联到 HTML 文档中,将非关键 CSS 分离到单独的样式表中,然后对第一个非关键 CSS 进行延迟加载。
由于此工具内置于工具链中,因此可以设置为在每次构建时执行,这样您就无需时刻关注哪些样式是关键的。此工具还提供大量选项,因此您可以完全自定义关键 CSS、索引文件或 HTML 文件、CSS 以及目标视口中发生的操作,所有这些都可以配置。
例如: Critical 实际上会启动一个浏览器,然后以我们定义的视口大小在浏览器中显示内容。然后查看哪些 CSS 影响了该视口中的内容,并将其拆分到这个关键 CSS 文件中。示例中的视口宽度为 320,高度为 480。 关键内联 CSS 甚至会在 DOM 构建之前运行。这样,这将定义首屏内容。 下面是链接元素,但链接元素现在指向非关键 CSS。您会注意到媒体属性设置为打印。这是 JavaScript 的技巧。 现在,普通浏览器会将自己标识为屏幕。因此,此样式表将不会被加载,因为它被设置为仅在打印时加载。这意味着当您实际打印某些内容时。然后,on load(页面完全加载时触发的事件)会将此媒体更改为全部。此时,一旦其他所有操作都完成,就会加载此额外的样式表。
要查看有多少 JavaScript、CSS 和其他代码被不必要地加载到浏览器中,您可以使用浏览器开发工具中的覆盖率视图。 如果您看到任何红色标记的内容,则表示该规则当前未在页面上使用。这就是 Critical 所做的,它会运行此类流程,然后在视图窗口中识别哪些规则正在使用,哪些规则未使用,然后进行筛选。 如果您有一个庞大的样式表,则需要比较所有这些页面并进行大量工作。 更好的解决方案是,我们可以将 CSS 模块化,将 CSS 拆分成更小的组件,然后仅在需要时加载它们。我们可以实现这一点的一种方法是延迟 CSS 的加载,直到发生某些情况。现在,您已经在 Critical 中看到了一个这样的例子。您还记得,我们使用 Critical 时,Critical CSS 是内联的,其余样式则放在这个非关键 CSS 文件中并延迟加载。
所以,这里有一种不同的方法来实现同样的效果。 在这里,我们将 rel preload 和 as 样式属性设置到 link 元素中,告诉浏览器在有可用处理时预加载此样式表,这意味着加载会延迟以避免渲染阻塞。然后,当 CSS 完全加载时, on load 属性会触发,并将 rel 属性设置为 stylesheet,以便浏览器识别并渲染它。但是,底部的这个非脚本元素是针对不支持 JavaScript 的浏览器的后备方案,在这种情况下,浏览器会立即加载样式表。 我们也可以: 在 disable 属性被移除或设置为默认值之前,浏览器根本不会加载此样式表。然后,您可以设置一个 JavaScript 函数,当且仅当发生某些事件(例如激活图库、触发 JavaScript 或触发某些外部函数)时,才更改 disabled 属性。只有这样,浏览器才会访问互联网下载样式表并将其挂载到浏览器中。
最后, 在 body 中加载样式表意味着您可以让每个组件动态加载自己的样式表。这样,组件就会自带样式,您无需加载任何不需要的样式。这使得代码更加简洁易管理,并且符合现代基于组件的开发实践。