HTML 性能优化
HTML 默认情况下是快速且可访问的。作为开发者,我们的工作是在创建或编辑 HTML 代码时确保我们保留这两个特性。例如,当 <video>
嵌入的文件大小过大,或者 JavaScript 解析阻塞了关键页面元素的渲染时,就会出现问题。本文将带您了解可以大幅提升网页质量的关键 HTML 性能特性。
先决条件 | 已安装基本软件,并具备对 客户端 Web 技术 的基本了解。 |
---|---|
目标 | 了解 HTML 对网站性能的影响,以及如何优化您的 HTML 以提高性能。 |
是否优化
HTML 性能的关键问题
从性能角度来看,HTML 很简单——它主要是文本,尺寸很小,因此通常下载和渲染速度很快。可能影响网页性能的关键问题包括
- 图像和视频文件的大小:重要的是要考虑如何处理替换元素(例如
<img>
和<video>
)的内容。图像和视频文件很大,会显著增加页面的重量。因此,务必最大限度地减少下载到用户设备上的字节数(例如,为移动设备提供更小的图像)。您还需要考虑通过仅在需要时加载页面上的图像和视频来提高感知性能。 - 嵌入内容的交付:这通常是嵌入在
<iframe>
元素中的内容。将内容加载到<iframe>
中会严重影响性能,因此应谨慎考虑。 - 资源加载顺序:为了最大限度地提高感知性能和实际性能,应首先按其在页面上的显示顺序加载 HTML。然后,您可以使用各种功能来影响资源加载顺序,以获得更好的性能。例如,您可以预加载关键 CSS 和字体,但将非关键 JavaScript 推迟到稍后。
注意:有人认为简化 HTML 结构和 压缩 源代码可以加快渲染和下载速度。但是,与图像和视频相比,HTML 文件大小可以忽略不计,而且如今浏览器渲染速度非常快。如果您的 HTML 源代码非常庞大而复杂,导致渲染和下载性能下降,那么您可能遇到了更大的问题,应该尝试简化它并拆分内容。
对替换元素的响应式处理
响应式设计 彻底改变了跨不同设备处理 Web 内容布局的方式。它实现的一项关键优势是可以动态切换针对不同屏幕尺寸优化的布局,例如宽屏布局与窄屏(移动)布局。它还可以根据其他设备属性(例如分辨率或对浅色或深色配色方案的偏好)处理内容的动态切换。
所谓的“移动优先”技术可以确保默认布局适合小屏幕设备,因此移动设备只需下载适合其屏幕的图像,而无需承担下载更大桌面图像的性能损失。但是,由于这是使用 媒体查询 在您的 CSS 中控制的,因此它只能积极影响 CSS 中加载的图像的性能。
在以下部分,我们将总结如何在响应式替换元素中实现响应式设计。您可以在 视频和音频内容 和 响应式图像 指南中找到有关这些实现的更多详细信息。
通过 srcset
提供不同的图像分辨率
要根据设备的分辨率和视口大小提供相同图像的不同分辨率版本,可以使用 srcset
和 sizes
属性。
此示例为不同屏幕宽度提供了不同尺寸的图像
<img
srcset="480w.jpg 480w, 800w.jpg 800w"
sizes="(max-width: 600px) 480px,
800px"
src="800w.jpg"
alt="Family portrait" />
srcset
提供了源图像的内在尺寸及其文件名,sizes
提供了媒体查询以及在每种情况下需要填充的图像插槽宽度。然后浏览器会决定为每个插槽加载哪些图像。例如,如果屏幕宽度为 600px
或更小,则 max-width: 600px
为真,因此要填充的插槽为 480px
。在这种情况下,浏览器可能会选择加载 480w.jpg 文件(480 像素宽的图像)。这有助于提高性能,因为浏览器不会加载超过其所需的图像。
此示例为不同屏幕分辨率提供了不同分辨率的图像
<img
srcset="320w.jpg, 480w.jpg 1.5x, 640w.jpg 2x"
src="640w.jpg"
alt="Family portrait" />
1.5x
、2x
等是相对分辨率指示符。如果图像的样式设置为 320 像素宽(例如,在 CSS 中使用 width: 320px
),则如果设备为低分辨率(每个 CSS 像素一个设备像素),浏览器将加载 320w.jpg
,或者如果设备为高分辨率(每个 CSS 像素两个或更多设备像素),则加载 640x.jpg
。
在这两种情况下,src
属性都提供了一个默认图像,如果浏览器不支持 src
/srcset
,则将加载该图像。
为图像和视频提供不同的来源
<picture>
元素建立在传统的 <img>
元素之上,允许您为不同的情况提供多个不同的来源。例如,如果布局很宽,您可能想要一个宽图像,如果布局很窄,您可能想要一个仍然适合该上下文更窄的图像。
当然,这也可以为移动设备提供更小的下载信息量,从而有助于提高性能。
一个例子如下
<picture>
<source media="(max-width: 799px)" srcset="narrow-banner-480w.jpg" />
<source media="(min-width: 800px)" srcset="wide-banner-800w.jpg" />
<img src="large-banner-800w.jpg" alt="Dense forest scene" />
</picture>
<source>
元素在 media
属性内包含媒体查询。如果媒体查询返回真,则加载其 <source>
元素的 srcset
属性中引用的图像。在上面的示例中,如果视口宽度为 799px
或更小,则加载 narrow-banner-480w.jpg
图像。还要注意 <picture>
元素如何包含一个 <img>
元素,它提供了一个默认图像,以供不支持 <picture>
的浏览器加载。
注意本例中使用 srcset
属性。如前一部分所示,您可以为每个图像源提供不同的分辨率。
<video>
元素在提供不同的来源方面的工作方式类似
<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="(min-width: 800px)" />
<source
src="video/larger.webm"
type="video/webm"
media="(min-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>
元素内指定不同的视频格式,每个格式都通过其type
属性内的 MIME 类型来标识。浏览器将加载他们遇到的第一个支持的格式,其中媒体查询测试返回真。
延迟加载图像
提高性能的一个非常有用的技术是延迟加载。它指的是不立即在 HTML 渲染时加载所有图像,而是在图像实际出现在用户视口中(或即将出现在视口中)时才加载它们。这意味着立即可见/可用的内容可以更快地使用,而后续内容的图像仅在滚动到时才渲染,浏览器不会浪费带宽加载用户永远不会看到的图像。
延迟加载过去通常使用 JavaScript 处理,但浏览器现在可以使用 loading
属性来指示浏览器自动延迟加载图像
<img src="800w.jpg" alt="Family portrait" loading="lazy" />
请参阅 web.dev 上的 浏览器级图像延迟加载,以获取详细的信息。
您还可以通过使用 preload
属性来延迟加载视频内容。例如
<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>
的任何 CSS 样式或 JavaScript 操作(除非嵌入页面来自同一个来源),这会变得更加复杂。你无法使用应用于顶级页面的 CSS 和 JavaScript 来定位嵌入内容,反之亦然。这是一种合理的安全措施,是网络的基础。想象一下,如果第三方嵌入内容可以任意地对它嵌入的任何页面运行脚本,你可能会遇到多少问题! - 每个
<iframe>
还需要分别加载任何共享数据和媒体文件——你无法跨不同的页面嵌入共享缓存的资产(同样,除非嵌入页面来自同一个来源)。这会导致页面使用比你预期的更多的带宽。
建议将内容放在一个页面中。如果你想在页面更改时动态地拉入新内容,为了性能,最好将其加载到同一页面,而不是将其放入<iframe>
中。例如,你可以使用fetch()
方法获取新数据,然后使用一些 DOM 脚本将其注入页面。有关更多信息,请参阅从服务器获取数据和操作文档。
注意:如果你控制内容并且内容相对简单,你可以考虑在src
属性中使用 base-64 编码的内容来填充<iframe>
,甚至可以将原始 HTML 插入到srcdoc
属性中(有关更多信息,请参阅Iframe 性能第 2 部分:好消息)。
如果你必须使用<iframe>
,那么请谨慎使用。
延迟加载 iframe
与<img>
元素相同,你也可以使用loading
属性来指示浏览器延迟加载最初处于屏幕外的<iframe>
内容,从而提高性能。
<iframe src="https://example.com" loading="lazy" width="600" height="400">
</iframe>
有关更多信息,请参阅是时候延迟加载屏幕外的 iframe 了!。
处理资源加载顺序
资源加载的顺序对于最大化感知性能和实际性能很重要。当加载网页时
- HTML 通常首先被解析,解析顺序与它在页面上的显示顺序相同。
- 解析任何找到的 CSS 以了解需要应用于页面的样式。在此期间,链接的资产(如图像和网络字体)开始被获取。
- 解析任何找到的 JavaScript,并针对页面进行评估和运行。默认情况下,这会阻止解析出现在
<script>
元素之后的 HTML,而这些元素是遇到 JavaScript 的地方。 - 稍后,浏览器将根据应用于它的 CSS 计算每个 HTML 元素的样式。
- 然后将样式化的结果绘制到屏幕上。
注意:这只是对发生情况的简化说明,但它确实让你了解了一些情况。
各种 HTML 功能允许你修改资源加载的方式,以提高性能。我们现在将探索其中的一些功能。
处理 JavaScript 加载
解析和执行 JavaScript 会阻止后续 DOM 内容的解析。这会增加渲染该内容以及用户可以使用该内容的时间。一个小脚本不会有太大区别,但要考虑到现代 Web 应用程序往往非常依赖 JavaScript。
默认 JavaScript 解析行为的另一个副作用是,如果正在渲染的脚本依赖于页面后面出现的 DOM 内容,你将收到错误。
例如,想象一下页面上的一个简单段落
<p>My paragraph</p>
现在想象一个包含以下代码的 JavaScript 文件
const pElem = document.querySelector("p");
pElem.addEventListener("click", () => {
alert("You clicked the paragraph");
});
我们可以通过在<script>
元素中引用它来将此脚本应用于页面
<script src="index.js"></script>
如果我们将此<script>
元素放在源代码顺序中<p>
元素之前(例如,在<head>
元素中),页面将抛出错误(例如,Chrome 在控制台中报告“Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')”。这是因为脚本依赖于<p>
元素才能工作,但在解析脚本时,页面上不存在<p>
元素。它还没有被渲染。
你可以通过将<script>
元素放在<p>
元素之后(例如在文档主体末尾)来解决上述问题,或者通过在合适的事件处理程序中运行代码来解决(例如,在DOMContentLoaded
上运行它,该事件在 DOM 完全解析后触发)。
但是,这并不能解决等待脚本加载的问题。通过向<script>
元素添加async
属性可以实现更好的性能
<script async src="index.js"></script>
这会导致脚本与 DOM 解析并行获取,因此脚本在同一时间准备就绪,并且不会阻止渲染,从而提高性能。
注意:还有另一个属性defer
,它会导致脚本在文档解析完成后但DOMContentLoaded
触发之前执行。这与async
具有类似的效果。
另一个 JavaScript 加载处理技巧是将你的脚本拆分为代码模块,并在需要时加载每个部分,而不是将所有代码都放在一个巨大的脚本中,并在开头加载所有代码。这是使用JavaScript 模块完成的。阅读链接的文章以获取详细指南。
使用 rel="preload" 预加载内容
从你的 HTML、CSS 和 JavaScript 中链接的其他资源(如图像、视频或字体文件)的获取也会导致性能问题,阻止你的代码执行并减慢体验速度。缓解此类问题的一种方法是使用rel="preload"
将<link>
元素变成预加载器。例如
<link rel="preload" href="sintel-short.mp4" as="video" type="video/mp4" />
遇到rel="preload"
链接时,浏览器会尽快获取引用的资源,并将其放入浏览器缓存中,以便在后续代码中引用时可以更快地使用。预加载用户在页面早期遇到的高优先级资源很有用,这样可以使体验尽可能平滑。
有关使用rel="preload"
的详细信息,请参阅以下文章
rel="preload"
- 预加载关键资产以提高加载速度 在 web.dev 上(2020 年)
注意:你也可以使用rel="preload"
预加载 CSS 和 JavaScript 文件。
注意:还有其他rel
值也旨在加速页面的加载速度:dns-prefetch
、preconnect
、modulepreload
、prefetch
和prerender
。转到链接的页面并了解它们的功能。