HTML 性能优化

HTML 默认情况下是快速且可访问的。作为开发人员,我们的职责是确保在创建或编辑 HTML 代码时保留这两个属性。例如,当 <video> 嵌入的文件大小过大,或者当 JavaScript 解析阻止关键页面元素的渲染时,可能会出现问题。本文将引导您了解关键的 HTML 性能功能,这些功能可以显著提高网页的质量。

预备知识 已安装基本软件,并具备 客户端 Web 技术 的基础知识。
目标 了解 HTML 对网站性能的影响以及如何优化 HTML 以提高性能。

优化还是不优化

在开始优化 HTML 之前,您应该回答的第一个问题是“我需要优化什么?”。下面讨论的一些提示和技术是良好的实践,将有益于几乎所有 Web 项目,而另一些则仅在某些情况下才需要。试图将所有这些技术应用于所有地方可能是不必要的,并且可能会浪费您的时间。您应该弄清楚每个项目实际需要哪些性能优化。

为此,您需要测量您网站的性能。正如该链接所示,有几种不同的方法可以测量性能,其中一些涉及复杂的性能 API。然而,最好的入门方法是学习如何使用内置浏览器网络性能工具,来检查页面中加载时间过长且需要优化的部分。

关键 HTML 性能问题

HTML 在性能方面是简单的——它主要是文本,体积小,因此大多下载和渲染速度快。可能影响网页性能的关键问题包括:

  • 图片和视频文件的大小:考虑如何处理可替换元素(如 <img><video>)的内容非常重要。图片和视频文件通常很大,会显著增加页面的权重。因此,尽量减少用户设备下载的字节数(例如,为移动设备提供更小的图片)非常重要。您还需要考虑通过仅在需要时才加载页面上的图片和视频来提高感知性能。
  • 嵌入内容的交付:这通常是嵌入在 <iframe> 元素中的内容。将内容加载到 <iframe> 中会显著影响性能,因此应仔细考虑。
  • 资源加载顺序:为了最大化感知和实际性能,HTML 应该首先按照其在页面中出现的顺序加载。然后,您可以使用各种功能来影响资源加载顺序,以获得更好的性能。例如,您可以尽早预加载关键的 CSS 和字体,但将非关键的 JavaScript 推迟到稍后。

注意: 有人主张简化 HTML 结构并压缩源代码,以便加快渲染和下载速度。然而,与图片和视频相比,HTML 文件大小微不足道,而且如今浏览器的渲染速度非常快。如果您的 HTML 源代码如此庞大和复杂,以至于造成渲染和下载性能问题,您可能面临更大的问题,应该着手简化并拆分内容。

可替换元素的响应式处理

响应式设计彻底改变了 Web 内容在不同设备上的布局方式。它实现的一个关键优势是动态切换针对不同屏幕尺寸优化的布局,例如宽屏布局与窄屏(移动)布局。它还可以根据其他设备属性(例如分辨率或对亮色或暗色主题的偏好)动态切换内容。

所谓的“移动优先”技术可以确保默认布局适用于小屏幕设备,因此移动设备只需下载适合其屏幕的图像,而无需承担下载更大桌面图像的性能损失。然而,由于这在 CSS 中使用媒体查询进行控制,因此它只能积极影响 CSS 中加载的图像的性能。

在以下部分中,我们将总结如何实现响应式可替换元素。您可以在 HTML 视频和音频以及 响应式图像指南中找到有关这些实现的更多详细信息。

通过 srcset 提供不同的图像分辨率

为了根据设备的分辨率和视口大小提供相同图像的不同分辨率版本,您可以使用 srcsetsizes 属性。

此示例为不同的屏幕宽度提供不同大小的图片

html
<img
  srcset="480w.jpg 480w, 800w.jpg 800w"
  sizes="(width <= 600px) 480px,
         800px"
  src="800w.jpg"
  alt="Family portrait" />

srcset 提供源图像的固有大小及其文件名,而 sizes 提供媒体查询以及在每种情况下需要填充的图像槽位宽度。然后,浏览器决定为每个槽位加载哪些图像。例如,如果屏幕宽度为 600px 或更小,则 width <= 600px 为真,因此,要填充的槽位被认为是 480px。在这种情况下,浏览器很可能会选择加载 480w.jpg 文件(480px 宽的图像)。这有助于提高性能,因为浏览器不会加载超出所需大小的图像。

此示例为不同的屏幕分辨率提供不同的分辨率图像

html
<img
  srcset="320w.jpg, 480w.jpg 1.5x, 640w.jpg 2x"
  src="640w.jpg"
  alt="Family portrait" />

1.5x2x 等是相对分辨率指示器。如果图像样式设置为 320px 宽(例如在 CSS 中使用 width: 320px),如果设备是低分辨率(每个 CSS 像素一个设备像素),浏览器将加载 320w.jpg;如果设备是高分辨率(每个 CSS 像素两个或更多设备像素),浏览器将加载 640x.jpg

在两种情况下,如果浏览器不支持 src/srcsetsrc 属性都会提供要加载的默认图像。

为图片和视频提供不同的来源

<picture> 元素建立在传统的 <img> 元素之上,允许您为不同情况提供多个不同的源。例如,如果布局很宽,您可能需要一个宽图像;如果布局很窄,您可能需要一个在该上下文中仍然有效的更窄图像。

当然,这也可以为移动设备提供更小的数据下载量,有助于提高性能。

例如:

html
<picture>
  <source media="(width < 800px)" srcset="narrow-banner-480w.jpg" />
  <source media="(width >= 800px)" srcset="wide-banner-800w.jpg" />
  <img src="large-banner-800w.jpg" alt="Dense forest scene" />
</picture>

<source> 元素在 media 属性中包含媒体查询。如果媒体查询返回 true,则加载其 <source> 元素的 srcset 属性中引用的图像。在上面的示例中,如果视口宽度小于 800px,则加载 narrow-banner-480w.jpg 图像。另请注意 <picture> 元素如何包含一个 <img> 元素,该元素为不支持 <picture> 的浏览器提供一个默认加载的图像。

请注意此示例中 srcset 属性的使用。如上一节所示,您可以为每个图像源提供不同的分辨率。

<video> 元素在提供不同源方面以类似方式工作

html
<video controls>
  <source src="video/smaller.mp4" type="video/mp4" />
  <source src="video/smaller.webm" type="video/webm" />
  <source src="video/larger.mp4" type="video/mp4" media="(width >= 800px)" />
  <source src="video/larger.webm" type="video/webm" media="(width >= 800px)" />

  <!-- fallback for browsers that don't support video element -->
  <a href="video/larger.mp4">download video</a>
</video>

然而,为图像和视频提供来源之间存在一些关键差异

  • 在上面的示例中,我们使用的是 src 而不是 srcset;您无法通过 srcset 为视频指定不同的分辨率。
  • 相反,您在不同的 <source> 元素中指定不同的分辨率。
  • 请注意,我们还在不同的 <source> 元素中指定了不同的视频格式,每种格式都通过其 MIME 类型在 type 属性中标识。浏览器将加载它们遇到的第一个支持的视频,其中媒体查询测试返回 true。

图片懒加载

一个非常有用的性能优化技术是懒加载。这指的是在 HTML 渲染时不是立即加载所有图像,而是仅在它们实际在视口中对用户可见(或即将可见)时才加载它们。这意味着立即可见/可用的内容可以更快地投入使用,而后续内容只有在滚动到时才渲染其图像,并且浏览器不会浪费带宽加载用户永远不会看到的图像。

过去,懒加载通常使用 JavaScript 处理,但现在浏览器提供了 loading 属性,可以指示浏览器自动懒加载图像

html
<img src="800w.jpg" alt="Family portrait" loading="lazy" />

有关详细信息,请参阅 web.dev 上的Web 浏览器级别的图像懒加载

您还可以使用 preload 属性懒加载视频内容。例如

html
<video controls preload="none" poster="poster.jpg">
  <source src="video.webm" type="video/webm" />
  <source src="video.mp4" type="video/mp4" />
</video>

preload 的值设置为 none 告诉浏览器在用户决定播放视频之前不要预加载任何视频数据,这显然有利于性能。相反,它只会显示由 poster 属性指示的图像。不同的浏览器有不同的默认视频加载行为,因此明确指定是个好习惯。

有关详细信息,请参阅 web.dev 上的通过音频和视频预加载实现快速播放

处理嵌入内容

网页中嵌入来自其他来源的内容非常常见。这最常用于在网站上显示广告以创收——广告通常由某种第三方公司生成并嵌入到您的页面中。其他用途可能包括:

  • 显示用户在多个页面上可能需要的共享内容,例如购物车或个人资料信息。
  • 显示与组织主站点相关的第三方内容,例如社交媒体帖子提要。

嵌入内容最常使用 <iframe> 元素,尽管也存在其他不常用的嵌入元素,例如 <object><embed>。在本节中,我们将重点讨论 <iframe>

关于使用 <iframe> 最重要和最关键的建议是:“除非绝对必要,否则不要使用嵌入式 <iframe>。”如果您正在创建一个包含多个不同信息窗格的页面,将它们分解成单独的页面并加载到不同的 <iframe> 中可能听起来在组织上是合理的。然而,这在性能和其他方面都存在许多问题:

  • 将内容加载到 <iframe> 中的成本远高于将内容作为同一直接页面的一部分加载——它不仅需要额外的 HTTP 请求来加载内容,而且浏览器还需要为每个 <iframe> 创建一个单独的页面实例。每个 <iframe> 实际上都是一个嵌入在顶级网页中的独立网页实例。
  • 承接上一点,您还需要为每个不同的 <iframe> 分别处理任何 CSS 样式或 JavaScript 操作(除非嵌入页面来自同一源),这变得复杂得多。您无法使用应用于顶级页面的 CSS 和 JavaScript 来定位嵌入内容,反之亦然。这是一项明智的安全措施,也是 Web 的基本原则。想象一下,如果第三方嵌入内容可以随意运行针对其嵌入的任何页面的脚本,您可能会遇到所有问题!
  • 每个 <iframe> 还需要单独加载任何共享数据和媒体文件——您不能在不同的页面嵌入之间共享缓存资源(同样,除非嵌入页面来自同一源)。这可能导致页面使用的带宽远超您的预期。

建议将内容放入单个页面。如果您想在页面更改时动态拉取新内容,将其加载到同一页面而不是放入 <iframe> 仍然对性能更好。例如,您可以使用 fetch() 方法获取新数据,然后使用一些 DOM 脚本将其注入页面。有关更多信息,请参阅使用 JavaScript 发出网络请求DOM 脚本简介

注意: 如果您控制内容并且内容相对简单,您可以考虑在 src 属性中使用 base-64 编码的内容来填充 <iframe>,甚至将原始 HTML 插入 srcdoc 属性(有关更多信息,请参阅Iframe 性能第二部分:好消息)。

如果必须使用 <iframe>,请节制使用。

Iframes 懒加载

<img> 元素一样,您也可以使用 loading 属性指示浏览器懒加载最初在屏幕外的 <iframe> 内容,从而提高性能

html
<iframe src="https://example.com" loading="lazy" width="600" height="400">
</iframe>

有关更多信息,请参阅是时候懒加载屏幕外 iframes 了!

处理资源加载顺序

资源加载顺序对于最大化感知和实际性能至关重要。当网页加载时

  1. HTML 通常首先被解析,按照其在页面中出现的顺序。
  2. 任何发现的 CSS 都被解析以理解需要应用于页面的样式。在此期间,链接的资源(如图像和 Web 字体)开始被获取。
  3. 任何发现的 JavaScript 都会被解析、评估并在页面上运行。默认情况下,这会阻止解析出现在 JavaScript 所在 <script> 元素之后的 HTML。
  4. 稍后,浏览器根据应用于每个 HTML 元素的 CSS 确定其样式。
  5. 然后将样式化的结果绘制到屏幕上。

注意: 这只是对所发生事情的一个非常简化的描述,但它确实能让您有所了解。

各种 HTML 功能允许您修改资源加载方式以提高性能。我们现在将探讨其中一些功能。

处理 JavaScript 加载

解析和执行 JavaScript 会阻塞后续 DOM 内容的解析。这增加了内容渲染并供页面用户使用的时间。一个小的脚本不会有太大影响,但请考虑现代 Web 应用程序往往 JavaScript 代码量很大。

默认 JavaScript 解析行为的另一个副作用是,如果正在渲染的脚本依赖于页面中稍后出现的 DOM 内容,您将收到错误。

例如,想象页面上的一个简单段落

html
<p>My paragraph</p>

现在想象一个包含以下代码的 JavaScript 文件

js
const pElem = document.querySelector("p");

pElem.addEventListener("click", () => {
  alert("You clicked the paragraph");
});

我们可以通过在 <script> 元素中引用它来将此脚本应用于页面,如下所示

html
<script src="index.js"></script>

如果我们按照源代码顺序将此 <script> 元素放在 <p> 元素之前(例如,在 <head> 元素中),页面将抛出错误(例如,Chrome 会在控制台中报告“Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')”)。发生这种情况是因为脚本依赖于 <p> 元素才能工作,但在脚本解析时,<p> 元素在页面上尚不存在。它尚未被渲染。

您可以通过将 <script> 元素放在 <p> 元素之后(例如,在文档主体的末尾),或者通过在合适的事件处理程序中运行代码(例如,在 DOM 完全解析后触发的 DOMContentLoaded 事件上运行)来解决上述问题。

然而,这并不能解决等待脚本加载的问题。通过向 <script> 元素添加 async 属性可以实现更好的性能

html
<script async src="index.js"></script>

这使得脚本与 DOM 解析并行获取,因此它同时准备就绪,并且不会阻塞渲染,从而提高性能。

注意: 还有另一个属性 defer,它使脚本在文档解析后但在触发 DOMContentLoaded 之前执行。这与 async 具有类似的效果。

另一个 JavaScript 加载处理技巧是将您的脚本拆分成代码模块,并在需要时加载每个部分,而不是将所有代码放入一个巨大的脚本中并在开始时全部加载。这是使用JavaScript 模块完成的。阅读链接文章以获取详细指南。

使用 rel="preload" 预加载内容

从您的 HTML、CSS 和 JavaScript 链接的其他资源(如图片、视频或字体文件)的获取也可能导致性能问题,阻碍您的代码执行并减慢体验。缓解此类问题的一种方法是使用 rel="preload"<link> 元素转换为预加载器。例如

html
<link rel="preload" href="sintel-short.mp4" as="video" type="video/mp4" />

浏览器遇到 rel="preload" 链接时,会尽快获取引用的资源并将其存储在浏览器缓存中,以便在后续代码中引用时能更快地使用。预加载用户在页面早期会遇到的高优先级资源非常有用,这样可以使体验尽可能流畅。

有关使用 rel="preload" 的详细信息,请参阅以下文章

注意: 您也可以使用 rel="preload" 预加载 CSS 和 JavaScript 文件。

注意: 还有其他一些 rel 值也旨在加速页面加载的各个方面:dns-prefetchpreconnectmodulepreloadprefetch。访问链接页面并了解它们的功能。

另见