万维网是如何工作的 - 学习 Web 开发

万维网是如何工作的
=========

* 上一页
* Overview: Getting started with the web

这篇文章简单描述了你在计算机或手机上通过浏览器访问网页时发生了什么。

这个理论在短期内对你编写网页代码不会有实质性的帮助，但是不久之后你就会真正受益于理解了后台究竟发生了什么。

客户端和服务器
-------

连接到互联网的计算机被称作客户端和服务器。下面是一个简单描述它们如何交互的图表：

![Two circles representing client and server. An arrow labelled request is going from client to server, and an arrow labelled responses is going from server to client](/zh-CN/docs/Learn/Getting_started_with_the_web/How_the_Web_works/simple-client-server.png)

* 客户端是典型的 Web 用户入网设备（比如，你连接了 Wi-Fi 的电脑，或接入移动网络的手机）和设备上可联网的软件（通常使用像 Firefox 和 Chrome 的浏览器）。
* 服务器是存储网页，站点和应用的计算机。当一个客户端设备想要获取一个网页时，一份网页的拷贝将从服务器上下载到客户端机器上来在用户浏览器上显示。
其他部分
----

上文所述的客户端和服务器并不能完成全部工作。还有其他必要的部分，我们将在下面讲述。

现在，让我们假设 Web 就是一条路。路的一端是客户端，就像你的家。另一端则是服务器，就像你想去的商店。

![A black-and-white photo of a person crossing a road at a crosswalk](/zh-CN/docs/Learn/Getting_started_with_the_web/How_the_Web_works/road.jpg)

除了客户端和服务器，我们还需要了解：

**备注：** 我感觉下面的比喻还不是很契合。我感觉再合理一点的比喻：

假如你生活在一个封闭的村子叫做“盘溪新村”，村子盛产苹果。

* **互联网**：好比地球上纵横交错的道路。
* **网络连接**：道路通到了村子路口。从此，村子里的苹果就可以运出去卖了。
* **TCP/IP**：为了将村里的苹果能规范有效的运卖出去而不出问题，村长作出如下规定：“用规格刚好 20 cm \* 20 cm \* 20 cm 的泡沫箱来装，之后外面又用相应规格的纸箱包裹上，最后打上透明胶”。并且要求，对方收到时，一定要外包装完好，不然就会补发。而且还给对方发了一张发货单，明确说明了，苹果有多少，是用什么方法包装的，只有货和发货单对上了，对方才会确认收货。
* **DNS**：突然一天，郭德纲想吃苹果，就跟于谦说，“我听说盘溪新村（域名）的苹果好，要他们那个套餐一选项啊！”，于谦一听，得，也不知道盘溪新村在哪，打开地图查（DNS）吧，一查，好嘛，江苏省苏州市（IP 地址），于是于谦去了苏州，找了村子，告诉村长，要套餐一，要用顺丰快递，并且留下了北京德云社的地址。
* **HTTP**：过了几天，德云社的人一看，有快递来了，来了这么一句，“只收‘顺丰’，拒收其他快递”。司机忙说，“是顺丰，是顺丰”，这才对上暗号，德云社的人收下了货。
* **组成文件**：送来的货可不止一车，而且也不止一种苹果，这车是红富士，那车黄富士的。
    + **代码**：有点像，村长事先安排的说明书，让司机到了地方，如何卸车，货放到什么位置，而德云社的看说明书，知道什么样的苹果放到什么位置上，什么样苹果如何食用最佳，等等。
    + **资源**：不同种类的苹果。

一点拙见，在下抛砖引玉，希望有更好理解的比喻。

* **网络连接**: 允许你在互联网上发送和接受数据。基本上和你家到商店的街道差不多。
* **TCP/IP**: 传输控制协议和因特网互连协议是定义数据如何传输的通信协议。这就像你去商店购物所使用的交通方式，比如汽车或自行车（或是你能想到的其他可能）。
* **DNS**: 域名系统服务器像是一本网站通讯录。当你在浏览器内输入一个网址时，浏览器获取网页之前将会查看域名系统。浏览器需要找到存放你想要的网页的服务器，才能发送 HTTP 请求到正确的地方。就像你要知道商店的地址才能到达那。
* **HTTP**: 超文本传输协议是一个定义客户端和服务器间交流的语言的协议（protocol ）。就像你下订单时所说的话一样。
* **组成文件**: 一个网页由许多文件组成，就像商店里不同的商品一样。这些文件有两种类型：
    + **代码** : 网页大体由 HTML、CSS、JavaScript 组成，不过你会在后面看到不同的技术。
    + **资源** : 这是其他组成网页的东西的集合，比如图像、音乐、视频、Word 文档、PDF 文件。
到底发生了什么？
--------

当你在浏览器里输入一个网址时（在我们的例子里就是走向商店的路上时）：

1. 浏览器在域名系统（DNS）服务器上找出存放网页的服务器的实际地址（找出商店的位置）。
2. 浏览器发送 HTTP 请求信息到服务器来请拷贝一份网页到客户端（你走到商店并下订单）。这条消息，包括其他所有在客户端和服务器之间传递的数据都是通过互联网使用 TCP/IP 协议传输的。
3. 服务器同意客户端的请求后，会返回一个“200 OK”信息，意味着“你可以查看这个网页，给你～”，然后开始将网页的文件以数据包的形式传输到浏览器（商店给你商品，你将商品带回家）。
4. 浏览器将数据包聚集成完整的网页然后将网页呈现给你（商品到了你的门口——新东西，好棒！）。
解析组成文件的顺序
---------

当浏览器向服务器发送请求获取 HTML 文件时，HTML 文件通常包含 `<link>` 和 `<script>` 元素，这些元素分别指向了外部的 CSS 样式表文件和 JavaScript 脚本文件。了解这些文件被浏览器解析的顺序是很重要的：

* 浏览器首先解析 HTML 文件，并从中识别出所有的 `<link>` 和 `<script>` 元素，获取它们指向的外部文件的链接。
* 继续解析 HTML 文件的同时，浏览器根据外部文件的链接向服务器发送请求，获取并解析 CSS 文件和 JavaScript 脚本文件。
* 接着浏览器会给解析后的 HTML 文件生成一个 DOM 树（在内存中），会给解析后的 CSS 文件生成一个 CSSOM 树（在内存中），并且会编译和执行解析后的 JavaScript 脚本文件。
* 伴随着构建 DOM 树、应用 CSSOM 树的样式、以及执行 JavaScript 脚本文件，浏览器会在屏幕上绘制出网页的界面；用户看到网页界面也就可以跟网页进行交互了。
DNS 解析
------

真正的网址看上去并不像你输入到地址框中的那样美好且容易记忆。它们是一串数字，像 `63.245.217.105`。

这叫做 IP 地址，它代表了一个互联网上独特的位置。然而，它并不容易记忆，不是吗？那就是域名系统（DNS）被发明的原因。它们是将你输入浏览器的地址（像 "mozilla.org"）与实际 IP 地址相匹配的特殊的服务器。

网页可以通过 IP 地址直接访问。你可以通过在 DNS 查询工具 等工具中输入域名来查找网站的 IP 地址。

数据包详解
-----

前面我们用“包”来描述了数据从服务器到客户端传输的格式。这是什么意思？基本上，当数据在 Web 上传输时，是以成千上万的小数据块的形式传输的。大量不同的用户都可以同时下载同一个网页。如果网页以单个大的数据块形式传输，一次就只有一个用户下载，无疑会让 Web 非常没有效率并且失去很多乐趣。

扩展阅读
----

* 互联网是如何工作的
* HTTP — 一种应用级协议
* HTTP：让我们开始吧！
* HTTP：响应代码
感谢
--

由 Kevin D 提供的街景图片 Street composing。

* 上一页
* Overview: Getting started with the web


处理文件 - 学习 Web 开发

处理文件
====

* 上一页
* Overview: Getting started with the web
* 下一页

一个网站包含许多文件：文本内容、代码、样式表、媒体内容，等等。当你建立一个网站时，你需要在计算机上将这些文件以合理的结构组织起来，确保文件之间交互畅通，并在你最终将它们上传到服务器之前使你的所有内容看起来正确。*处理文件*讨论了一些你应该注意的问题，以便你能为你的网站建立一个合理的文件结构。

网站应该保存在何处？
----------

对于本地网站，应将所有相关文件放在一个单独文件夹中，可以映射出服务器端站点文件结构。这个文件夹可以放在任何你喜欢的地方，但你应该把它放在你能轻易找到的地方，也许是在你的桌面上，在你的主页文件夹里，或者在你的硬盘根目录下。

1. 选择一个地方来存储你的网站项目。在你选择的地方，创建一个名为 `web-projects`（或类似）的新文件夹。这是你所有的网站项目的存放地。
2. 在这个文件夹中，创建另一个文件夹来存放你的第一个网站。称之为 `test-site`（或更有想象力的东西）。
关于大小写和空格的提示
-----------

你会注意到，在这篇文章中，我们要求你完全用小写字母命名文件夹和文件，没有空格。这是因为：

1. 许多计算机，特别是网络服务器，是区分大小写的。因此，假如你把一张图片放在你的网站上 `test-site/MyImage.jpg`，然后在一个不同的文件中，你试图以 `test-site/myimage.jpg` 来调用该图片，它可能无法工作。
2. 浏览器、网络服务器和编程语言对空格的处理并不一致。例如，如果你在文件名中使用空格，一些系统可能将其视为两个文件名。一些服务器会用“%20”（URL 中空格的字符代码）替换文件名中的区域，导致所有链接被破坏。最好用连字符而不是下划线来分隔单词。对比 `my-file.html` 与 `my_file.html`。

简言之，文件名中应使用连字符。谷歌搜索引擎把连字符当作单词的分隔符，但不会识别下划线。基于此，最好在一开始就养成习惯，文件夹和文件名使用小写，用短横线来分隔。这可以避免许多问题。

网站应该使用什么结构？
-----------

接下来，让我们看看我们的测试网站应该是什么结构。在我们创建的任何网站项目中，最常见的是一个主页 HTML 文件和包含图像、样式文件和脚本文件的文件夹。现在让我们来创建这些：

1. **`index.html`**：这个文件一般会包含主页内容，也就是人们第一次进入网站时看到的文字和图片。使用文本编辑器，创建一个名为`index.html`的新文件，并将其保存在`test-site`文件夹内。
2. **`images` 文件夹**：这个文件夹包含网站上使用的所有图片。在 `test-site` 文件夹内创建一个名为 `images` 的文件夹。
3. **`styles` 文件夹**：这个文件夹包含用于设置内容样式的 CSS 代码（例如，设置文本和背景颜色）。在你的 `test-site` 文件夹内创建一个名为 `styles` 的文件夹。
4. **`scripts` 文件夹**：这个文件夹包含所有用于向网站添加交互功能的 JavaScript 代码（例如，点击时加载数据的按钮）。在 `test-site` 文件夹内创建一个名为 `scripts` 的文件夹。

**备注：** 在 Windows 上你可能看不到文件扩展名，因为 Windows 有一个默认开启的**隐藏已知文件类型的扩展名**的选项。一般来说，你可以通过 Windows 资源管理器，选择**文件夹选项**选项，取消勾选**隐藏已知文件类型的扩展名**复选框，然后点击**确认**将其关闭。对于涉及你的 Windows 版本的更多具体信息，你可以在网上搜索。

文件路径
----

为使文件间正常交互，应为每个文件提供访问路径，所以一个文件知道另一个文件的位置。为作为演示，我们将在 `index.html` 中插入一小段 HTML，并使其显示你在“你的网站会是什么样？“一文中选择的图片。或者，你可以在你的电脑上或从网上选择一个现有的图片，并在下面的步骤中使用它：

1. 将你之前选择的图片复制到你的 `images` 文件夹。
2. 打开你的 `index.html` 文件，并准确地将以下代码插入该文件中。当下不要对其感到困惑——我们将在本系列的后面更详细地研究这些结构。

```html
<!doctype html>
<html lang="en-US">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>My test page</title>
  </head>
  <body>
    <img src="" alt="My test image" />
  </body>
</html>
```

3. 该行代码 `<img src="" alt="My test image">` 用于向页面中插入图片。我们需要告知 HTML 图片的位置。这张图片位于 *images* 目录下，与 `index.html` 处于同一目录。为了从 `index.html` 访问到我们的图像，我们需要的文件路径是 `images/your-image-filename`。例如，这里的图像叫做 `firefox-icon.png`，那么文件路径就是 `images/firefox-icon.png`。
4. 在 HTML 代码 `src=""` 的双引号之间插入文件路径。
5. 保存 HTML 文件，然后使用浏览器打开（双击该文件）。你应该看到新网页显示了新的图像！

![A screenshot of our basic website showing just the firefox logo - a flaming fox wrapping the world](/zh-CN/docs/Learn/Getting_started_with_the_web/Dealing_with_files/website-screenshot.png)

文件路径的一些通用规则：

* 若引用的目标文件与 HTML 文件同级，只需直接使用文件名，例如：`my-image.jpg`。
* 要引用子目录中的文件，请在路径前面写上目录名，再加上一个正斜杠。例如：`subdirectory/my-image.jpg`。
* 若引用的目标文件位于 HTML 文件的**上级**，需要加上两个点。举个例子，如果 `index.html` 在 `test-site` 的一个子文件夹内，而 `my-image.jpg` 在 `test-site` 内，你可以使用`../my-image.jpg` 从 `index.html` 引用 `my-image.jpg`。
* 以上方法可以随意组合，比如：`../subdirectory/another-subdirectory/my-image.jpg`。

这是你目前需要知道的全部内容。

**备注：** Windows 的文件系统使用反斜杠而不是正斜杠，例如：`C:\Windows`。这在 HTML 中并不重要——即使你在 Windows 系统上进行开发，你也应该在代码中使用正斜杠。

还应该做些什么？
--------

现在你的文件夹结构应该看起来像这样：

![A file structure in mac os x finder, showing an images folder with an image in, empty scripts and styles folders, and an index.html file](/zh-CN/docs/Learn/Getting_started_with_the_web/Dealing_with_files/file-structure.png)

* 上一页
* Overview: Getting started with the web
* 下一页


MathML 入门概述 - 学习 Web 开发

MathML 入门概述
===========

MathML 是用于在网页中编写数学公式的标记语言。本单元将为你提供一个轻松的开始，帮助你掌握 MathML 的基本原理、语法和如何在 HTML 中开始使用它。

#### 想成为前端开发人员吗？

我们提供了一门课程，包含了你实现此目标所需的所有基本信息。

**开始学习**

前提
--

在开始本单元之前，你应具备以下知识：

1. 对使用计算机和被动使用 Web（即查看内容）有基本熟悉。
2. 已完成基本的工作环境设置，详见安装基本软件，并了解如何创建和管理文件，详见处理文件。
3. 熟悉 HTML 的基本知识，详见 HTML 入门。

**备注：** 如果你在计算机/平板电脑/其他设备上工作，但这些设备上你无法创建自己的文件，那么可以尝试在在线编程平台（如 JSBin 或 Glitch）中运行代码示例。

指南
--

本单元包含以下文章，将带你了解 MathML 的基本理论，并提供机会让你测试练习技能。

开始使用 MathML

在本文中，我们将使用一个简单的 HTML 文档，看看如何在其中添加 MathML 公式，并逐步介绍一些元素。

文本容器

现在你对 MathML 有了更好的了解，我们将介绍文本容器（变量、数字、运算符等），它们是 MathML 公式的构建模块。

分数和根号

在文本容器的基础上，本文介绍了如何通过嵌套分数和根号来构建更复杂的 MathML 表达式。

附加符号

我们继续回顾基本数学符号，并重点介绍如何使用附加符号构建 MathML 元素。

表格

在掌握所有基本数学符号后，我们需要考虑表格布局，它可用于矩阵式表达式和其他高级数学布局。

评估
--

以下评估将测试你对上述指南中介绍的 MathML 基础知识的理解程度。

三个著名数学公式

学完之前的几篇文章后，你已经能够编写相对复杂的 MathML 公式。本评估将给你一个机会展示这一能力。


MathML 文本容器 - 学习 Web 开发

MathML 文本容器
===========

* 上一页
* Overview: First steps
* 下一页

现在，我们将重点介绍 MathML 的文本容器（变量、数字、运算符等），它们是 MathML 公式的构建模块。

|  |  |
| --- | --- |
| 前提： |
 基本的计算机知识，已安装基本软件，了解基本的文件操作，了解 HTML 基础知识（学习HTML 入门），并对文本样式方面的 CSS 有一定的了解（阅读文本和字体样式基础和
 Web 字体）。
  |
| 目标： | 熟悉用于编写文本的 MathML 元素，并掌握它们的特殊行为。 |

数学符号的 Unicode 字符
----------------

数学公式涉及许多特殊字符，例如希腊字母（例如 Δ）、弗拉克图尔字母（例如 𝔄）、双线字母（例如 ℂ）、二元运算符（例如 ≠）、箭头（例如 ⇒）、积分符号（例如 ∮）、求和符号（例如 ∑）、逻辑符号（例如 ∀）以及括号（例如 ⌊）等等。维基百科的文章数学运算符和符号的 Unicode 提供了这些字符的概述。

由于这些字符大多不属于基本拉丁 Unicode 块，因此建议指定你的文档字符编码，并使用适当的 Web 字体。以下是一个使用 UTF-8 编码和 Latin Modern Math (en-US) 字体的基本模板：

```html
<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <title>带数学字符的页面</title>
    <link
 rel="stylesheet"
 href="https://fred-wang.github.io/MathFonts/LatinModern/mathfonts.css" />
  </head>
  <body>
    <p style="font-family: Latin Modern Math">∀A∊𝔰𝔩(n,𝔽),TrA=0</p>
  </body>
</html>
```

一些语义
----

我们在 MathML 使用入门文章中提到，MathML 公式中的文本被包裹在特定的容器元素中，例如 `<mn>` 或 `<mo>`。一般而言，MathML 公式中的所有文字都必须包含在这类容器元素中，这类元素被称为*标记*（token）元素。此外，MathML 提供了多种标记元素，以区分文字内容的不同含义。

* `<mi>` 元素表示“标识符”，其可以是符号名称或任意文本。例如：`<mi>x</mi>`（变量）、`<mi>cos</mi>`（函数名）和 `<mi>π</mi>`（符号常量）。
* `<mn>` 元素表示“数值文字”或其他应呈现为数值文字的数据。例如：`<mn>2</mn>`（整数）、`<mn>0.123</mn>`（小数）或 `<mn>0xFFEF</mn>`（十六进制值）。
* `<mo>` 元素表示运算符或任何应呈现为运算符的内容。例如：`<mo>+</mo>`（二元运算符）、`<mo>≤</mo>`（二元关系符），`<mo>∑</mo>`（求和符号）或 `<mo>[</mo>`（括号）。
* `<mtext>` 元素用于表示任意文本。例如公式中的短词，如 `<mtext>if</mtext>` 或 `<mtext>映射到</mtext>`。
### 主动学习：识别标记元素

下面是一个更复杂的例子，它告诉我们，只有当一个实数是非负数时，它的绝对值才等于它自己。请注意不同的标记元素，它们有什么作用。点击文本中的任何部分，就会高亮显示，并给出相应的解释。
```

<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <title>带数学字符的页面</title>
    <link
 rel="stylesheet"
 href="https://fred-wang.github.io/MathFonts/LatinModern/mathfonts.css" />
  </head>
  <body>
    <math display="block">
      <mrow>
        <mrow>
          <mo>|</mo>
          <mi>x</mi>
          <mo>|</mo>
        </mrow>
        <mo>=</mo>
        <mi>x</mi>
      </mrow>
      <mtext> iff </mtext>
      <mrow>
        <mi>x</mi>
        <mo>≥</mo>
        <mn>0</mn>
      </mrow>
    </math>
    <input type="button" id="clearOutput" value="重置" />
    <div id="output"></div>
  </body>
</html>
```

```
.highlight {
  color: red;
}
math {
  font-size: 200%;
}
```

```
const tokenElements = Array.from(
  document.querySelectorAll("mi, mo, mn, mtext"),
);
const outputDiv = document.getElementById("output");
function clearHighlight() {
  tokenElements.forEach((token) => {
    token.classList.remove("highlight");
  });
}
tokenElements.forEach((token) => {
  token.addEventListener("click", () => {
    clearHighlight();
    token.classList.add("highlight");
    outputDiv.insertAdjacentHTML(
      "beforeend",
      `<p><strong>你点击了 <code><${token.tagName}></code> 元素。</strong></p>`,
    );
  });
});
document.getElementById("clearOutput").addEventListener("click", () => {
  clearHighlight();
  outputDiv.innerHTML = "";
});
```

最后，阅读 MathML 源代码以验证其是否符合你的期望：

```xml
<math display="block">
  <mrow>
    <mrow>
      <mo>|</mo>
      <mi>x</mi>
      <mo>|</mo>
    </mrow>
    <mo>=</mo>
    <mi>x</mi>
  </mrow>
  <mtext> iff </mtext>
  <mrow>
    <mi>x</mi>
    <mo>≥</mo>
    <mn>0</mn>
  </mrow>
</math>
```

**备注：** 对于给定的文本内容，有时很难确定要使用哪个标记元素。在实践中，选择错误的元素通常不会导致重大问题，因为所有的标记元素在浏览器实现中通常都会以相同的方式呈现（用于视觉显示和辅助技术）。然而，`<mi>` 和 `<mo>` 元素具有特殊的区别特征，需要注意。下面的小节将对它们进行解释。

<mi> 的自动斜体化
-----------

在数学中的一种排版惯例是使用斜体字母表示变量。为了帮助实现这一点，仅具有单个字符的 `<mi>` 元素会自动呈现为斜体。请比较以下公式中两个 `<mi>` 元素的渲染效果：

```html
<math>
  <mi>sin</mi>
  <mi>x</mi>
</math>
```

**备注：** MathML Core 中的这个表格提供了受斜体化影响的字符的详尽列表，以及相应的斜体字符。

阻止对 <mi> 的自动斜体化
---------------

要想阻止 `<mi>` 元素的自动斜体化，可以在该元素上附加 `mathvariant="normal"` 属性。比较以下公式中大写伽马字母的渲染效果：

```html
<math>
  <mi>Γ</mi>
  <mi mathvariant="normal">Γ</mi>
</math>
```

**备注：** 尽管可以应用此转换，但通常你只需直接使用所需的数学字母数字符号即可。

<mo> 的运算符属性
-----------

MathML 包含一个运算符字典，其根据 `<mo>` 元素的内容和其在容器中的位置（前缀、中缀或后缀），定义了默认属性。让我们考虑一个具体的例子：

```html
<table>
  <tr>
    <td>前缀加号</td>
    <td>
      <math>
        <mo>+</mo>
        <mi>i</mi>
      </math>
    </td>
  </tr>
  <tr>
    <td>中缀加号</td>
    <td>
      <math>
        <mi>j</mi>
        <mo>+</mo>
        <mi>i</mi>
      </math>
    </td>
  </tr>
  <tr>
    <td>前缀求和符号</td>
    <td>
      <math>
        <mo>∑</mo>
        <mi>i</mi>
      </math>
    </td>
  </tr>
</table>
```

本示例渲染结果应该与下面的屏幕截图类似。观察 `<mi>i</mi>` 元素及其前面的 `<mo>` 之间的间距：前缀加号没有间距，中缀加号有一些间距，前缀求和符号有一些较小的间距。

![具有不同运算符间距的 MathML 公式的屏幕截图](/zh-CN/docs/Learn/MathML/First_steps/Text_containers/operator-spacing.png)

运算符还有许多其他属性，我们将在后面的内容中详细了解。现在，请记住在运算符字典中使用 `<mo>` 容器来包含字符，并使用 `<mrow>` 元素正确分组子表达式，以利于 MathML 的渲染。

### 主动学习：发现不同点

现在你对 `<mi>` 和 `<mo>` 的特殊特性有了一些了解，让我们使用一些实际中用到的 MathML 来重写页面顶部的示例中的 `<p>` 元素。比较新版本与纯文本版本在你的浏览器中的视觉呈现，并解释两者的区别。

```html
<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <title>带数学字符的页面</title>
    <link
 rel="stylesheet"
 href="https://fred-wang.github.io/MathFonts/LatinModern/mathfonts.css" />
  </head>
  <body>
    <p style="font-family: Latin Modern Math">∀A∊𝔰𝔩(n,𝔽),TrA=0</p>
    <p>
      <math>
        <mo>∀</mo>
        <mrow>
          <mi>A</mi>
          <mo>∊</mo>
          <mrow>
            <mi>𝔰𝔩</mi>
            <mrow>
              <mo>(</mo>
              <mi>n</mi>
              <mo>,</mo>
              <mi>𝔽</mi>
              <mo>)</mo>
            </mrow>
          </mrow>
        </mrow>
        <mo>,</mo>
        <mrow>
          <mrow>
            <mi>Tr</mi>
            <mi>A</mi>
          </mrow>
          <mo>=</mo>
          <mn>0</mn>
        </mrow>
      </math>
    </p>
    <input id="showSolution" type="button" value="显示结果" />
    <div id="solution"></div>
  </body>
</html>
```

```
div {
  padding: 0.5em;
}
```

```
document.getElementById("showSolution").addEventListener(
  "click",
  () => {
    document.getElementById("solution").insertAdjacentHTML(
      "beforeEnd",
      `<ul>
 <li><strong>包裹“A”和“n”变量的<code><mi></code>元素以斜体渲染</strong>。但是，具有包含多个字符的“𝔰𝔩”或字符为“𝔽”的<code><mi></code>元素仍然以直立的方式渲染。</li>
 <li><strong>在文本为“∀”、“∊”、“=”或逗号的<code><mo></code>元素周围自动添加间距</strong>。但是，其中一些元素之前没有添加间距，而括号周围也没有间距。</li>
 </ul>`,
    );
  },
  { once: true },
);
```

**备注：** 一个明显的区别是，使用 MathML 的源代码会变得更加冗长。请记住，本教程是关于学习语言本身，但在实践中，MathML 内容通常不会手动编写。要查看更多信息，请参阅编写 MathML 页面。

### 主动学习：可伸缩运算符

操作符词典定义了一些操作符的默认*可伸缩*属性以及相应的*伸缩轴*。例如，操作符可以默认垂直伸展，以覆盖其 `<mrow>` 容器中非可伸缩兄弟节点的最大高度。通过稍微调整之前的练习，你可以使操作符在垂直方向上伸展。你能找到它们吗？
```

<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <title>具有垂直操作符的页面</title>
    <link
 rel="stylesheet"
 href="https://fred-wang.github.io/MathFonts/LatinModern/mathfonts.css" />
  </head>
  <body>
    <math display="block">
      <mrow>
        <mrow>
          <mo>|</mo>
          <mfrac>
            <mn>1</mn>
            <mi>x</mi>
          </mfrac>
          <mo>|</mo>
        </mrow>
        <mo>=</mo>
        <mfrac>
          <mn>1</mn>
          <mrow>
            <mo>|</mo>
            <mi>x</mi>
            <mo>|</mo>
          </mrow>
        </mfrac>
        <mo>=</mo>
        <mfrac>
          <mn>1</mn>
          <mi>x</mi>
        </mfrac>
      </mrow>
      <mtext> iff </mtext>
      <mrow>
        <mi>x</mi>
        <mo>≥</mo>
        <mn>0</mn>
      </mrow>
    </math>
    <input type="button" id="clearOutput" value="重置" />
    <div id="output"></div>
  </body>
</html>
```

```
.highlight {
  color: red;
}
math {
  font-size: 200%;
}
```

```
const tokenElements = Array.from(
  document.querySelectorAll("mi, mo, mn, mtext"),
);
const stretchyMoElements = Array.from(
  document.getElementsByTagName("mo"),
).slice(0, 2);
const outputDiv = document.getElementById("output");
function clearHighlight() {
  tokenElements.forEach((token) => {
    token.classList.remove("highlight");
  });
}
tokenElements.forEach((token) => {
  token.addEventListener("click", () => {
    clearHighlight();
    token.classList.add("highlight");
    let message = "";
    let tagName = `<code><${token.tagName}></code>`;
    if (token.tagName !== "mo") message = `不，这是 ${tagName} 元素！`;
    else if (!stretchyMoElements.includes(token))
      message = `不，这是 ${tagName} 元素，但它不是垂直伸展元素。`;
    else
      message = `正确，此 ${tagName} 元素伸展至它的 <code><mfrac></code> 兄弟元素的高度。`;
    outputDiv.insertAdjacentHTML(
      "beforeend",
      `<p><strong>${message}</strong></p>`,
    );
  });
});
document.getElementById("clearOutput").addEventListener("click", () => {
  clearHighlight();
  outputDiv.innerHTML = "";
});
```

一如往常，请你在完成后阅读源码：

```xml
<math display="block">
  <mrow>
    <mrow>
      <mo>|</mo>
      <mfrac>
        <mn>1</mn>
        <mi>x</mi>
      </mfrac>
      <mo>|</mo>
    </mrow>
    <mo>=</mo>
    <mfrac>
      <mn>1</mn>
      <mrow>
        <mo>|</mo>
        <mi>x</mi>
        <mo>|</mo>
      </mrow>
    </mfrac>
    <mo>=</mo>
    <mfrac>
      <mn>1</mn>
      <mi>x</mi>
    </mfrac>
  </mrow>
  <mtext> iff </mtext>
  <mrow>
    <mi>x</mi>
    <mo>≥</mo>
    <mn>0</mn>
  </mrow>
</math>
```

**警告：** 通常需要特殊的数学字体 (en-US)才能实现文本的拉伸效果，前面的示例依赖于 Web 字体。

总结
--

在本文中，我们学习了一些用作文本容器的*标记*元素及其不同的语义，即 `<mi>`（标识符）、`<mn>`（数字）、`<mo>`（运算符）、`<mtext>`（通用文本）。我们介绍了在数学公式中常见的特殊 Unicode 字符，并概述了 `<mi>` 和 `<mo>` 元素的一些可观察表现。在下一篇文章中，我们将学习如何借助*标记*元素构建更复杂的表达式，例如分数和根号。

* 上一页
* Overview: First steps
* 下一页
参见
--

* `<mi>` 元素 (en-US)
* `<mn>` 元素 (en-US)
* `<mo>` 元素 (en-US)
* `<mtext>` 元素 (en-US)


MathML 使用入门 - 学习 Web 开发

MathML 使用入门
===========

* Overview: First steps
* 下一页

在本文中，我们将使用一个简单的 HTML 文档，了解如何在其中添加 MathML 公式，并顺便介绍一些元素。

|  |  |
| --- | --- |
| 前提： | 基本的计算机操作能力，已安装基本软件，基本的文件操作知识以及 HTML 基础知识（学习过 HTML 简介。） |
| 目标： | 了解 MathML 的基本语法以及如何将其集成到 HTML 页面中。 |

通过 <math> 元素在 HTML 中插入公式
------------------------

MathML 使用与 HTML 相同的语法来表示元素和属性的树形结构。特别地，每个数学公式都由一个 `<math>` 元素表示，该元素可以放置在 HTML 页面中。在下面的文档中，它位于一个段落文本中：

```html
<!doctype html>
<html lang="zh-CN">
  <head>
    <title>我的第一个数学页面</title>
  </head>
  <body>
    <p>
      分数
      <math>
        <mfrac>
          <mn>1</mn>
          <mn>3</mn>
        </mfrac>
      </math>
      不是十进制数。
    </p>
  </body>
</html>
```

`<mfrac>` 元素指定了一个分数，其中分子是它的第一个子元素，分母是它的第二个子元素。以下是它在浏览器中的呈现方式：

**警告：** 如果你只看到“1 3”而不是一个分数，那么你的浏览器可能不支持 MathML。请查看浏览器兼容性表格获取进一步的详细信息。

### display 属性

请注意，在前面的示例中，公式与段落文本在一行上。然而，通常情况下，我们会将大的数学公式居中显示在独立的行上，如下所示。为了实现这一点，你需要在 `<math>` 元素上附加一个 `display="block"` 属性。
```

<!doctype html>
<html lang="zh-CN">
  <head>
    <title>我的第一个数学页面</title>
  </head>
  <body>
    <p>
      分数
      <math display="block">
        <mfrac>
          <mn>1</mn>
          <mn>3</mn>
        </mfrac>
      </math>
      不是一个十进制数。
    </p>
  </body>
</html>
```

你可能还会注意到一些细微的外观变化：分数的文本和垂直间距变大了一点。没有 `display="block"` 属性，高度会被最小化，以避免干扰周围文本的流畅性。使用 `display="block"` 属性时，优先考虑的是数学公式的易读性。

**备注：** 这对应于 LaTeX 中的*行内*公式（用美元符号 `$...$` 包围）和*展示*公式（用 `\[...\]` 包围）的概念。

**备注：** 上述提到的外观变化实际上是由 `math-style` (en-US) 属性控制的，该属性初始值为代表 `<math display="block">` 的 `normal`，其他情况下为 `compact`。在某些 MathML 子树中，此属性可能会自动变为 `compact`，但在本入门教程中我们将忽略这个细微差别。这与 LaTeX 类似。

使用 <mrow> 元素进行分组
----------------

实际上，`<math>` 元素可以包含任意数量的子元素，并且将它们在一行内呈现。例如，简单的公式“1 + 2 + 3”在 MathML 中的编码如下：

```html
<math>
  <mn>1</mn>
  <mo>+</mo>
  <mn>2</mn>
  <mo>+</mo>
  <mn>3</mn>
</math>
```

`<mrow>` 元素是一个通用容器，可以执行类似的布局，但可以放置在 MathML 子树的任何位置。它可以将多个元素组合在一起。例如，下面的分数的分子部分（它的第一个子元素）是“一加二”。

```html
<math>
  <mfrac>
    <mrow>
      <mn>1</mn>
      <mo>+</mo>
      <mn>2</mn>
    </mrow>
    <mn>3</mn>
  </mfrac>
</math>
```

### 主动练习：内嵌表达式

作为一项练习，我们将使用我们已经了解的 MathML 元素来编写以下表达式。如果你有困惑或想验证解决方案，请查看示例的源代码。
```

<ol>
  <li>
    “二分之一”加上“三分之二”：
    <!-- 对于 math 元素的子元素使用 mrow 元素进行分组是可以的，但不是必需的。 -->
    <math>
      <mfrac>
        <mn>1</mn>
        <mn>2</mn>
      </mfrac>
      <mo>+</mo>
      <mfrac>
        <mn>2</mn>
        <mn>3</mn>
      </mfrac>
    </math>
  </li>
  <li>
    “一加二加三”除以“四加五”：
    <math>
      <mfrac>
        <mrow>
          <mn>1</mn>
          <mo>+</mo>
          <mn>2</mn>
          <mo>+</mo>
          <mn>3</mn>
        </mrow>
        <mrow>
          <mn>4</mn>
          <mo>+</mo>
          <mn>5</mn>
        </mrow>
      </mfrac>
    </math>
  </li>
  <li>
    “四分之一”除以“二加三”：
    <math>
      <mfrac>
        <mfrac>
          <mn>1</mn>
          <mn>4</mn>
        </mfrac>
        <mrow>
          <mn>2</mn>
          <mo>+</mo>
          <mn>3</mn>
        </mrow>
      </mfrac>
    </math>
  </li>
</ol>
```

总结
--

本文中，我们介绍了如何使用 `<math>` 元素将数学公式插入 HTML 文档中。我们了解了使用 `display="block"` 和不使用它的 `<math>` 元素之间的渲染差异。此外，我们还介绍了几个其他的 MathML 元素：`<mfrac>` 用于表示分数，`<mrow>` 用于分组，以及一些文本元素。在下一篇文章中，我们将进一步分析这些文本容器。

* Overview: First steps
* 下一页
参见
--

* 使用 MathML
* `<math>` 元素
* `<mfrac>` 元素 (en-US)
* `<mrow>` 元素
* `math-style` 属性 (en-US)
* `math-depth` 属性 (en-US)


MathML 分数和根号 - 学习 Web 开发

MathML 分数和根号
============

* 上一页
* Overview: First steps
* 下一页

本文在前文对文本容器介绍的基础上，介绍了如何通过嵌套分数和根号构建更复杂的 MathML 表达式。

|  |  |
| --- | --- |
| 前提： | 基本的计算机操作能力，已安装基本软件，基本的文件操作知识，以及 HTML 基础知识（学习过 HTML 简介。） |
| 目标： | 熟悉用于编写分数和平方根的 MathML 元素。 |

<mfrac>、<msqrt> 和 <mroot> 的子树
-----------------------------

在 MathML 使用入门文章中，我们学习了用于描述分数的 `<mfrac>` 元素。现在让我们看一个基本示例，其中还添加了用于根号（`<msqrt>` 和 `<mroot>`）的新元素：

```html
<math>
  <mfrac>
    <mtext>child1</mtext>
    <mtext>child2</mtext>
  </mfrac>
</math>
<br />
<math>
  <msqrt>
    <mtext>child1</mtext>
    <mtext>child2</mtext>
    <mtext>...</mtext>
    <mtext>childN</mtext>
  </msqrt>
</math>
<br />
<math>
  <mroot>
    <mtext>child1</mtext>
    <mtext>child2</mtext>
  </mroot>
</math>
```

下面是浏览器渲染的屏幕截图：

![mfrac、msqrt 和 mroot 的屏幕截图](/zh-CN/docs/Learn/MathML/First_steps/Fractions_and_roots/mfrac-msqrt-mroot.png)

* 我们知道 `<mfrac>` 元素被渲染为一个分数：第一个子元素（分子）被绘制于第二个子元素（分母）的上方，并由一条水平线分隔。
* `<msqrt>` 被渲染为一个平方根：其子元素呈现为一个 `<mrow>`，前缀为根号√，并完全被一条横线覆盖。
* 最后，`<mroot>` 元素被渲染为一个 n 次方根：第一个元素被根号符号覆盖，而第二个元素被用作根的次数，并作为前缀上标呈现。
### 主动练习：内嵌不同元素

这是一个简单的练习，旨在验证你是否理解了 MathML 子树与其可视化呈现之间的关系。该文档包含一个 MathML 公式，你需要标记所有与该 MathML 公式中的子树相对应的子树。完成后，你可以查看 MathML 公式的源代码，以验证它是否符合你的预期。
```

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8"/>
    <title>带数学字符的页面</title>
    <link rel="stylesheet" href="https://fred-wang.github.io/MathFonts/LatinModern/mathfonts.css">
  </head>
  <body>
  <p>
    <math>
    <mfrac id="mfrac1">
      <msqrt id="msqrt1">
        <mn>2</mn>
      </msqrt>
      <mroot id="mroot1">
        <mn>4</mn>
        <mn>3</mn>
      </mroot>
    </mfrac>
    <mo>+</mo>
    <mroot id="mroot2">
      <mn>5</mn>
      <mfrac id="mfrac2">
        <mn>6</mn>
        <mn>7</mn>
      </mfrac>
    </mroot>
    <mo>+</mo>
    <msqrt id="msqrt2">
      <mn>8</mn>
      <mo>−</mo>
      <mn>9</mn>
    </msqrt>
  </p>

  <ol id="options">
    <li>
      <input type="checkbox" data-comment="验证 mfrac 中子元素的顺序！">
      一个将 mroot 作为第一个子元素，将 msqrt 作为第二个子元素的 mfrac。
    </li>
    <li>
      <input type="checkbox" data-highlight="mroot2" data-comment="5 的七分之六次方根。">
      一个将 mn 作为第一个子元素，将 mfrac 作为第二个子元素的 mroot。
    </li>
    <li>
      <input type="checkbox" data-comment="该公式不包含任何带有分数的平方根！">
      一个包含 mfrac 元素的 msqrt。
    </li>
    <li>
      <input type="checkbox" data-comment="2 的平方根。" data-highlight="msqrt1">
      一个具有一个 mn 子元素的 msqrt。
    </li>
    <li>
      <input type="checkbox" data-comment="验证 mroot 中子元素的顺序！">
      一个将 mfrac 作为第一个子元素，将 mn 作为第二个子元素的 mroot。
    </li>
    <li>
      <input type="checkbox" data-comment="“8 减 9”的平方根。" data-highlight="msqrt2">
      一个具有以下子元素列表的 msqrt：mn、mo、mn。
    </li>
    <li>
      <input type="checkbox" data-comment="2 的平方根除以 4 的立方根。" data-highlight="mfrac1">
      一个将 msqrt 作为第一个子元素，将 mroot 作为第二个子元素的 mfrac。
    </li>
    <li>
      <input type="checkbox" data-comment="mfrac 必须恰好有两个子元素！">
      一个具有以下子元素列表的 mfrac：msqrt、mn、msqrt。
    </li>
    <li>
      <input type="checkbox" data-comment="mroot 必须恰好有两个子元素！">
      一个具有一个 mn 子元素的 mroot。
    </li>
    <li>
      <input type="checkbox" data-comment="6 除以 7。" data-highlight="mfrac2">
      一个具有两个 mn 子元素的 mfrac。
    </li>
    <li>
      <input type="checkbox" data-comment="该公式不包含超过两个数字的平方根！">
      一个具有五个 mn 子元素的 msqrt。
    </li>
    <li>
      <input type="checkbox" data-highlight="mroot1" data-comment="4 的立方根。">
      一个具有两个 mn 子元素的 mroot。
    </li>
  </ol>
  <p>
    <strong id="comment"></strong>
  </p>
  <p>
    <strong id="status"></strong>
  </p>
  </body>
```

```
math {
  font-family:
    Latin Modern Math,
    STIX Two Math;
  font-size: 200%;
}
math .highlight {
  background: pink;
}
math [id] .highlight {
  background: lightblue;
}
p {
  padding: 0.5em;
}
```

```
const options = document.getElementById("options");
const comment = document.getElementById("comment");
const checkboxes = Array.from(options.getElementsByTagName("input"));
const status = document.getElementById("status");
function verifyOption(checkbox) {
  let mathml = checkbox.dataset.highlight;
  if (mathml) {
    mathml = document.getElementById(mathml);
  }
  if (checkbox.checked) {
    comment.textContent = checkbox.dataset.comment;
    if (mathml) {
      mathml.classList.add("highlight");
    } else {
      checkbox.checked = false;
    }
  } else {
    comment.textContent = "";
    if (mathml) {
      mathml.classList.remove("highlight");
    }
  }
  const finished = checkboxes.every(
    (checkbox) => !!checkbox.checked === !!checkbox.dataset.highlight,
  );
  status.textContent = finished ? "恭喜，你选中了所有正确选项！" : "";
}
checkboxes.forEach((checkbox) => {
  checkbox.addEventListener("change", () => {
    verifyOption(checkbox);
  });
});
```

可伸缩的根号符号
--------

正如之前所见，`<msqrt>` 和 `<mroot>` 元素上的上横线会水平拉伸以覆盖其内容。但实际上根号符号 √ 也会拉伸以与其内容一样高。
```

<link
 rel="stylesheet"
 href="https://fred-wang.github.io/MathFonts/LatinModern/mathfonts.css" />
```

```html
<math display="block">
  <mroot>
    <msqrt>
      <mfrac>
        <mn>1</mn>
        <mn>2</mn>
      </mfrac>
    </msqrt>
    <mn>3</mn>
  </mroot>
</math>
```

**警告：** 通常需要特殊的数学字体 (en-US)才能实现该拉伸效果，上面的示例依赖于 Web 字体。

无横线的分数
------

有些数学概念有时会使用类似分数的表示法，例如二项式系数或勒让德符号。对于这种不绘制横线的类似分数的表示法，可以在 `<mfrac>` 元素上附加 `linethickness="0"` 属性进行标记：
```

<link
 rel="stylesheet"
 href="https://fred-wang.github.io/MathFonts/LatinModern/mathfonts.css" />
```

```html
<math display="block">
  <mrow>
    <mo>(</mo>
    <mfrac linethickness="0">
      <mn>3</mn>
      <mn>2</mn>
    </mfrac>
    <mo>)</mo>
  </mrow>
  <mo>=</mo>
  <mn>3</mn>
  <mo>≠</mo>
  <mfrac>
    <mn>3</mn>
    <mn>2</mn>
  </mfrac>
</math>
```

**备注：** 虽然 `linethickness` 属性可以用于指定任意的线条粗细，但最好保持默认值，该值是根据数学字体中指定的参数计算得出的。

总结
--

在本文中，我们学习了如何使用 `<mfrac>`、`<msqrt>` 和 `<mroot>` 元素来构建分数和根式。我们注意到了这些元素的一些特殊功能，即分数和根号符号。我们还学习了如何使用 `linethickness` 属性绘制无横线的分数。在下一篇文章中，我们将继续介绍基本的数学符号，并学习上下标记的使用。

* 上一页
* Overview: First steps
* 下一页
参见
--

* `<mfrac>` 元素 (en-US)
* `<msqrt>` 元素
* `<mroot>` 元素


三个著名的数学公式 - 学习 Web 开发

三个著名的数学公式
=========

* 上一页
* Overview: First steps

|  |  |
| --- | --- |
| 前提： | 在尝试本测验之前，你应该已经完成本单元中的所有文章，并且对 HTML 基础知识有所了解（请参阅 HTML 简介）。 |
| 目标： | 尝试使用 MathML 并测试你的新技能。 |

一篇数学小文章
-------

目标是使用 HTML 和 MathML 重写以下数学文章：

![通过 XeLaTeX 生成的 PDF 输出的屏幕截图](/zh-CN/docs/Learn/MathML/First_steps/Three_famous_mathematical_formulas/xelatex-output.png)

尽管你不需要熟悉 LaTeX，但知道生成它的 LaTeX 源代码可能会对你有帮助：

```latex
\documentclass{article}

\usepackage{amsmath}
\usepackage{amssymb}

\begin{document}

要解决三次方程 $t^3 + pt + q = 0$（其中实数 $p, q$ 满足 ${4p^3 + 27q^2} > 0$），可以使用卡尔达诺公式：

\[
 \sqrt[{3}]{
 -\frac{q}{2}
 +\sqrt{\frac{q^2}{4} + {\frac{p^{3}}{27}}}
 }+
 \sqrt[{3}]{
 -\frac{q}{2}
 -\sqrt{\frac{q^2}{4} + {\frac{p^{3}}{27}}}
 }
\]

对于任意 $u\_1, \dots, u\_n \in \mathbb{C}$ 和 $v\_1, \dots, v\_n \in \mathbb{C}$，柯西—布尼亚科夫斯基—施瓦茨不等式可以写成如下形式：

\[
 \left| \sum\_{k=1}^n {u\_k \bar{v\_k}} \right|^2
 \leq
 {
 \left( \sum\_{k=1}^n {|u\_k|} \right)^2
 \left( \sum\_{k=1}^n {|v\_k|} \right)^2
 }
\]

最后，范德蒙行列式可以使用以下表达式计算：

\[
 \begin{vmatrix}
 1 & x\_1 & x\_1^2 & \dots & x\_1^{n-1} \\
 1 & x\_2 & x\_2^2 & \dots & x\_2^{n-1} \\
 1 & x\_3 & x\_3^2 & \dots & x\_3^{n-1} \\
 \vdots & \vdots & \vdots & \ddots & \vdots \\
 1 & x\_n & x\_n^2 & \dots & x\_n^{n-1} \\
 \end{vmatrix}
 = {\prod\_{1 \leq {i,j} \leq n} {(x\_i - x\_j)}}
\]

\end{document}
```

起始点
---

要开始这个测验，你可以使用我们常用的 HTML 模板。默认情况下，它使用 UTF-8 编码，在 `<body>` 和 `<math>` 标签上使用特殊的 Web 字体（具有与 LaTeX 输出类似的界面外观）。目标是将问号 `???` 替换为实际的 MathML 内容。

```html
<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <title>三个著名的数学公式</title>
    <link
 rel="stylesheet"
 href="https://fred-wang.github.io/MathFonts/LatinModern/mathfonts.css" />
  </head>
  <body class="htmlmathparagraph">
    <p>
      要解决三次方程 ???（其中实数 ??? 满足 ???），可以使用卡尔达诺公式：???
    </p>

    <p>
      对于任意 ??? 和 ???，柯西—布尼亚科夫斯基—施瓦茨不等式可以写成如下形式：???
    </p>

    <p>最后，范德蒙行列式可以使用以下表达式计算：???</p>
  </body>
</html>
```

提示和技巧
-----

* 首先插入空的 `<math>` 标签，并决定它们是否应该有 `display="block"` 属性。
* 检查所使用的文本，并找到它们的 Unicode 字符（“−”、“ℂ”、“∑”等）。
* 分析每个文本部分的语义（变量？运算符？数字？），并确定每个部分应使用的正确的标记元素。
* 查找高级结构（分数？根号？上下标？矩阵？），并确定每个结构应使用的正确的 MathML 元素。
* 别忘了使用 `<mrow>` 对子表达式进行分组。
* 注意可伸缩和大型运算符！
* 使用 W3C 验证器检查你的 HTML/MathML 标记中是否有意外的错误。
* 如果你陷入困境，或感到手动编写 MathML 有多么痛苦，请随时使用编写 MathML 的工具（如 TeXZilla）。
* 上一页
* Overview: First steps


MathML 附加符号 - 学习 Web 开发

MathML 附加符号
===========

* 上一页
* Overview: First steps
* 下一页

我们继续回顾基本数学符号，并重点介绍如何使用附加符号构建 MathML 元素。

|  |  |
| --- | --- |
| 前提： |
 基本的计算机操作能力、安装基本软件、处理文件的基本知识，以及
 HTML 基础知识（可以学习 HTML 入门。）
  |
| 目标： | 熟悉基本的 MathML 上下标元素。 |

下标和上标
-----

类似于我们在上一篇文章中所学习的，`<msub>`、`<msup>` 和 `<msubsup>` 具有特殊的结构，它们恰好有两个元素（`<msub>`、`<msup>`）或三个元素（`<msubsup>`）：

```html
<p>
  msub:
  <math>
    <msub>
      <mtext>child1</mtext>
      <mtext>child2</mtext>
    </msub>
  </math>
</p>

<p>
  msup:
  <math>
    <msup>
      <mtext>child1</mtext>
      <mtext>child2</mtext>
    </msup>
  </math>
</p>
<p>
  msubsup:
  <math>
    <msubsup>
      <mtext>child1</mtext>
      <mtext>child2</mtext>
      <mtext>child3</mtext>
    </msubsup>
  </math>
</p>
```

以下是在你的浏览器中的上述示例的渲染结果。

可以看出：

* `<msub>` 元素的第二个子元素作为其第一个子元素的下标附加。
* `<msup>` 元素的第二个子元素作为其第一个子元素的上标附加。
* `<msubsup>` 元素的第二个和第三个子元素分别作为其第一个子元素的下标和上标附加。
* 上下标内的文本会缩小显示。

**备注：** MathML 元素 `<msub>` 和 `<msup>` 与 HTML 元素 `<sub>` 和 `<sup>` 不同。它们允许开发者将任意的 MathML 子树作为附加符号，而不仅仅是文本。

正下标和正上标
-------

`<munder>`、`<mover>` 和 `<munderover>` 元素与之前介绍的元素非常相似，只是它们用于附加正下标（underscript）和正上标（overscirpt）。我们不会提供详细信息，而是让你自己通过以下练习来了解它们的定义。

### 主动学习：识别正下标和正上标

在下面的示例中，试着猜测神秘元素的名称（用问号表示），然后点击按钮来显示答案：
```

<p>
  <code><<span>????????</span>></code>
  元素正好具有两个子元素（child1、child2）：
  <math>
    <mover>
      <mtext>child1</mtext>
      <mtext>child2</mtext>
    </mover>
  </math>
</p>
<p>
  <code><<span>????????</span>></code>
  元素正好具有三个子元素（child1、child2 以及 child3）：
  <math>
    <munderover>
      <mtext>child1</mtext>
      <mtext>child2</mtext>
      <mtext>child3</mtext>
    </munderover>
  </math>
</p>
<p>
  <code><<span>????????</span>></code>
  元素正好具有两个子元素（child1、child2）：
  <math>
    <munder>
      <mtext>child1</mtext>
      <mtext>child2</mtext>
    </munder>
  </math>
</p>

<p><input type="button" id="showSolution" value="展示结果" /></p>
```

```
p {
  padding: 0.5em;
}
```

```
document.getElementById("showSolution").addEventListener("click", () => {
  const maths = Array.from(document.getElementsByTagName("math"));
  Array.from(document.getElementsByTagName("span")).forEach((span, index) => {
    span.textContent = maths[index].firstElementChild.tagName;
  });
});
```

### 主动学习：识别附加符号元素

以下的 MathML 公式包含了更复杂的表达式，嵌套了分数、根号和附加符号元素。尝试猜测使用了 `<msub>`、`<msup>`、`<msubsup>`、`<munder>`、`<mover>`、`<munderover>` 的元素。每次当你点击这样的元素时，它会被突出显示，并显示确认信息。最后，阅读 MathML 源代码以验证是否与你的预期相符。
```

<!doctype html>
<html lang="en-US">
  <head>
    <meta charset="utf-8" />
    <title>带有附加符号元素的页面</title>
    <link
 rel="stylesheet"
 href="https://fred-wang.github.io/MathFonts/LatinModern/mathfonts.css" />
  </head>
  <body>
    <math display="block">
      <mroot>
        <mrow>
          <munder>
            <mi>β</mi>
            <mo>⎵</mo>
          </munder>
        </mrow>
        <mn>3</mn>
      </mroot>
      <mo>+</mo>
      <mfrac>
        <mrow
 ><mo>|</mo>
          <mover>
            <mi>α</mi>
            <mo>→</mo>
          </mover>
          <mo>|</mo>
        </mrow>
        <msup>
          <mi>s</mi>
          <mn>3</mn>
        </msup>
      </mfrac>
      <mo>−</mo>
      <mrow>
        <munderover>
          <mo>∑</mo>
          <mrow>
            <mi>i</mi>
            <mo>=</mo>
            <mn>1</mn>
          </mrow>
          <mi>n</mi>
        </munderover>
        <msqrt>
          <mrow>
            <msub>
              <mi>a</mi>
              <mi>i</mi>
            </msub>
            <mo>−</mo>
            <msubsup>
              <mi>K</mi>
              <mn>0</mn>
              <mi>i</mi>
            </msubsup>
          </mrow>
        </msqrt>
      </mrow>
    </math>
    <input type="button" id="clearOutput" value="重置" />
    <div id="output"></div>
  </body>
</html>
```

```
.highlight {
  color: red;
}
math {
  font-size: 200%;
}
```

```
const scriptedElements = Array.from(
  document.querySelectorAll("msub, msup, msubsup, munder, mover, munderover"),
);
const outputDiv = document.getElementById("output");
function clearHighlight() {
  scriptedElements.forEach((scripted) => {
    scripted.classList.remove("highlight");
  });
}
scriptedElements.forEach((scripted) => {
  scripted.addEventListener("click", () => {
    clearHighlight();
    scripted.classList.add("highlight");
    outputDiv.insertAdjacentHTML(
      "beforeend",
      `<p><strong>你点击了 <code><${scripted.tagName}></code> 元素。</strong></p>`,
    );
  });
});
document.getElementById("clearOutput").addEventListener("click", () => {
  clearHighlight();
  outputDiv.innerHTML = "";
});
```

其他运算符属性
-------

我们之前已经了解了 `<mo>` 元素的一些属性，包括在垂直方向上的拉伸和间距。现在有了附加符号元素，我们可以扩展该列表。我们将通过修改我们的上一个示例来实现这一点。

### 在水平方向上的拉伸

首先，让我们进行以下替换：

β
≔

z
1

+

z
2

 和

α
≔

v
1

+

v
2

：
```

<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <title>带有水平拉伸操作符的页面</title>
    <link
 rel="stylesheet"
 href="https://fred-wang.github.io/MathFonts/LatinModern/mathfonts.css" />
  </head>
  <body>
    <math display="block">
      <mroot>
        <mrow>
          <munder>
            <mrow>
              <msub>
                <mi>z</mi>
                <mn>1</mn>
              </msub>
              <mo>+</mo>
              <msub>
                <mi>z</mi>
                <mn>2</mn>
              </msub>
            </mrow>
            <mo>⎵</mo>
          </munder>
        </mrow>
        <mn>3</mn>
      </mroot>
      <mo>+</mo>
      <mfrac>
        <mrow
 ><mo>|</mo>
          <mover>
            <mrow>
              <msub>
                <mi>v</mi>
                <mn>1</mn>
              </msub>
              <mo>+</mo>
              <msub>
                <mi>v</mi>
                <mn>2</mn>
              </msub>
            </mrow>
            <mo>→</mo>
          </mover>
          <mo>|</mo>
        </mrow>
        <msup>
          <mi>s</mi>
          <mn>3</mn>
        </msup>
      </mfrac>
      <mo>−</mo>
      <mrow>
        <munderover>
          <mo>∑</mo>
          <mrow>
            <mi>i</mi>
            <mo>=</mo>
            <mn>1</mn>
          </mrow>
          <mi>n</mi>
        </munderover>
        <msqrt>
          <mrow>
            <msub>
              <mi>a</mi>
              <mi>i</mi>
            </msub>
            <mo>−</mo>
            <msubsup>
              <mi>K</mi>
              <mn>0</mn>
              <mi>i</mi>
            </msubsup>
          </mrow>
        </msqrt>
      </mrow>
    </math>
  </body>
</html>
```

```
.highlight {
  color: red;
}
math {
  font-size: 200%;
}
```

可以看出底部括号“⎵”和右箭头“→”在水平方向上会延伸以覆盖被替代值的宽度。回想一下，一些竖直运算符可以伸展，以覆盖 `<mrow>` 中非伸展兄弟节点的高度。类似地，一些水平运算符可以伸展以覆盖 `<munder>`、`<mover>` 或 `<munderover>` 元素中非伸展兄弟节点的宽度。

**备注：** 伸展适用于 `<munder>`、`<mover>` 或 `<munderover>` 元素的任何子元素，不仅仅是正下标或正上标。

### 大型运算符和极限

到目前为止，我们的示例实际上是使用 `display="block"` 属性呈现的。让我们看一下同一个示例，在没有该属性的情况下渲染的效果：
```

<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <title>极限符号被挪动并且大型运算符变小的页面</title>
    <link
 rel="stylesheet"
 href="https://fred-wang.github.io/MathFonts/LatinModern/mathfonts.css" />
  </head>
  <body>
    <math>
      <mroot>
        <mrow>
          <munder>
            <mrow>
              <msub>
                <mi>z</mi>
                <mn>1</mn>
              </msub>
              <mo>+</mo>
              <msub>
                <mi>z</mi>
                <mn>2</mn>
              </msub>
            </mrow>
            <mo>⎵</mo>
          </munder>
        </mrow>
        <mn>3</mn>
      </mroot>
      <mo>+</mo>
      <mfrac>
        <mrow
 ><mo>|</mo>
          <mover>
            <mrow>
              <msub>
                <mi>v</mi>
                <mn>1</mn>
              </msub>
              <mo>+</mo>
              <msub>
                <mi>v</mi>
                <mn>2</mn>
              </msub>
            </mrow>
            <mo>→</mo>
          </mover>
          <mo>|</mo>
        </mrow>
        <msup>
          <mi>s</mi>
          <mn>3</mn>
        </msup>
      </mfrac>
      <mo>−</mo>
      <mrow>
        <munderover>
          <mo>∑</mo>
          <mrow>
            <mi>i</mi>
            <mo>=</mo>
            <mn>1</mn>
          </mrow>
          <mi>n</mi>
        </munderover>
        <msqrt>
          <mrow>
            <msub>
              <mi>a</mi>
              <mi>i</mi>
            </msub>
            <mo>−</mo>
            <msubsup>
              <mi>K</mi>
              <mn>0</mn>
              <mi>i</mi>
            </msubsup>
          </mrow>
        </msqrt>
      </mrow>
    </math>
  </body>
</html>
```

```
.highlight {
  color: red;
}
math {
  font-size: 200%;
}
```

不出所料，公式不再居中，渲染方式发生变动，导致高度最小化。注意求和符号，可以看到求和符号变小了，并且 `<munderover>` 元素的附加符号现在以下标和上标的方式附加在其上！这是由于“∑”运算符的两个属性：

* *largeop*：如果 `<math>` 标签具有 `display="block"` 属性，则使用更大的字形绘制运算符。
* *movablelimits*：如果 `<math>` 标签没有 `display="block"` 属性，则将附加在运算符上的正下标和正上标分别渲染为下标和上标。

**备注：** *largeop* 属性实际上与附加符号无关，尽管具有此属性的运算符通常会有附加符号。*movablelimits* 属性也适用于 `<munder>` 和 `<mover>` 元素。

总结
--

在本文中，我们完成了对引入下标、上标、正下标和正上标的基本布局元素 `<msub>`、`<msup>`、`<msubsup>`、`<munder>`、`<mover>` 以及 `<munderover>` 的介绍。在介绍这些元素的过程中，我们顺便介绍了 `<mo>` 元素的新属性。在下一篇文章中，我们将继续关注表格布局。

* 上一页
* Overview: First steps
* 下一页
参见
--

* 元素
* 元素
* 元素
* 元素 (en-US)
* 元素 (en-US)
* 元素 (en-US)


MathML 表格 - 学习 Web 开发

MathML 表格
=========

* 上一页
* Overview: First steps
* 下一页

掌握了所有基本的数学符号后，接下来要考虑的是用于矩阵式表达和其他高级数学布局的表格布局。

|  |  |
| --- | --- |
| 前提： | 基本的计算机操作能力，安装基本软件，基本的文件操作知识，以及 HTML 基础知识（学习 HTML 简介和 HTML 表格） |
| 目标： | 了解 MathML 表格元素，并了解一些使用案例。 |

MathML 表格元素
-----------

MathML 表格元素与 HTML 表格的元素类似：`<mtable>` 元素表示数学表格，它的子元素是 `<mtr>` 元素（表示行），每个 `<mtr>` 元素都有 `<mtd>` 元素作为其子元素（表示单元格）。`<mtable>` 元素可以在 MathML 公式的任何位置插入。`<mtd>` 元素可以包含任意数量的 MathML 子元素，并将它们布局为 `<mrow>` 容器。

表格通常用于矩阵式表达式（包括向量）。以下是一个基本示例，该示例取自有关 CSS `matrix()` 函数的文章：
```

<link
 rel="stylesheet"
 href="https://fred-wang.github.io/MathFonts/LatinModern/mathfonts.css" />
```

```html
<math display="block">
  <mrow>
    <mo>(</mo>
    <mtable>
      <mtr>
        <mtd>
          <mi>a</mi>
        </mtd>
        <mtd>
          <mi>c</mi>
        </mtd>
        <mtd>
          <mn>0</mn>
        </mtd>
        <mtd>
          <msub>
            <mi>t</mi>
            <mi>x</mi>
          </msub>
        </mtd>
      </mtr>
      <mtr>
        <mtd>
          <mi>b</mi>
        </mtd>
        <mtd>
          <mi>d</mi>
        </mtd>
        <mtd>
          <mn>0</mn>
        </mtd>
        <mtd>
          <msub>
            <mi>t</mi>
            <mi>y</mi>
          </msub>
        </mtd>
      </mtr>
      <mtr>
        <mtd>
          <mn>0</mn>
        </mtd>
        <mtd>
          <mn>0</mn>
        </mtd>
        <mtd>
          <mn>1</mn>
        </mtd>
        <mtd>
          <mn>0</mn>
        </mtd>
      </mtr>
      <mtr>
        <mtd>
          <mn>0</mn>
        </mtd>
        <mtd>
          <mn>0</mn>
        </mtd>
        <mtd>
          <mn>0</mn>
        </mtd>
        <mtd>
          <mn>1</mn>
        </mtd>
      </mtr>
    </mtable>
    <mo>)</mo>
  </mrow>
</math>
```

允许单元格跨多行多列
----------

与 HTML 表格类似，`<mtd>` 元素具有 `columnspan` 和 `rowspan` 属性，以指示单元格跨多行和多列。下面的内部矩阵跨越外部矩阵的两列：
```

<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <title>横跨多列的矩阵</title>
    <link
 rel="stylesheet"
 href="https://fred-wang.github.io/MathFonts/LatinModern/mathfonts.css" />
  </head>
  <body>
    <math display="block">
      <mrow>
        <mo>(</mo>
        <mtable>
          <mtr>
            <mtd columnspan="2">
              <mrow>
                <mo>(</mo>
                <mtable>
                  <mtr>
                    <mtd>
                      <mi>a</mi>
                    </mtd>
                    <mtd>
                      <mi>c</mi>
                    </mtd>
                  </mtr>
                  <mtr>
                    <mtd>
                      <mi>b</mi>
                    </mtd>
                    <mtd>
                      <mi>d</mi>
                    </mtd>
                  </mtr>
                </mtable>
                <mo>)</mo>
              </mrow>
            </mtd>
            <mtd>
              <mn>0</mn>
            </mtd>
            <mtd>
              <mi>T</mi>
            </mtd>
          </mtr>
          <mtr>
            <mtd>
              <mn>0</mn>
            </mtd>
            <mtd>
              <mn>0</mn>
            </mtd>
            <mtd>
              <mn>1</mn>
            </mtd>
            <mtd>
              <mn>0</mn>
            </mtd>
          </mtr>
          <mtr>
            <mtd>
              <mn>0</mn>
            </mtd>
            <mtd>
              <mn>0</mn>
            </mtd>
            <mtd>
              <mn>0</mn>
            </mtd>
            <mtd>
              <mn>1</mn>
            </mtd>
          </mtr>
        </mtable>
        <mo>)</mo>
      </mrow>
    </math>
  </body>
</html>
```

**备注：** 出于历史原因，MathML 中用于跨列的属性被称为 `columnspan` 而不是 `colspan`。

高级布局用法
------

除了表示类似矩阵的对象外，MathML 表格有时也用于数学公式中的高级布局，例如在维基百科对勒让德符号的定义中，公式被分隔在三行中，而值和条件则分隔于两列。

```html
<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <title>第一个矩阵</title>
    <link
 rel="stylesheet"
 href="https://fred-wang.github.io/MathFonts/LatinModern/mathfonts.css" />
  </head>
  <body>
    <math display="block">
      <mrow>
        <mrow>
          <mo>(</mo>
          <mfrac>
            <mi>a</mi>
            <mi>b</mi>
          </mfrac>
          <mo>)</mo>
        </mrow>
        <mo>=</mo>
        <mrow>
          <mo>{</mo>
          <mtable>
            <mtr>
              <mtd>
                <mn>1</mn>
              </mtd>
              <mtd>
                <mtext>如果 </mtext>
                <mi>a</mi>
                <mtext> 是模 </mtext>
                <mi>p</mi>
                <mtext> 的二次剩余且不是 </mtext>
                <mi>p</mi>
                <mtext> 的倍数 </mtext>
                <mo>；</mo>
              </mtd>
            </mtr>
            <mtr>
              <mtd>
                <mo>−</mo>
                <mn>1</mn>
              </mtd>
              <mtd>
                <mtext>如果 </mtext>
                <mi>a</mi>
                <mtext> 是模 </mtext>
                <mi>p</mi>
                <mtext> 的非二次剩余 </mtext>
                <mo>；</mo>
              </mtd>
            </mtr>
            <mtr>
              <mtd>
                <mn>0</mn>
              </mtd>
              <mtd>
                <mtext>如果 </mtext>
                <mi>a</mi>
                <mtext> 是 </mtext>
                <mi>p</mi>
                <mtext> 的倍数 </mtext>
                <mo>。</mo>
              </mtd>
            </mtr>
          </mtable>
        </mrow>
      </mrow>
    </math>
  </body>
</html>
```

**警告：** `<mtable>` (en-US) 文档中记录了更多高级布局选项，如对齐或间距等特殊属性。这些属性起源于 CSS 一类事物之前，最初设计用于不支持 CSS 的渲染器。然而，并非所有浏览器都实现了这些属性。将来，可能会用基于 CSS 的替代方案取代仅用于布局目的（即非实际矩阵对象）的 `<mtable>` 的用法。

总结
--

在本文中，我们回顾了 `<mtable>`、`<mtr>` 和 `<mtd>` 元素，它们是用于表格的 HTML 元素的等效元素。我们学习了如何使用它们表示类似矩阵的对象，以及如何用它们实现高级布局。

你已经接近完成本单元了——我们只剩下最后一件事要做。在三个著名数学公式测验中，你需要运用新学到的知识，使用 HTML 和 MathML 重写一个小型数学文章。

* 上一页
* Overview: First steps
* 下一页
参见
--

* 学习 HTML 表格
* `<mtable>` 元素 (en-US)
* `<mtr>` 元素 (en-US)
* `<mtd>` 元素 (en-US)


多媒体：图片 - 学习 Web 开发

多媒体：图片
======

* 上一页
* Overview: Performance
* 下一页

媒体，即图片和视频，占据了平均网站下载字节的 70% 以上。从下载性能的角度来看，消除媒体和减少文件大小是最容易解决的问题。本文将介绍优化图像和视频以改善网页性能的方法。

|  |  |
| --- | --- |
| 前提： | 基本的计算机操作能力，已安装基本软件，以及基本的客户端 Web 技术知识。 |
| 目标： | 了解各种图像格式、它们对性能的影响，以及如何减少图像对页面整体加载时间的影响。 |

**备注：** 这是对 Web 上多媒体传输进行优化的概要介绍，只涵盖了一般原理和技术。如需更深入的指南，请参阅 https://images.guide。

为何优化多媒体？
--------

对于一般网站而言，其带宽的 51% 来自图像，其次是视频占 25%，因此可以说处理和优化多媒体内容非常重要。

你需要考虑数据使用情况。许多人使用的是有限流量套餐，甚至是按使用量付费，每兆字节都要付费。这不仅仅是新兴市场的问题。截至 2018 年，英国仍有 24% 的用户使用按使用量付费。

你还需要考虑内存问题，因为许多移动设备的 RAM 有限。请记住，当图像下载后，它们需要存储在内存中。

优化图像传递
------

尽管图像下载是带宽消耗最大的部分，但其对感知性能的影响远低于许多人的预期（主要是因为页面文本内容可以立即下载，用户可以在图像到达时看到它们被渲染）。然而，为了提供良好的用户体验，访问者仍然应尽快看到图像。

### 加载策略

对于大多数网站来说，最大的改进之一是延迟加载可视区域以下的图像，而不是在初始页面加载时，无论访客是否滚动查看，都下载所有这些内容。许多 JavaScript 库可以为你实现此功能，例如 lazysizes，浏览器供应商目前正在开发一种原生的 `lazyload` 属性，目前处于实验阶段。

除了加载一部分图像之外，接下来你应该考虑图像本身的格式：

* 你是否加载了最优的文件格式？
* 你是否对图像进行了良好的压缩？
* 你是否加载了正确的尺寸？

#### 最优格式

最优的文件格式通常取决于图像的特点。

**备注：** 有关图像类型的一般信息，请参阅图像文件类型和格式指南。

SVG 格式更适合颜色较少且不太逼真的照片的图像。这需要源文件以矢量图形格式提供。如果这样的图像仅以位图形式存在，则应选择 PNG 作为后备格式。这种类型的图案示例是徽标、插图、图表或图标（注意：SVG 比图标字体好得多！）。两种格式都支持透明度。

PNG 可以以三种不同的输出组合进行保存：

* 24 位色 + 8 位透明度——提供全色准确性和平滑透明度，以牺牲大小为代价。你可能希望为了避免这种组合，而选择 WebP（见下文）。
* 8 位色 + 8 位透明度——提供不超过 255 种颜色，但保持平滑的透明度。大小不太大。这些可能是你想要的 PNG。
* 8 位色 + 1 位透明度——提供不超过 255 种颜色，并仅为每个像素提供无或完全透明度，这使透明边界看起来生硬并且有锯齿。尺寸小，但视觉保真度付出了代价。

优化 SVG 的一个不错的在线工具是 SVGOMG。对于 PNG，有 ImageOptim 在线或 Squoosh 这几种工具。

对于不具有透明度的摄影素材，有更广泛的格式可供选择。如果要谨慎行事，那么你应该选择压缩良好的**渐进式 JPEG**。渐进式 JPEG 与普通 JPEG 不同，它们是渐进式显示的（因此得名），这意味着用户先看到的是一个低分辨率的图像版本，随着图像下载而逐渐清晰，而不是图像以全分辨率从上到下加载，或者只有在完全下载后才显示。对于这些图片，一个好的压缩工具是 MozJPEG，可用于在线图像优化工具 Squoosh 中，75% 的质量设置就能够得到优秀的结果。

其他格式在压缩方面改进了 JPEG，但并非所有浏览器都支持：

* WebP——既适用于图像又适用于动图的绝佳选择。WebP 提供比 PNG 或 JPEG 更好的压缩，支持更高的色深、动画帧和透明度等（但不支持渐进式显示）。除具有 Big Sur 或更早版本的 macOS 桌面版 Safari 14 外，所有主流浏览器都支持它。

**备注：** 尽管 Apple 宣布在 Safari 14 中支持 WebP，但在 Safari 版本 16.0 之前，`.webp` 图像无法在早于 Big Sur 版本的 macOS 桌面版本上正常显示，而 iOS 14 上的 Safari *却能*正常显示 `.webp` 图像。
* AVIF——由于其是高性能和免版税的图像格式（甚至比 WebP 更高效），它是用于图像和动图的不错选择（尽管不太广泛）。它现在受 Chrome、Opera 和 Firefox 的支持。另请参阅将以前的图像格式转换为 AVIF 的在线工具。
* **JPEG2000**——曾经是 JPEG 的继任者，但仅受 Safari 支持。也不支持渐进式显示。

鉴于 JPEG-XR 和 JPEG2000 的支持范围很窄，并且还要考虑解码成本，JPEG 的唯一真正竞争对手是 WebP。这就是为什么你也可以以该格式提供图像。这可以通过使用配备了 type 属性的 `<picture>` 元素和 `<source>` 元素来完成。

如果所有这些对你的团队来说听起来有点复杂或感觉有太多的工作要做，那么还有一些在线服务，你可以将其用作图像 CDN，它们将根据请求图像的设备或浏览器类型自动提供正确的图像格式。这其中最大的就是 Cloudinary 和 Image Engine。

最后，如果你想在页面中包含动图，那么请知道 Safari 允许在 `<img>` 和 `<picture>` 元素中使用视频文件。这样，你还可以为所有其他现代浏览器添加**动态 WebP**。

```html
<picture>
  <source type="video/mp4" src="giphy.mp4" />
  <source type="image/webp" src="giphy.webp" />
  <img src="giphy.gif" alt="动画 GIF" />
</picture>
```

#### 提供最佳尺寸

在图像传输中，采用“一刀切”的方法不会得到最佳结果，这意味着对于较小的屏幕，你希望提供更低分辨率的图像，而对于较大的屏幕则相反。此外，你还希望为那些具有高 DPI 屏幕（例如“Retina”）的设备提供更高分辨率的图像。因此，除了创建许多中间图像变体之外，你还需要一种方法将正确的文件提供给正确的浏览器。这就是你需要通过使用 media 和/或 sizes 属性来升级你的 `<picture>` 和 `<source>` 元素的地方。有关如何组合所有这些属性的详细文章可以在这里找到。

关于高 DPI 屏幕，有两个有趣的效果需要记住：

* 在高 DPI 屏幕上，人眼对压缩伪影的敏感度要低得多，这意味着对于这些屏幕上的图像，你可以超过通常的压缩程度。
* 只有很少的人能够察觉到超过 2 倍 DPI 的分辨率增加，这意味着你不需要提供超过 2 倍分辨率的图像。

#### 控制下载图像的优先级（和顺序）

将最重要的图像优先展示给访问者会提供更好的感知性能。

首先，检查你的内容图像是否使用 `<img>` 或 `<picture>` 元素，并且你的背景图像是否在 CSS 中使用 `background-image` 定义——`<img>` 元素中引用的图像比背景图像具有更高的加载优先级。

其次，通过采用优先级提示（Priority Hints），你可以进一步控制优先级，只需在图像标签中添加 `fetchPriority` 属性即可。图像优先级提示的一个示例用例是幻灯片轮播，其中第一张图像的优先级高于后续的图像。

### 渲染策略：在加载图像时避免卡顿

由于图像是异步加载的，并且在第一次绘制后继续加载，如果在加载之前未定义其尺寸，它们可能会导致页面内容重新布局。例如，当图像加载时，文本会被推到页面下方。因此，你需要设置 `width` 和 `height` 属性，以便浏览器可以在布局中为它们预留空间。

当在 HTML `<img>` 元素上包含图像的 `width` 和 `height` 属性时，浏览器可以在图像加载之前计算图像的宽高比。在图像下载和绘制到屏幕时，这个宽高比用于预留显示图像所需的空间，从而减少或甚至防止布局变化。减少布局变化是良好用户体验和 web 性能的重要组成部分。

浏览器在解析 HTML 时开始渲染内容，通常在下载所有资源（包括图像）之前。包括尺寸使浏览器可以为每个图像预留一个正确尺寸的占位框，以便在首次渲染页面时显示图像。

![两个截图，第一个没有图像但预留了空间，第二个显示了加载到预留空间的图像。](/zh-CN/docs/Learn/Performance/Multimedia/ar-guide.jpg)

没有 `width` 和 `height` 属性，将不会创建占位空间，导致页面在渲染后加载图片时出现明显的卡顿或布局移动。页面的重新布局和重绘是性能和可用性问题。

在响应式设计中，当容器比图片更窄时，通常使用以下 CSS 代码来防止图片溢出容器：

```css
img {
  max-width: 100%;
  height: auto;
}
```

虽然在响应式布局中很有用，但如果不包括宽度和高度信息，就会导致卡顿。如果在解析 `<img>` 元素时没有提供高度信息，则在图片加载之前，此 CSS 实际上将高度设置为 0。当页面在初始显示到屏幕后加载图片时，页面会重新布局和重绘，为新确定的高度创建空间，从而导致布局移动。

浏览器在实际加载图像之前有一种调整图像大小的机制。当 `<img>`、`<video>` 或 `<input type="button">` 元素设置了 `width` 和 `height` 属性时，它的宽高比会在加载前计算出来，并使用提供的尺寸提供给浏览器。

接着，根据宽高比计算出图片的高度，并将正确的尺寸赋给 `<img>` 元素。这样做的好处是，当图片加载时，就不会出现之前提到的卡顿，或者即使列出的尺寸不太精确，卡顿程度也会降到最低。

宽高比仅在图片加载时用于保留空间。一旦图片加载完成，将使用加载的图片的实际宽高比，而不是属性中的宽高比。这确保即使属性尺寸不准确，图片也会以正确的宽高比显示。

虽然上个十年的开发者最佳实践可能建议省略 HTML `<img>` 上的 `width` 和 `height` 属性，但由于宽高比映射，包含这两个属性被认为是开发者的最佳实践。

对于任何背景图片，很重要的是设置 `background-color` 值，以便在图片加载之前，任何叠加在上面的内容仍然可读。

结论
--

本节我们学习了图像优化的方法。你已经大概掌握了如何优化占一般网站带宽总量一半的图像。但这只是影响用户带宽和页面加载速度的媒体类型之一。接下来，我们要看看视频优化，它可以解决带宽消耗的另外 20%。

* 上一页
* Overview: Performance
* 下一页


测量性能 - 学习 Web 开发

测量性能
====

* 上一页
* Overview: Performance
* 下一页

测量性能提供了一个重要的指标，以帮助你评估应用程序、网站或 web 服务的成功程度。

例如，你可以使用性能指标来确定你的应用程序与竞争对手相比如何表现，或者你可以比较应用程序在各个版本之间的性能。你选择的度量指标应与你的用户、网站和业务目标相关。它们应以一致的方式收集和测量，并以非技术利益相关者可以使用和理解的格式进行分析。

本文介绍了可以用来获取网站性能指标的工具，这些工具可以用来衡量和优化你的网站性能。

|  |  |
| --- | --- |
| 前提： | 基本计算机素养，已安装基本软件，以及客户端网络技术的基本知识。 |
| 目标： | 提供关于可以通过不同的 web 性能 API 和工具来收集的性能指标的信息，并介绍可以用来展示这些数据的工具。 |

性能工具
----

有几种不同的工具可用于帮助你测量和改进性能。这些工具通常可以分为两类：

* 表明或测量性能的工具，比如 PageSpeed Insights 或 Firefox 的网络监视器和性能监视器。这些工具展示了你的网站加载的快慢。它们也指出了你的 Web 应用程序中可以优化的区域。
* 你可以用来构建自定义性能工具的性能 API。
通用性能报告工具
--------

像 PageSpeed Insights 这样的工具可以衡量网站的性能。你可以输入一个 URL，并在几秒钟内获得一份性能报告。报告包含有关你的网站在移动设备和桌面设备上的表现的分数。这是了解你已经做得很好的地方以及可以改进的地方的良好开始。

撰写本文时，MDN 的性能报告摘要看起来类似于以下内容：

![Mozilla 主页的 PageSpeed Insights 报告截图。](/zh-CN/docs/Learn/Performance/Measuring_performance/pagespeed-insight-mozilla-homepage.png)

性能报告包含有关用户在页面上显示任何内容之前需要等待多长时间、显示页面需要下载多少字节等信息。它还会让你知道测量的值是否被认为是好的或坏的。

webpagetest.org 是另一个可以自动测试你的网站并返回有用指标的工具示例。

尝试现在使用这些工具运行你最喜欢的网站，查看分数如何。

网络监视工具
------

大多数浏览器都有可用的工具，你可以使用这些工具来针对已加载的页面进行运行，以确定它们的性能如何，大多数工具的工作方式都很类似。例如，Firefox 的网络监视器返回有关从网络下载的所有素材的详细信息，并以瀑布图的形式显示每个素材的下载时间。

![Firefox 网络监视器显示已加载的素材列表以及每个素材的加载时间](/zh-CN/docs/Learn/Performance/Measuring_performance/network-monitor.png)

你还可以查看 Chrome 的网络监视器文档

性能监视工具
------

你也可以使用浏览器性能监测工具，比如 Firefox 性能监测器来测量在执行不同操作时一个 web 应用或网站的用户界面性能。这可以指出可能拖慢你的 web 应用或网站速度的特性。

![开发者工具性能面板显示录制 #1. 的瀑布图](/zh-CN/docs/Learn/Performance/Measuring_performance/perf-monitor.png)

另见 Chrome 性能工具文档。

性能 API
------

编写 web 代码时，有大量的 web API 可用，使你可以创建自己的性能测量工具。

你可以使用导航计时 API 来测量客户端 web 性能，包括卸载上一个页面所需的时间、域名查询所需的时间、执行窗口加载处理程序所花费的总时间等。你可以将该 API 用于与下图中显示的所有导航事件相关的度量。

![导航计时 API 可处理的各种处理器（包括 Navigation Timing API 度量）：unload 提示、redirect、unload、应用缓存、DNS、TCP、请求、相应、处理、onLoad，以及 navigationStart、redirectStart、redirectEnd、fetchStart、domainLookupEnd、domainLookupStart、connectStart（secureConnectionStart）、connectEnd、requestStart、responseStart、responseEnd、unloadStart、unloadEnd、domLoading、domInteractive、domContentLoaded、domComplete、loadEventStart、loadEventEnd](/zh-CN/docs/Learn/Performance/Measuring_performance/navigationtimingapi.jpg)

性能 API 为当前页面提供了访问性能相关信息的能力，包括性能时间轴 API、导航计时 API、用户计时 API 和资源计时 API。这些接口允许准确测量 JavaScript 任务完成所需的时间。

PerformanceEntry 对象是*性能时间轴*的一部分。*性能条目*可以通过在应用程序的显式点创建性能 *`mark` (en-US)* 或 *`measure` (en-US)*（例如，通过调用 `mark()` 方法直接创建）。性能条目还可以通过间接方式创建，例如加载图像等资源。

PerformanceObserver API 可用于观察性能测量事件，并在浏览器的性能时间轴中记录新的性能条目时通知你。

虽然本文并没有深入介绍这些 API 的使用，但了解它们的存在是有用的。你还可以参考导航和计时 (en-US)文章，以获取有关使用性能 web API 的更多示例。

结语
--

本文简要介绍了 web 性能指标，以帮助你了解可以在 web 应用程序或网站上测量的内容。接下来，你将了解如何优化网站上的图片以优化其性能。

* 上一页
* Overview: Performance
* 下一页


Web 性能的重要性 - 学习 Web 开发

Web 性能的重要性
==========

* Overview: Performance
* 下一页

Web 性能关乎网站的快速加载，以及让缓慢过程*看起来*更快。本文将介绍 web 性能对于网站访问者和你的业务目标的重要性。

|  |  |
| --- | --- |
| 预备条件： |
 基本计算机素养、基本软件已安装，以及
 客户端网络技术的基本知识。
  |
| 目标： | 了解 web 性能对良好用户体验的重要性。 |

Web 性能是指网站内容在网页浏览器中的**加载**和**渲染**速度，以及对用户交互的响应。性能不佳的网站显示和响应输入速度缓慢。性能不佳的网站会增加用户放弃访问的概率。在最糟糕的情况下，性能不佳会导致内容完全无法访问。一个很好的 web 性能目标是让用户注意不到性能。虽然个人对网站性能的感知是主观的，但加载和渲染可以进行测量。良好的性能可能对大多数网站访问者来说并不明显，但大多数人会立即意识到一个网站的响应是否迟缓。这就是为什么我们关心 web 性能。

为什么关心性能？
--------

Web 性能及其相关的最佳实践对于网站访问者拥有良好的体验至关重要。从某种意义上说，web 性能可以被认为是 web 无障碍的一个子集。在性能和无障碍方面，你需要考虑网站访问者使用的设备以及设备的连接速度。

以 CNN.com 的加载体验为例，截至撰写本文时，该网站有超过 400 个 HTTP 请求，文件大小超过 22.6MB。

* 想象一下在连接光纤网络的台式电脑上加载这个网站。这将相对较快，文件大小基本无关紧要。
* 想象一下在通勤回家的公共交通工具上，通过用了 9 年的 iPad 使用移动数据加载相同的网站。根据蜂窝网络覆盖情况，该网站将加载缓慢，甚至可能无法使用。在加载完成之前，你可能已经放弃了。
* 想象一下在印度农村使用覆盖有限或无覆盖的 35 美元华为设备加载同样的网站。加载速度会非常慢，还可能因为阻塞脚本而超时。即使加载能成功，不良的 CPU 也可能导致浏览器崩溃。

在 3G 网络上，一个 22.6 MB 的网站可能需要长达 83 秒才能加载，`DOMContentLoaded`（即网站的基本 HTML 结构）为 31.86 秒。

不仅仅下载所需时间是主要问题。很多国家的互联网连接仍然按兆字节计费。以我们的例子，22.6 MB 的 CNN.com 体验可能需要印度平均工资的 11% 才能下载。在西北非洲的移动设备上，可能需要两天的平均工资。如果这个网站在美国运营商的国际漫游计划上加载呢？费用会让任何人都流泪。（参见下载你的网站需要多少费用。）

### 提高转化率

减少网站的下载和渲染时间可以提高转化率和用户留存。

**转化率**是指网站访问者执行某种测量或期望操作的比率。例如，这可能是购买商品、阅读文章或订阅时事通讯。被视为转化率的操作取决于网站的业务目标。

性能影响转化；提高 web 性能就能提高转化。网站访问者期望一个网站在两秒内或更短的时间内加载；有时在移动设备上甚至更短（通常需要更长时间）。从 3 秒开始，这些网站访问者就会开始放弃缓慢的网站。

网站加载速度是一个因素。如果网站对用户交互的反应慢，或者看起来不流畅，这会导致访问者失去兴趣和信任。

以下是一些现实世界中的性能改进示例：

* Tokopedia 将 3G 连接的渲染时间从 14 秒减少到 2 秒，访问者增加了 19%，总会话数增加了 35%，新用户增加了 7%，活跃用户增加了 17% ，每位用户的会话数增加了 16%。
* Pinterest 页面重建以提高性能，导致等待时间减少了 40%，SEO 流量增加了 15%，注册转化率增加了 15%。

要构建人们愿意使用的网站和应用程序；要吸引和留住网站访问者，你需要创建一个易于访问且提供良好用户体验的网站。构建网站需要 HTML、CSS 和 JavaScript，通常还包括诸如图像和视频等二进制文件类型。你在构建网站时所作的决策和选择的工具在很大程度上会影响完成后的性能。

良好的性能是一种资产，糟糕的性能是一种负担。网站速度直接影响跳出率、转化、收入、用户满意度和搜索引擎排名。已经证明，性能优良的网站能够提高访问者留存和用户满意度。加载缓慢的内容已经被证明会导致网站被放弃，有些访问者离开后可能永远不会回来。减少客户端和服务器之间传输的数据量可降低各方的成本。减小 HTML/CSS/JavaScript 和媒体文件大小可以降低网站的加载时间和功耗（请参阅性能预算 (en-US)）。

跟踪性能非常重要。多种因素，包括网络速度和设备性能，都会影响性能。没有单一的性能指标；不同的业务目标和用户需求需要不同的性能度量。要提高网站性能，你需要了解用户体验、加载和渲染性能，以及如何将性能度量与业务指标结合起来。通过对性能的了解，你可以为你的网站和应用程序提供更快、更顺畅的体验。

结论
--

Web 性能对于可访问性以及服务于组织或企业目标的其他网站指标非常重要。网站性能的好坏与用户体验以及大多数网站的整体效果密切相关。这就是为什么你应该关心 web 性能。

* Overview: Performance
* 下一页


HTML 性能优化 - 学习 Web 开发

HTML 性能优化
=========

* 上一页  (en-US)
* Overview: Performance
* 下一页

HTML 默认情况下快速且易于访问。作为开发者，我们的工作是在创建或编辑 HTML 代码时确保保持这两个特性。例如当嵌入的 `<video>` 文件大小过大，或者 JavaScript 解析阻塞了关键页面元素的渲染时，情况可能会比较复杂。本文将引导你了解关键的 HTML 性能特性，以大幅提高网页质量。

|  |  |
| --- | --- |
| 前提： |
 基本的计算机知识，
 已安装基本软件，以及基本的客户端 Web 技术知识。
  |
| 目标： | 学习了解 HTML 对网站性能的影响，以及如何优化 HTML 以提高性能。 |

优化还是不优化
-------

在开始优化 HTML 之前，你应该首先回答的问题是“我需要优化什么？”。下面讨论的一些技巧和技术是适用于几乎任何 Web 项目的良好实践，而其他一些技巧只在特定情况下才需要使用。试图在任何地方应用这些技术可能是不必要的，而且可能是浪费时间的。你应该确定在每个项目中实际需要哪些性能优化。

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

HTML 性能关键问题
-----------

就性能而言，HTML 非常简单，它主要是文本，文本大小较小，因此下载和渲染速度通常很快。影响网页性能的关键问题包括：

* 图像和视频文件的大小：需要考虑如何处理替换元素（例如 `<img>` 和 `<video>`）。图像和视频文件较大，可能会显著增加页面的大小。因此，需要尽量减少下载到用户设备的字节数（例如，为移动设备提供较小的图像）。还需要考虑仅在需要时加载页面上的图像和视频，以改善感知性能。
* 嵌入内容的交付：通常是嵌入在 `<iframe>` 元素中的内容。将内容加载到 `<iframe>` 中可能会显著影响性能，因此应该仔细考虑。
* 资源加载顺序：为了最大化感知性能和实际性能，HTML 应该首先按照在页面上出现的顺序加载。然后，你可以利用各种特性来影响资源加载顺序以获得更好的性能。例如，你可以提前预加载关键的 CSS 和字体，但将非关键的 JavaScript 推迟到稍后加载。

**备注：** 简化 HTML 结构和对源代码进行缩小化可以加快渲染和下载速度是有道理的。然而，与图像和视频相比，HTML 文件大小微不足道，而且现代浏览器的渲染速度非常快。如果你的 HTML 源代码非常庞大且复杂，导致渲染和下载性能受到影响，那么你可能存在更大的问题，并且应该考虑简化和拆分内容。

响应式处理替代元素
---------

响应式设计彻底改变了在不同设备上处理网页内容布局的方式。它的一个关键优势是可以动态切换根据不同的屏幕尺寸优化后的布局，例如宽屏布局与窄屏（移动设备）布局之间的切换。它还可以根据其他设备属性，如分辨率或亮色或暗色配色方案的偏好，来处理内容的动态切换。

所谓的“移动优先”技术可以确保默认布局适用于小屏幕设备，因此移动设备只需下载适合其屏幕的图像，无需下载更大的桌面图像，以此提高性能。然而，由于这是通过 CSS 中的媒体查询来控制的，因此它只能对在 CSS 中加载的图像的性能产生积极影响。

在下面的小节中，我们将总结如何实现响应式的替代元素。你可以在视频和音频内容和响应式图像指南中找到更多关于这些实现的详细信息。

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

要根据设备的分辨率和视口大小提供相同图像的不同分辨率版本，你可以利用 `srcset` 和 `sizes` 这两个属性。

以下示例为不同屏幕宽度提供了不同尺寸的图像：

```html
<img
 srcset="480w.jpg 480w, 800w.jpg 800w"
 sizes="(max-width: 600px) 480px,
 800px"
 src="800w.jpg"
 alt="家庭照" />
```

`srcset` 提供源图像的固有尺寸以及它们的文件名，而 `sizes` 在每种情况下提供媒体查询和需要填充的图像槽宽度。然后，浏览器根据每个槽位决定加载哪些图像。例如，如果屏幕宽度小于等于 `600px`，那么 `max-width: 600px` 为真，因此需要填充的槽位是 `480px`。在这种情况下，浏览器很可能选择加载 480w.jpg 文件（480px 宽的图像）。这有助于性能提升，因为浏览器不会加载比所需更大的图像。

以下示例为不同屏幕分辨率提供了不同分辨率的图像：

```html
<img
 srcset="320w.jpg, 480w.jpg 1.5x, 640w.jpg 2x"
 src="640w.jpg"
 alt="家庭照" />
```

`1.5x`、`2x` 等是相对分辨率指示器。如果图像的样式设置为 320px 宽（例如在 CSS 中使用 `width: 320px`），则如果设备分辨率较低（每个 CSS 像素对应一个设备像素），浏览器将加载 `320w.jpg`，如果设备分辨率较高（每个 CSS 像素对应两个或更多设备像素），则加载 `640w.jpg`。

在这两种情况下，`src` 属性在浏览器不支持 `src`/`srcset` 的情况下提供了默认图像。

### 为图像和视频提供不同来源

`<picture>` 元素基于传统的 `<img>` 元素，允许你为不同的情况提供多个不同的源。例如，如果布局是宽的，你可能希望有一个宽的图像，如果是窄的，你将希望有一个在该上下文中仍然有效的较窄的图像。

当然，在移动设备上，这也有助于通过提供较小的信息下载来提高性能。

下面是一个示例：

```html
<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="茂密森林景观" />
</picture>
```

`<source>` 元素在 `media` 属性中包含媒体查询。如果媒体查询返回 true，则加载其 `<source>` 元素的 `srcset` 属性中引用的图像。在上面的示例中，如果视口宽度为 `799px` 或更小，则加载 `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="(min-width: 800px)" />
  <source
 src="video/larger.webm"
 type="video/webm"
 media="(min-width: 800px)" />

  <!-- 在不支持 video 元素的浏览器中的备用 -->
  <a href="video/larger.mp4">下载视频</a>
</video>
```

然而，提供图像和视频的源之间存在一些关键差异：

* 在上面的示例中，我们使用的是 `src` 而不是 `srcset`；你无法通过 `srcset` 指定视频的不同分辨率。
* 相反，要在不同的 `<source>` 元素中指定了不同的分辨率。
* 请注意，我们还在不同的 `<source>` 元素中指定了不同的视频格式，每个格式都在 `type` 属性中通过其 MIME 类型进行标识。浏览器将加载它们支持（媒体查询测试返回 true）的首个格式。
### 图像的懒加载

提高性能的一个非常有用的技术是**懒加载**。这指的是在 HTML 渲染时不立即加载所有图像，而是仅在它们实际对用户可见（或即将可见）时加载它们。这意味着立即可见/可用的内容更快地准备就绪，而随后的内容只有在滚动到时才会渲染其图像，并且浏览器不会浪费带宽加载用户永远看不到的图像。

懒加载通常使用 JavaScript 处理，但现在浏览器有一个可用的 `loading` 属性，可以指示浏览器自动进行图像的懒加载：

```html
<img src="800w.jpg" alt="家庭合照" 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 脚本将其插入到页面中。更多信息，请参见从服务器获取数据和操作文档。

**备注：** 如果你掌控内容并且内容相对简单，你可以考虑使用在 `src` 属性中以 base-64 编码的内容填充 `<iframe>`，甚至可以将原始 HTML 插入到 `srcdoc` 属性中（有关更多信息，请参见 Iframe 性能第二部分：好消息）。

如果必须使用 `<iframe>`，请保持谨慎。

#### 懒加载 iframe

与 `<img>` 元素一样，你还可以使用 `loading` 属性指示浏览器对最初屏幕外的 `<iframe>` 内容进行懒加载，从而提高性能：

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

详情请参阅是时候延迟加载屏外 iframe 了！。

处理资源加载顺序
--------

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

1. 通常首先解析 HTML，按照页面上的顺序进行解析。
2. 解析任何找到的 CSS，以理解应该应用于页面的样式。在此期间，开始获取链接的资源，如图像和 Web 字体。
3. 解析任何找到的 JavaScript，对页面进行评估和执行。默认情况下，这会阻止解析遇到的 `<script>` 元素之后的 HTML。
4. 稍后，浏览器根据应用于它的 CSS 来确定每个 HTML 元素应该如何进行样式化。
5. 将经过样式化的结果绘制到屏幕上。

**备注：** 这只是一个非常简单的叙述，但可以让你了解发生的事情。

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

### 处理 JavaScript 加载

解析和执行 JavaScript 会阻塞后续 DOM 内容的解析。这就增加了页面渲染和用户要使用该内容所需等待的时间。一个小的脚本不会有太大影响，但考虑到现代 Web 应用程序通常非常依赖 JavaScript，这一点很重要。

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

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

```html
<p>我的段落</p>
```

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

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

pElem.addEventListener("click", () => {
  alert("你点击了段落");
});
```

我们可以通过在 `<script>` 元素中引用它来将此脚本应用于页面：

```html
<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` 属性来实现更好的性能：

```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"`
* 预加载关键资源以提高加载速度 web.dev（2020）

**备注：** 你还可以使用 `rel="preload"` 预加载 CSS 和 JavaScript 文件。

**备注：** 还有其他 `rel` 值，也旨在加速页面加载的各个方面：`dns-prefetch`、`preconnect`、`modulepreload`、`prefetch` 和 `prerender`。请访问链接页面，了解它们的作用。

* 上一页  (en-US)
* Overview: Performance
* 下一页
参见
--

* 从服务器获取数据
* 操作文档


多媒体：视频 - 学习 Web 开发

多媒体：视频
======

* 上一页
* Overview: Performance
* 下一页  (en-US)

正如我们在前一节中了解到的，媒体（即图片和视频）占了一般网站下载量的 70% 以上。我们已经学过了如何优化图片。本文将研究如何优化视频来提高 web 性能。

|  |  |
| --- | --- |
| 前提： |
 基本的计算机知识，
 已安装基本软件，以及客户端 web 技术的基本知识。
  |
| 目标： | 了解各种视频格式及其对性能的影响，以及如何在为每个浏览器提供最小的视频文件类型的同时减少视频对整个页面加载时间的影响。 |

为什么优化你的多媒体？
-----------

对于一般网站来说，25% 的带宽来自视频。优化视频有望带来巨大的带宽节省，而这又可以转化为更好的网站性能。

优化视频传输
------

下面的小节描述了以下优化技术：

* 压缩所有视频
* 优化 `<source>` 顺序
* 视频自动播放
* 从静音的视频中移除音频
* 优化视频预加载
* 考虑流媒体
### 压缩所有视频

大多数视频压缩工作都是比较视频内相邻帧之间的细节，目的是移除两帧中相同的细节。压缩视频并导出多种视频格式，包括 WebM、MPEG-4/H.264 和 Ogg/Theora。

你的视频编辑软件可能有减小文件大小的功能。如果没有，有在线工具，比如 FFmpeg（将在后面的小节讨论）可以对视频进行编码、解码、转换以及其他优化功能。

### 优化 `<source>` 顺序

按从小到大的顺序排序视频源。例如，给定三种分别压缩为 10 MB、12 MB 和 13 MB 的不同格式的视频，先声明最小的，最后声明最大的：

```html
<video width="400" height="300" controls="controls">
  <!-- WebM: 10 MB -->
  <source src="video.webm" type="video/webm" />
  <!-- MPEG-4/H.264: 12 MB -->
  <source src="video.mp4" type="video/mp4" />
  <!-- Ogg/Theora: 13 MB -->
  <source src="video.ogv" type="video/ogv" />
</video>
```

浏览器会下载它所理解的第一个格式。这样做的目的是优先提供较小的版本。对于最小的版本，应确保压缩程度最高的视频质量仍然可以。有一些压缩算法可以使视频看起来（很糟糕）像动画 GIF。尽管一个 128 Kb 视频相比 10 MB 在下载方面貌似可以提供更好的用户体验，但是模糊的 GIF 似的视频可能会对品牌或项目产生不好的影响。

参阅 CanIUse.com 了解当前浏览器对视频和其他媒体类型的支持。

### 视频自动播放

要确保背景视频自动循环播放，你必须在视频标签中添加几个属性：`autoplay`、`muted` 和`playsinline`。

```html
<video
 autoplay=""
 loop=""
 muted="true"
 playsinline=""
 src="backgroundvideo.mp4"></video>
```

虽然 `loop` 和 `autoplay` 足以使视频循环和自动播放，但想要在移动浏览器中自动播放的话，`muted` 属性是必需的。

`Playsinline` 对于移动 Safari 浏览器来说是必需的，它允许视频播放时不强制全屏模式。

### 从静音的 hero 视频中移除音频

对于 hero 视频或者其他没有音频的视频，移除音频是明智的选择。

```html
<video autoplay="" loop="" muted="true" playsinline="" id="hero-video">
  <source src="banner\_video.webm" type='video/webm; codecs="vp8, vorbis"' />
  <source src="web\_banner.mp4" type="video/mp4" />
</video>
```

这段 hero 视频代码（上面）常用于会议网站和企业主页。它包含了一个自动播放、循环和静音的视频。没有控制按钮，所以无法听到声音。音频通常是无声的，但仍然存在，而且仍然占用带宽。没有理由在永远静音的视频中提供音频。**删除音频可以节省 20% 的带宽。**

根据你选择的软件，你可能可以在导出和压缩过程中移除音频。如果不能，一个名为 FFmpeg 的免费工具可以帮你做到。这是用 FFmpeg 移除音频的命令字符串：

```bash
ffmpeg -i original.mp4 -an -c:v copy audioFreeVersion.mp4
```

### 视频预加载

预加载属性有三个可用选项：`auto`|`metadata`|`none`。默认设置是 `metadata`。这些设置控制了在页面加载时下载多少视频文件。你可以通过延迟下载较少受欢迎的视频来节省数据。

设置 `preload="none"` 的结果是在播放开始之前，视频不会被下载。这会延迟启动，但对于播放概率较低的视频，可以节省大量数据。

为了更适度的带宽节省，可以设置 `preload="metadata"`，这可能会在页面加载时下载视频的 3%。这对于一些小型或中等大小的文件来说是一个有用的选项。

将设置改为 `auto` 告诉浏览器自动下载整个视频。只有当播放可能性非常高时才这样做。否则，这将浪费大量的带宽。

### 考虑流媒体

视频流媒体允许适当的视频大小和带宽（基于网络速度）被传送到最终用户。类似于响应式图像，正确大小的视频被传送到浏览器，确保视频快速启动、缓冲少以及播放优化。

结论
--

优化视频有可能显著提高网站性能。视频文件相比其他网站文件相对较大，因此始终值得关注。本文解释了如何通过减少文件大小、使用（HTML）下载设置和流媒体来优化网站视频。

* 上一页
* Overview: Performance
* 下一页  (en-US)


CSS 性能优化 - 学习 Web 开发

CSS 性能优化
========

* 上一页
* Overview: Performance
* 下一页

当开发网站时，你需要考虑浏览器如何处理你网站上的 CSS。为了减轻 CSS 可能引起的性能问题，你应该对其进行优化。例如，你应该优化 CSS 以减少阻塞渲染 (en-US)并最小化所需的回流次数。本文将为你介绍关键的 CSS 性能优化技术。

|  |  |
| --- | --- |
| 前提： | 基本的计算机操作能力，已安装基本软件，以及对客户端 Web 技术的基本了解。 |
| 目标： | 了解 CSS 对网站性能的影响，以及如何优化 CSS 以改善性能。 |

优化与否
----

在开始优化 CSS 之前，你应该回答一个问题：“我需要优化什么？”下面讨论的一些技巧和技术是适用于几乎任何 Web 项目的良好实践，然而另一些只在特定情况下需要。试图在所有地方应用这些技术可能是不必要的，而且可能会浪费你的时间。你应该确定每个项目实际上需要哪些性能优化。

为了做到这一点，你需要测量网站性能。正如上面的链接所示，有几种不同的测量性能的方法，其中一些涉及复杂的性能 API。然而，入门的最佳方法是学习如何使用内置的浏览器网络和性能工具，以查看页面加载中哪些部分花费了很长时间，并且需要进行优化。

优化渲染
----

浏览器有一个固定的渲染流程——只有在布局（layout）完成后才能绘制（paint）页面，而布局的前提是要生成渲染树（render tree），而渲染树的生成则需要 DOM 和 CSSOM 树的配合。

如果先让用户看到一个没有样式的页面，等 CSS 样式解析完后再重绘（repaint），这样的体验会很差。所以，浏览器会等到确定需要 CSS 时才开始渲染。只有在下载完 CSS 并生成 CSS 对象模型 CSS 对象模型（CSSOM）之后，浏览器才会绘制页面。

为了优化 CSSOM 的构建并提高页面性能，你可以根据当前 CSS 的状态执行以下一项或多项操作：

* **删除不必要的样式**：这听起来很明显，但你可能不相信，很多开发者都会忘记清除在开发过程中添加的不会被用到的 CSS 规则。所有脚本都会被解析，无论它在布局于绘制时是否被使用，因此删除无用样式可以加速网页渲染。正如如何从网站中删除未使用的 CSS？（csstricks.com，2019）总结的那样，对于大型代码库来说，这是一个难题，没有灵丹妙药可以可靠地查找和删除未使用的 CSS。你需要尽量保持 CSS 的模块化，并在添加和删除时谨慎小心。
* **将 CSS 拆分为独立模块**：CSS 模块化可以延迟加载在页面加载阶段非必要的 CSS，缩短初始 CSS 的阻塞和加载时间。最简单的方法是将 CSS 拆分为独立的文件，并仅加载所需内容：

```html
<!-- 加载和解析 styles.css 会阻塞渲染 -->
<link rel="stylesheet" href="styles.css" />

<!-- 加载和解析 print.css 不会阻塞渲染 -->
<link rel="stylesheet" href="print.css" media="print" />

<!-- 在大屏幕上，加载和解析 mobile.css 不会阻塞渲染 -->
<link
 rel="stylesheet"
 href="mobile.css"
 media="screen and (max-width: 480px)" />
```

上面的示例提供了三组样式——始终会加载的默认样式、只有在打印文档时才会加载的样式，以及仅在窄屏设备上加载的样式。默认情况下，浏览器会假设每个指定的样式表都会阻塞渲染。你可以通过添加包含媒体查询的 `media` 属性来告诉浏览器何时应用样式表。当浏览器看到只需要在特定情况下应用的样式表时，它仍然会下载样式表，但不会阻塞渲染。通过将 CSS 拆分为多个文件，主要的渲染阻塞文件（在本例中为 `styles.css`）会小得多，从而减少渲染阻塞的时间。
* **最小化和压缩你的 CSS**：最小化包括在代码进入生产环境后，在文件中删除仅为了人类可读性而存在的所有空格。通过对 CSS 进行最小化，你可以大大减少加载时间。通常，最小化是作为构建过程的一部分进行的（例如，大多数 JavaScript 框架在构建项目准备部署时会对代码进行最小化）。除了最小化之外，还要确保你的站点所在的服务器在提供文件之前对文件使用诸如 gzip 的压缩。
* **简化选择器**：人们经常编写比应用所需样式更复杂的选择器。这不仅会增加文件大小，还会增加解析这些选择器的时间。例如：

```css
/\* 非常具体的选择器 \*/
body div#main-content article.post h2.headline {
  font-size: 24px;
}

/\* 你可能只需要这个 \*/
.headline {
  font-size: 24px;
}
```

将选择器简化和降低优先级对于维护也是有好处的。简单选择器的作用很容易理解，如果选择器不具有那么高的优先级，以后需要时就很容易覆盖样式。
* **不要将样式应用于不需要的元素**：常见的错误是使用通用选择器将样式应用于所有元素，或者至少应用于比实际需要的元素更多的元素。这种类型的样式会对性能产生负面影响，特别是在较大的站点上。

```css
/\* 选择 <body> 元素内的所有元素 \*/
body \* {
  font-size: 14px;
  display: flex;
}
```

请记住，许多属性（例如 `font-size`）会从其父元素继承其值，因此你不需要在所有地方应用它们。而且功能强大的工具（如弹性盒子）需要谨慎使用。在所有地方都使用它们可能会导致各种意外行为。
* **使用 CSS 精灵图减少图像相关的 HTTP 请求**：CSS 精灵图 是一种技术，它将你希望在站点上使用的多个小图像（例如图标）放入单个图像文件中，然后使用不同的 `background-position` 值在不同的位置显示图像的一部分。这可以大大减少获取图像所需的 HTTP 请求数量。
* **预加载重要资源**：你可以使用 `rel="preload"` 将 `<link>` 元素转换为预加载器，用于关键资源，包括 CSS 文件、字体和图片：

```html
<link rel="preload" href="style.css" as="style" />

<link
 rel="preload"
 href="ComicSans.woff2"
 as="font"
 type="font/woff2"
 crossorigin />

<link
 rel="preload"
 href="bg-image-wide.png"
 as="image"
 media="(min-width: 601px)" />
```

使用 `preload`，浏览器会尽快获取引用的资源，并将其存储在浏览器缓存中，以便在后续代码中引用时可以更快地使用它们。为了让用户体验尽可能流畅，我们应提前加载页面加载初期用户会遇到的高优先级资源。请注意，你还可以使用 `media` 属性创建响应式的预加载器。
 另请参阅预加载重要资源以提升加载速度这篇 web.dev 上的文章（2020）。
处理动画
----

动画可以改善感知性能，使界面更加流畅，让用户在等待页面加载时感觉到进展（例如加载旋转图标）。然而，更大更多的动画自然需要更多的处理能力来处理，这可能会降低性能。

最简单的建议是减少所有不必要的动画。你还可以为用户提供一个控件/站点选项，让他们可以关闭动画，例如当他们使用低功率设备或电池电量有限的移动设备时。你还可以使用 JavaScript 来控制页面是否应用动画。还有一个名为 `prefers-reduced-motion` 的媒体查询，可以根据用户对动画的操作系统级偏好选择性地提供动画样式。

对于必要的 DOM 动画，建议尽可能使用 CSS 动画，而不是 JavaScript 动画（Web 动画 API 提供了一种直接使用 JavaScript 连接到 CSS 动画的方法）。

### 选择要进行动画处理的属性

接下来，动画性能在很大程度上取决于你要进行动画处理的属性。某些属性在进行动画处理时会触发回流（进一步也会触发重绘 (en-US)），应该避免使用这些属性。这些属性包括：

* 修改元素的尺寸，例如 `width`、`height`、`border` 和 `padding`。
* 重新定位元素，例如 `margin`、`top`、`bottom`、`left` 和 `right`。
* 更改元素的布局，例如 `align-content`、`align-items` 和 `flex`。
* 添加改变元素几何形状的视觉效果，例如 `box-shadow`。

现代浏览器很智能，只会重新绘制文档中已更改的区域，而不是整个页面。因此，越大的动画成本越高。

如果可以的话，最好对不会引起回流/重绘的属性进行动画处理。这包括：

* 变换
* `opacity`
* `filter`
### 在 GPU 上进行动画处理

为了进一步提高性能，你应该考虑将动画处理工作转移到主线程之外，并放到设备的 GPU 上进行（也称为合成（compositing））。这可以通过选择特定类型的动画来实现，浏览器会自动将这些动画发送到 GPU 来处理。包括：

* 3D 变换动画，例如 `transform: translateZ()` 和 `rotate3d()`。
* 具有某些其他属性动画的元素，例如 `position: fixed`。
* 应用了 `will-change` 的元素（请参阅下面的小节）。
* 特定的在其自己层中渲染的元素，包括 `<video>`、`<canvas>` 和 `<iframe>`。

在 GPU 上进行动画处理可以提高性能，尤其是在移动设备上。然而，将动画处理移到 GPU 并不总是那么简单。请阅读 CSS GPU 动画：正确使用（smashingmagazine.com，2016）以获取非常有用和详细的分析。

使用 `will-change` 优化元素变化
-----------------------

浏览器可能会在元素实际发生变化之前进行优化设置。这类优化可以通过提前完成可能需要的大量工作，提高页面的响应速度。CSS 的 `will-change` 属性向浏览器提示元素预期的变化方式。

**备注：**`will-change` 应该作为处理现有的性能问题的最后一招。不应该用它来预测性能问题。

```css
.element {
  will-change: opacity, transform;
}
```

优化渲染阻塞
------

CSS 可以使用媒体查询将样式限定在特定条件下。媒体查询对于响应式网页设计非常重要，并且可以帮助我们优化关键渲染流程。浏览器会阻塞渲染直到解析完所有的样式，但不会阻塞不会使用的样式，例如打印样式表。通过根据媒体查询将 CSS 拆分为多个文件，可以防止在下载未使用的 CSS 时阻塞渲染。要创建一个非阻塞的 CSS 链接，将不立即使用的样式（例如打印样式）移动到单独的文件中，在 HTML 标记中添加一个 `<link>`，并添加一个媒体查询，在本例中是打印样式表。

```html
<!-- 加载和解析 styles.css 会阻塞渲染 -->
<link rel="stylesheet" href="styles.css" />

<!-- 加载和解析 print.css 不会阻塞渲染 -->
<link rel="stylesheet" href="print.css" media="print" />

<!-- 在大屏幕上，加载和解析 mobile.css 不会阻塞渲染 -->
<link
 rel="stylesheet"
 href="mobile.css"
 media="screen and (max-width: 480px)" />
```

默认情况下，浏览器假设每个指定的样式表都会阻塞渲染。通过添加具有媒体查询的 `media` 属性告诉浏览器样式表应该何时应用。当浏览器看到一个样式表时，它会知道它只需要将其应用于特定的情况，它仍然会下载样式表，但不会阻塞渲染。通过将 CSS 拆分为多个文件，主要的阻塞渲染文件（在本例中是 `styles.css`）会变得更小，从而减少了渲染阻塞的时间。

改善字体性能
------

本节包含一些有用的技巧，可以用于改善 Web 字体的性能。

总体而言，请仔细考虑在你的网站上使用的字体。某些字体文件可能非常大（几兆字节）。虽然使用大量字体以获得视觉上的吸引力可能很诱人，但这可能会显著降低页面加载速度，并使你的网站看起来凌乱不堪。你可能只需要使用两到三种字体，如果选择使用 Web 安全字体，则可以使用更少的字体。

### 字体加载

请记住，字体仅在使用 `font-family` 属性应用于元素时才会加载，而不是在首次使用 `@font-face` at 规则引用时加载：

```css
/\* 字体在此处没有加载 \*/
@font-face {
  font-family: "Open Sans";
  src: url("OpenSans-Regular-webfont.woff2") format("woff2");
}

h1,
h2,
h3 {
  /\* 字体实际上在此处加载 \*/
  font-family: "Open Sans";
}
```

因此，使用 `rel="preload"` 来提前加载重要的字体，这样它们在实际需要时可以更快地可用：

```html
<link
 rel="preload"
 href="OpenSans-Regular-webfont.woff2"
 as="font"
 type="font/woff2"
 crossorigin />
```

如果你的 `font-family` 声明位于一个大型的外部样式表中，并且在解析过程中不会被立即访问到，那么这种做法更有益处。不过要适当权衡：字体文件相当大，如果预加载太多字体，可能会延迟其他资源的加载。

你还可以考虑以下几点：

* 使用 `rel="preconnect"` 与字体提供方建立早期连接。有关详细信息，请参阅预连接到关键的第三方源。
* 使用 CSS 字体加载 API 通过 JavaScript 自定义字体加载行为。
### 只加载所需的字形

在选择用于正文的字体时，很难确定将在其中使用的字形，特别是如果你处理的是用户生成的内容和/或涉及多种语言的内容。

然而，如果你知道你将使用特定的字形集（例如，仅用于标题或特定标点符号字符的字形），你可以限制浏览器需要下载的字形数量。这可以通过创建仅包含所需子集的字体文件来实现，这个过程叫做子集化。然后可以使用 `@font-face` 的 `unicode-range` (en-US) 描述符来指定何时使用你的子集字体。如果页面中没有使用该范围内的任何字符，则不会下载该字体。

```css
@font-face {
  font-family: "Open Sans";
  src: url("OpenSans-Regular-webfont.woff2") format("woff2");
  unicode-range: U+0025-00FF;
}
```

### 使用 `font-display` 描述符定义字体的显示行为

应用于 `@font-face` at 规则的 `font-display` 描述符定义了浏览器加载和显示字体文件的方式，使得文字在字体正在加载或加载失败时都能以备用字体显示。这通过使文本可见而不是显示空白屏幕来提高性能，但代价是出现未样式化文本的闪烁。

```css
@font-face {
  font-family: someFont;
  src: url(/path/to/fonts/someFont.woff) format("woff");
  font-weight: 400;
  font-style: normal;
  font-display: fallback;
}
```

### 使用 CSS 局限优化样式重新计算

通过使用 CSS 局限模块中定义的属性，你可以指示浏览器将页面的不同部分隔离开来，并独立地优化它们的渲染。这允许在渲染各个部分时提高性能。例如，你可以指定浏览器在特定容器于视口中可见之前不要渲染它们。

`contain` 属性允许作者精确指定要应用于页面上各个容器的局限类型。这使得浏览器可以针对 DOM 的一部分重新计算布局、样式、绘制、大小或它们的任意组合。

```css
article {
  contain: content;
}
```

`content-visibility` 属性是一个有用的快捷方式，允许作者在一组容器上应用一组强大的局限，并指定浏览器在需要之前不要布局和渲染这些容器。

另一个属性 `contain-intrinsic-size` 也可用，它允许你为容器提供一个占位大小，同时它们受到局限的影响。这意味着即使容器的内容尚未渲染，它们也会占据空间，允许包含执行其性能优化而不会出现滚动条的变动和卡顿。这提高了内容正在加载时用户体验的质量。

```css
article {
  content-visibility: auto;
  contain-intrinsic-size: 1000px;
}
```

* 上一页
* Overview: Performance
* 下一页
参见
--

* CSS 动画性能
* 字体最佳实践（来自 web.dev，2022）
* content-visibility：提升渲染性能的新 CSS 属性（来自 web.dev，2022）


web 性能的商业案例 - 学习 Web 开发

web 性能的商业案例
===========

* 上一页
* Overview: Performance
* 下一页

我们已经讨论了 web 性能的重要性。你已经了解了如何优化 web 性能。但是如何说服你的客户和/或管理层优先考虑并投资于性能呢？在本节中，我们将讨论如何创建一个清晰的商业案例，以说服决策者进行投资。

|  |  |
| --- | --- |
| 前提： | 基本的计算机素养，对客户端 web 技术有基本的了解，以及对 web 性能优化有基本的理解。 |
| 目标： | 增强使客户和管理层将 web 性能作为优先事项。 |

将性能作为商业优先事项
-----------

我们已经讨论了优先考虑性能如何改善用户体验，从而增加收入。我们知道了，不优先考虑 web 性能可能导致业务收入的损失。本文讨论了某些业务指标与用户的 web 性能体验直接相关，并介绍了如何应用服务设计来提升用户对 web 性能的体验。它强调了理解累积体验如何影响转化率和留存率的重要性。

### 性能预算

设定一个 web 性能预算可以帮助你确保团队维护网站性能，避免性能衰退。性能预算是一组限制条件，用于指定必须保持的性能指标，例如允许的最大 HTTP 请求数量、所有资源的最大总大小、在特定设备上的最低可接受帧率等。预算可以应用于单个文件、文件类型、页面上加载的所有文件、特定的性能指标或一段时间内的阈值。预算反映了可达到的目标，无论是基于时间、数量还是规则。

定义和推广预算有助于性能倡导者为用户体验和其他利益相关方，比如市场营销、销售或者想要添加视频、第三方脚本或者框架的开发者之间进行平衡。性能预算 (en-US)可以帮助开发团队保护用户的最佳性能，同时使业务能够开拓新市场并提供定制化的体验。

### 关键绩效指标

将关键绩效指标（KPI）设定为目标可以突出既是业务目标又是性能目标的绩效目标。KPI 既可以是衡量用户体验和性能对企业收入影响的一组重要业务指标，也可以是展示优先考虑性能的好处的一种方式。以下是一些值得考虑的 KPI：

**转化率**

你的流量中采取有意义行动（例如完成购买或订阅 newsletter）的百分比。当企业网站运行缓慢时，可能会阻止用户完成所需的任务，从而导致转化率低。

**停留在网站上的时间**

用户在你的网站上平均停留的时间。当网站运行缓慢时，用户更有可能提前离开网站，导致停留时间指标较低。

**净推荐值**

净推荐值（NPS）是评估公司品牌、产品或服务的客户忠诚度的指标。用户体验较差的性能可能导致品牌声誉不佳。

将转化率、停留时间和/或净推荐值设定为 KPI，为 web 性能方面的努力赋予财务和其他业务目标的价值，并通过指标证明这些努力的价值，以提高支持度。

* 上一页
* Overview: Performance


Web performance basics - 学习 Web 开发

Web performance basics
======================

有很多的理由告诉你为什么你的网站需要尽可能好的性能。下面是关于最佳实践，工具，API 以及链接的简明介绍，它为每个主题提供了更多的信息。意识到对用户来说什么是真正重要的也至关重要，他可能不是绝对意义上的时间而是用户感知的时间。

#### 最佳实践

* 从学习浏览器的关键渲染路径开始。了解这些会帮助你通晓如何提升浏览器的性能。
* 使用resource hints例如`rel=preconnect, rel=dns-prefetch, rel=prefetch, and rel=preload`
* 压缩 Js 代码至最小。只为当前页面加载需要使用到的 js 代码
* CSS性能因素
* 在你的服务器（或者 CDN）上使用 HTTP/2协议
* 使用 CDN 托管静态资源，这样可以显著减少加载时间
* 使用gzip, Brotli 或者 Zopfli压缩你的资源
* 图片优化（如果可以，尽可能使用 css 动画或者 svg）
* 在超出应用视口范围的部分使用懒加载，如果你这么做了，为 SEO 制定一个后备计划（例如为 bot traffic 渲染整个页面）

#### 工具

* 学习使用Firefox Dev Tools来分析你的网站。
* Pagespeed Insights 可以分析你的页面并且给出一些通用的建议来提升网站性能。
* Lighthouse 可以给你一份有关你网站的包括性能，SEO 和无障碍等多个方面的详细分类。
* 使用 Webpagetest.org检测页面在不同真实设备类型和地点时候的速度。trics.
* 定义一个绩效预算（performance budget）。

#### APIs

* 收集用户指标通过 https://github.com/akamai/boomerang 。
* 或者通过window.performance.timing 直接收集。

#### 不该做的事（坏的实践）

* 将任何东西都下载下来
* 使用未经压缩的媒体文件


什么是 web 性能？ - 学习 Web 开发

什么是 web 性能？
===========

* 上一页
* Overview: Performance
* 下一页

Web 性能就是让网站变得更快，以及让缓慢过程*看起来*更快。网站加载速度是否很快，是否允许用户快速开始与之交互，是否在某些东西需要时间加载时提供可靠的反馈（例如，一个“加载中”的旋转指示器）？网页的滚动和动画是否流畅？本文简要介绍了客观、可衡量的网络性能\*，涵盖了网络优化所涉及的技术、方法和工具。

|  |  |
| --- | --- |
| 预备条件： | 基本计算机素养、基本软件已安装，以及客户端网络技术的基本知识。 |
| 目标： | 为了了解网络性能所涉及的基础知识。 |

*\*对比下一篇文章中介绍的主观感知性能*

什么是 web 性能？
-----------

Web 性能是对网站或应用程序的客观度量和用户的体验。这包括以下主要方面：

* **减少总体负载时间**：需要多长时间才能将渲染网站所需的文件下载到用户的计算机上？这往往受到延迟 (en-US)、文件大小、文件数量以及其他因素的影响。一般策略是使文件尽可能小，尽可能减少 HTTP 请求的次数，并采用巧妙的加载技术（例如 preload）使文件更快可用。
* **尽快使网站可用**：简单来说就是以合理的顺序加载你的网站资源，以便用户能够更快地开始使用。在用户继续执行主要任务的同时，任何其他资源都可以在后台继续加载。有时我们也会在实际需要时才加载资源（这被称为懒加载）。网站从开始加载，到达到可用状态为止所需的时间被称为交互等待时间 (en-US)。
* **流畅性和交互性**：应用程序是否可靠，使用起来是否愉快？滚动是否流畅？按钮是否可点击？弹出窗口是否能够快速打开，并且在打开时动画是否流畅？在制作流畅的应用程序时，有很多优秀实践可以参考。例如使用 CSS 动画而不是 JavaScript 来制作动画，并尽量减少由于 DOM 变化而引起重绘 UI 的次数。
* **感知性能**：用户所体验到的，是网站看起来有多快，而不是网站实际有多快。用户感受到的性能与任何客观统计数据一样重要，甚至可能更重要。但这种主观的感受不容易测量。感知性能是用户的感受，而不是度量标准。即使操作将花费很长时间（因为延迟或其他原因），也可以通过显示“加载中”的旋转指示器或一系列有用的提示和技巧（或笑话，以及其他你认为合适的内容）来保持用户在等待期间的参与度。这种方法比什么都不显示要好得多。因为什么都不显示会让用户觉得等待了更长的时间，可能会让你的用户认为网站坏了，然后放弃使用。
* **性能测量**：Web 性能包括测量应用程序的实际速度和感知速度，在可能的情况下进行优化，然后监视性能，以确保已经优化过的内容保持优化状态。这涉及到许多度量标准（可以指示成功或失败的可度量指标）和测量这些度量标准的工具，我们将在本节中讨论这些度量标准。

总而言之，许多特性都会影响性能。包括延迟、应用程序大小、DOM 节点数量、资源请求数量、JavaScript 性能、CPU 负载等等。重要的是尽量缩短加载和响应时间，并通过增加额外的功能来隐藏延迟：先使网站变得可以交互，同时在后台加载用户体验中的次要部分。

**备注：** Web 性能既包括客观测量，如加载时间、每秒帧数和交互等待时间 (en-US)，也包括主观体验，如内容加载所需的时间。

内容如何渲染
------

为了有效地理解 web 性能、背后的问题以及我们上面提到的主要部分，你需要先了解一些关于浏览器如何工作的细节。这包括：

* **浏览器的工作原理**。当你请求一个 URL 并敲击
 `Enter`
 或
 `Return`
 按键时，浏览器会找出保存该网站文件的服务器的位置，与之建立连接，并请求文件。请参阅渲染页面：浏览器的工作原理。
* **源顺序**。HTML 索引文件的源顺序会显著影响性能。从索引文件链接到的附加资源的下载通常基于引用的顺序。这是可以调整的，需要进行优化。请注意：一些资源在被解析和执行完成之前，会阻塞其他的下载。
* **关键路径**。这是从服务器下载文件后，浏览器用来构建 web 文档的过程。浏览器按照明确定义的步骤进行操作，优化关键渲染路径以优先显示与当前用户操作相关的内容，将显著改善内容呈现时间。有关更多信息，请参见关键渲染路径。
* **文档对象模型**。文档对象模型（DOM）是一种树状结构，它将 HTML 的内容和元素表示为节点树。这包括所有的 HTML 属性和节点之间的关系。加载页面后的大量 DOM 操作（例如添加、删除或移动节点）可能会影响性能。因此有必要了解 DOM 的工作原理，以及如何减轻此类问题。有关更多信息，请参见文档对象模型。
* **延迟**。我们之前简要地提到过延迟。简单来说，延迟是你的网站信息从服务器传输到用户计算机所需的时间。建立 TCP 和 HTTP 连接需要消耗系统资源，并且在网络上来回传输请求和响应字节时不可避免地存在延迟。但是有一些方法可以减少延迟（例如，通过下载更少的文件来减少 HTTP 请求的数量，使用 CDN 使你的站点在全球范围内具有更普遍的性能，并使用 HTTP/2 协议提高传输的效率）。你可以在了解延迟 (en-US)上阅读有关此主题的所有内容。
结论
--

目前就是这些。我们希望我们对 web 性能主题的简要概述可以帮助你理解它的全部内容，并使你想要知道更多。接下来，我们将研究感知性能，以及如何使用一些巧妙的技术使某些不可避免的性能影响对用户来说不那么严重，或者被完全忽视。

* 上一页
* Overview: Performance
* 下一页


JavaScript 性能优化 - 学习 Web 开发

JavaScript 性能优化
===============

* 上一页
* Overview: Performance
* 下一页

考虑如何在你的网站上使用 JavaScript 以及如何减少它可能造成的性能问题是非常重要的。虽然图片和视频占了平均网站下载字节的 70% 以上，但是逐字节来看，JavaScript 对性能的负面影响更大——它会显著影响下载时间、渲染性能、CPU 和电池使用。本文介绍了一些优化 JavaScript 的技巧和方法，以提高你的网站的性能。

|  |  |
| --- | --- |
| 前提： |
 基本的计算机知识，
 已安装基本软件，以及对客户端 Web 技术的基本了解。
  |
| 目标： | 了解 JavaScript 对 Web 性能的影响，以及如何减轻或解决相关问题。 |

优化与否
----

在开始优化代码之前，你应该先回答一个问题：“我需要优化什么？”下面讨论的一些技巧和方法是适用于任何 Web 项目的良好实践，而其他一些只在特定情况下才需要。试图在所有地方应用这些技术可能是不必要的，也可能是浪费时间。你应该确定每个项目实际上需要哪些性能优化。

为此，你需要测量性能。正如前面的链接所示，有多种不同的方法来测量性能，其中一些方法涉及复杂的性能 API。然而，最好的入门方式是学习如何使用内置的浏览器网络和性能工具，查看页面加载的哪些部分花费了很长时间，并需要进行优化。

优化 JavaScript 的下载
-----------------

最高效、最不阻塞的 JavaScript 是根本不使用 JavaScript。你应该尽量少使用 JavaScript。以下是一些需要记住的点：

* **并非总是需要框架**：你可能熟悉使用某个 JavaScript 框架。如果你对使用该框架有经验和信心，并且喜欢它提供的所有工具，那么它可能是你构建大多数项目的首选。然而，框架会增加 JavaScript 的负担。如果你创建的是一个相对静态的体验，对 JavaScript 的要求很少，那么你可能不需要那个框架。也许你可以使用几行标准 JavaScript 来实现你需要的功能。
* **考虑更简单的解决方案**：你可能有一个华丽、有趣的解决方案要实现，但请考虑用户是否会喜欢它。他们是否更喜欢简单的东西？
* **删除未使用的代码**：这听起来很明显，但令人惊讶的是很多开发者忘记清除在开发过程中添加的不会被用到的功能。你需要谨慎并有意识地添加和删除代码。所有脚本都会被解析，无论它是否被使用；因此，加快下载速度的一个快速方法是摆脱任何不会被使用的功能。此外，要考虑通常只会使用框架中的一小部分功能。是否有可能创建一个仅包含你所需部分的框架的自定义构建版本？
* **考虑使用浏览器内置特性**：也许你可以使用浏览器已经具备的特性，而不是通过 JavaScript 自己创建。例如：
    + 使用内置的客户端表单验证。
    + 使用浏览器自带的 `<video>` 播放器。
    + 使用 CSS 动画而不是 JavaScript 动画库（参见处理动画）。

你还应该将 JavaScript 分成表示关键部分和非关键部分的多个文件。通过使用 JavaScript 模块可以比仅使用单独的外部 JavaScript 文件更高效地实现这一点。

然后，你可以优化这些较小的文件。代码压缩 (en-US)减少文件中的字符数，从而减小 JavaScript 的字节数或大小。Gzip 压缩进一步压缩文件，即使你不对代码进行压缩也应该使用。Brotli 压缩类似于 Gzip，但通常优于 Gzip 压缩。

你可以手动拆分和优化代码，但通常使用类似 Webpack 的模块打包工具会做得更好。

处理解析和执行
-------

在查看本节中包含的要点之前，重要的是要了解浏览器页面渲染过程中 JavaScript 是在*哪里*处理的。当一个网页被加载时：

1. 通常首先解析 HTML，按照页面上出现的顺序进行解析。
2. 遇到 CSS 时，解析 CSS 以了解需要应用于页面的样式。在此期间，开始获取链接的资源，如图像和网络字体。
3. 遇到 JavaScript 时，浏览器解析、评估并执行它。
4. 稍后，浏览器根据应用于每个 HTML 元素的 CSS 来确定每个元素的样式。
5. 然后将经过样式处理的结果绘制到屏幕上。

**备注：** 这只是一个非常简单的叙述，但可以让你了解发生的事情。

这里关键的步骤是第 3 步。默认情况下，JavaScript 的解析和执行会阻塞渲染。这意味着浏览器在遇到 JavaScript 之后，会阻塞解析任何出现在其后的 HTML 代码，直到脚本处理完成。因此，样式和绘制也会被阻塞。因此，你不仅需要仔细考虑你要下载的内容，还要考虑代码何时以及以何种方式执行。

接下来的几个部分提供了优化 JavaScript 解析和执行的有用技巧。

尽早加载关键资源
--------

如果某个脚本非常重要，并且你担心由于加载速度不够快而影响性能，你可以在文档的 `<head>` 中加载它：

```html
<head>
  ...
  <script src="main.js"></script>
  ...
</head>
```

这个方法可以正常工作，但会阻塞渲染。更好的策略是使用 `rel="preload"` 来为关键 JavaScript 创建一个预加载器：

```html
<head>
  ...
  <!-- 预加载 JavaScript 文件 -->
  <link rel="preload" href="important-js.js" as="script" />
  <!-- 预加载 JavaScript 模块 -->
  <link rel="modulepreload" href="important-module.js" />
  ...
</head>
```

预加载的 `<link>` 尽快获取 JavaScript，而不会阻塞渲染。然后，你可以在页面中任何位置使用它：

```html
<!-- 在合适的位置包含该脚本 -->
<script src="important-js.js"></script>
```

或者在脚本中使用它（在使用 JavaScript 模块的情况下）：

```js
import { function } from "important-module.js";
```

**备注：** 预加载并不能保证脚本在你包含它时已经加载完成，但它确实意味着它将尽早开始下载。即使未完全移除阻塞渲染的时间，渲染阻塞时间仍将缩短。

推迟非关键 JavaScript 的执行
--------------------

另一方面，你应该尽量推迟解析和执行非关键 JavaScript 的时间，直到它真正需要时再加载。提前加载它会不必要地阻塞渲染。

首先，你可以给 `<script>` 元素添加 `async` 属性：

```html
<head>
  ...
  <script async src="main.js"></script>
  ...
</head>
```

这会导致脚本获取与 DOM 解析并行进行，因此它将在同一时间准备好，不会阻塞渲染。

**备注：** 还有另一个属性 `defer`，它会导致脚本在文档解析完成之后，但在触发 `DOMContentLoaded` 事件之前执行。这与 `async` 有类似的效果。

你也可以直到需要时才加载 JavaScript。这可以通过 DOM 脚本编写来实现，例如：

```js
const scriptElem = document.createElement("script");
scriptElem.src = "index.js";
scriptElem.addEventListener("load", () => {
  // 一旦 index.js 已完全加载，运行其中的函数
  init();
});
document.head.append(scriptElem);
```

可以使用 `import()` (en-US) 函数动态加载 JavaScript 模块：

```js
import("./modules/myModule.js").then((module) => {
  // 对模块进行操作
});
```

分解长任务
-----

当浏览器运行 JavaScript 时，它会将脚本组织成按顺序运行的任务，例如进行 fetch 请求、通过事件处理程序驱动用户交互和输入、运行 JavaScript 驱动的动画等等。

大部分任务都在主线程上运行，其中包括运行在 Web Worker 中的 JavaScript。主线程一次只能运行一个任务。

当单个任务的执行时间超过 50 毫秒时，它被归类为长任务。如果用户在长任务正在运行时尝试与页面交互或请求重要的 UI 更新，他们的体验将受到影响。预期的响应或视觉更新将被延迟，导致 UI 看起来迟钝或无响应。

为了解决这个问题，你需要将长任务分解为较小的任务。这样可以给浏览器更多机会执行重要的用户交互处理或 UI 渲染更新，浏览器可以在每个较小任务之间执行它们，而不是仅仅在长任务之前或之后执行。在你的 JavaScript 中，你可以通过将代码拆分为单独的函数来实现这一点。这样做也有其他几个原因，比如更容易维护、调试和编写测试。

例如：

```js
function main() {
  a();
  b();
  c();
  d();
  e();
}
```

然而，这种结构对于主线程阻塞并没有帮助。由于所有五个函数都在一个主函数中运行，浏览器将它们整体作为一个长任务运行。

为了处理这个问题，我们倾向于定期运行一个“yield”函数，以使代码“让步给主线程”。这意味着我们的代码被分成多个任务，在执行每个任务之间，浏览器有机会处理高优先级任务，比如更新 UI。一个常见的模式是使用 `setTimeout()` 将执行推迟到一个单独的任务中：

```js
function yield() {
  return new Promise((resolve) => {
    setTimeout(resolve, 0);
  });
}
```

可以在这样的任务运行模式中使用它，以在每个任务运行后让步给主线程：

```js
async function main() {
  // 创建要运行的函数数组
  const tasks = [a, b, c, d, e];

  // 循环遍历任务
  while (tasks.length > 0) {
    // 从任务数组中取出第一个任务
    const task = tasks.shift();

    // 运行任务
    task();

    // 让步给主线程
    await yield();
  }
}
```

为了进一步改进，我们可以使用 `navigator.scheduling.isInputPending()` (en-US)，仅在用户尝试与页面交互时运行 `yield()` 函数：

```js
async function main() {
  // 创建要运行的函数数组
  const tasks = [a, b, c, d, e];

  while (tasks.length > 0) {
    // 让步给挂起的用户输入
    if (navigator.scheduling.isInputPending()) {
      await yield();
    } else {
      // 从任务数组中取出第一个任务
      const task = tasks.shift();

      // 运行任务
      task();
    }
  }
}
```

这样可以避免在用户积极与页面交互时阻塞主线程，从而提供更流畅的用户体验。然而，通过仅在必要时让步，我们可以在没有用户输入需要处理时继续运行当前任务。这也避免了任务被放置在队列末尾，排在其他非必要的浏览器初始化任务之后。

处理 JavaScript 动画
----------------

动画可以改善感知性能，使界面更加流畅，让用户在等待页面加载时感觉到进展（例如加载旋转图标）。然而，更大更多的动画自然需要更多的处理能力来处理，这可能会降低性能。

最显然的动画建议是使用更少的动画——去除任何非必要的动画，或考虑为用户提供一个偏好设置，让他们可以关闭动画，例如当他们使用低功率设备或电池电量有限的移动设备时。

对于关键的 DOM 动画，建议尽可能使用 CSS 动画，而不是 JavaScript 动画（Web 动画 API 提供了一种通过 JavaScript 直接连接到 CSS 动画的方式）。直接使用浏览器执行 DOM 动画而不是使用 JavaScript 操纵内联样式表的效率更高。另请参阅 CSS 性能优化 > 处理动画。

对于无法在 JavaScript 中处理的动画，例如在 HTML `<canvas>` 上创建动画，建议使用 `Window.requestAnimationFrame()` 而不是旧的选项，例如 `setInterval()`。`requestAnimationFrame()` 方法专门设计用于高效、一致地处理动画帧，以获得流畅的用户体验。基本模式如下所示：

```js
function loop() {
  // 在绘制下一帧动画之前清除 canvas
  ctx.fillStyle = "rgba(0, 0, 0, 0.25)";
  ctx.fillRect(0, 0, width, height);

  // 在 canvas 上绘制对象并更新其位置数据，
  // 准备下一帧动画
  for (const ball of balls) {
    ball.draw();
    ball.update();
  }

  // 调用 requestAnimationFrame，在正确的时间再次运行 loop() 函数，
  // 以保持动画的流畅性
  requestAnimationFrame(loop);
}

// 调用 loop() 函数一次，启动动画
loop();
```

你可以在绘制图形 > 动画中找到有关 canvas 动画的简介，以及在对象构建实践 中找到更详细的示例。你还可以在 Canvas 教程中找到一整套 canvas 教程。

优化事件性能
------

跟踪及处理事件对于浏览器来说是很耗资源的，特别是当你持续运行一个事件时。例如，你可以使用 `mousemove` 事件来跟踪鼠标的位置，以检查它是否仍在页面的某个区域内：

```js
function handleMouseMove() {
  // 当鼠标指针在 elem 内时执行一些操作
}

elem.addEventListener("mousemove", handleMouseMove);
```

你可能在页面中运行一个 `<canvas>` 游戏。当鼠标在 canvas 内部时，你需要不断检查鼠标移动和光标位置，并更新游戏状态——包括分数、时间、所有精灵的位置以及碰撞检测信息等。一旦游戏结束，你将不再需要进行所有这些操作，实际上，继续保持监听该事件将浪费处理能力。

因此，最好是删除不再需要的事件监听器。可以使用 `removeEventListener()` 来实现：

```js
elem.removeEventListener("mousemove", handleMouseMove);
```

另一个要点是尽可能使用事件委托。当你有一些代码需要在用户与大量子元素中的任何一个进行交互时，可以在它们的父元素上设置事件监听器。在任何子元素上触发的事件都会冒泡到它们的父元素，这样你无需单独为每个子元素设置事件监听器。减少要跟踪的事件监听器数量可以提高性能。

请参阅事件委托以了解更多详细信息和一个有用的示例。

编写更高效代码的技巧
----------

有几个通用的最佳实践可以使你的代码运行更高效。

* **减少 DOM 操作**：访问和更新 DOM 的计算成本很高，因此你应该尽量减少 JavaScript 这种操作方面的操作量，特别是在执行持续的 DOM 动画时（参见上面的处理 JavaScript 动画）。
* **批量进行 DOM 更改**：对于重要的 DOM 更改，你应该将它们按批次处理，而不是在每个更改发生时单独执行。这可以减少浏览器实际执行的工作量，并改善感知性能。将多个更新一次性完成，而不是不断进行小的更新，可以使界面看起来更流畅。一个有用的技巧是，当你有大量 HTML 代码要添加到页面时，先构建整个片段（通常在 `DocumentFragment` 内部），然后一次性将其附加到 DOM 中，而不是逐个附加每个项目。
* **简化 HTML 代码**：DOM 树越简单，使用 JavaScript 进行访问和操作的速度就越快。仔细思考你的用户界面的需求，并删除不必要的冗余代码。
* **减少循环代码的数量**：循环是很消耗资源的，因此尽可能减少代码中的循环使用量。在不可避免使用循环的情况下：
    + 在不必要时避免运行完整的循环，适时使用 `break` 或 `continue` 语句。例如，如果你正在搜索数组中的特定名称，找到名称后就可以跳出循环；没有必要继续运行循环迭代：
     js
    ```js
    function processGroup(array) {
      const toFind = "Bob";
      for (let i = 0; i < array.length - 1; i++) {
        if (array[i] === toFind) {
          processMatchingArray(array);
          break;
        }
      }
    }

    ```
    + 在循环外执行只需要做一次的工作。这可能听起来有点显而易见，但很容易被忽视。看下面的代码片段，它获取一个包含要进行某种处理的数据的 JSON 对象。在这种情况下，`fetch()` 操作在每次循环迭代中都被执行，这是一种浪费计算能力的做法。可以将第 3 和第 4 行移到循环外部，这样网络获取操作只会执行一次。
     js
    ```js
    async function returnResults(number) {
      for (let i = 0; i < number; i++) {
        const response = await fetch(`/results?number=${number}`);
        const results = await response.json();
        processResult(results[i]);
      }
    }

    ```
* **将计算任务移到主线程之外**：在前面我们谈到了 JavaScript 通常在主线程上运行任务，长时间的操作可能会阻塞主线程，从而导致 UI 性能很差。我们还展示了如何将长任务分解为较小的任务，以缓解这个问题。处理这类问题的另一种方法是将任务完全移到主线程之外。有几种方法可以实现这一点：
    + 使用异步代码：异步 JavaScript基本上是指不会阻塞主线程的 JavaScript。异步 API 通常用于处理诸如从网络获取资源、访问本地文件系统上的文件或打开用户网络摄像头流等操作。因为这些操作可能需要很长时间，所以在等待它们完成的过程中阻塞主线程是不好的。相反，浏览器会执行这些函数，继续运行后续代码，这些函数将*在将来某个时间点*返回结果。现代异步 API 基于 Promise，这是一种用于处理异步操作的 JavaScript 语言特性。如果你有功能可以从异步运行中受益，则可以编写自己的基于 Promise 的函数。
    + 在 Web Worker 中进行计算：Web Worker 是一种机制，允许你打开一个单独的线程来运行一段 JavaScript 代码，以便不会阻塞主线程。Worker 有一些限制，最大的限制是你不能在 Worker 内部运行 DOM 脚本。你可以执行大多数其他操作，并且 Worker 可以与主线程之间发送和接收消息。Worker 的主要用例是如果你有大量计算需要进行，而不希望它阻塞主线程，那么就需要使用 Worker。在 Worker 中进行计算，等待结果，并在准备好时将结果发送回主线程。
    + **使用 WebGPU**：WebGPU 是一种浏览器 API，允许 Web 开发人员使用底层系统的 GPU（Graphics Processing Unit，图形处理单元）来进行高性能计算和绘制复杂的图像，这些图像可以在浏览器中呈现。它相对复杂，但可以提供比 Web Worker 更好的性能优势。
参见
--

* 优化长任务（web.dev，2022 年）
* Canvas 教程
* 上一页
* Overview: Performance
* 下一页


感知性能 - 学习 Web 开发

感知性能
====

* 上一页
* Overview: Performance
* 下一页

**感知性能 (en-US)** 是用户对网站速度的感受。用户如何看待性能与任何客观统计数据一样重要，甚至更重要，但它是主观的，并且不易测量。感知性能是用户视角，而不是指标。

本文简要介绍了感知性能，着眼于用户的感知，以及可以使用哪些客观工具来衡量这类主观因素。

|  |  |
| --- | --- |
| 前提： |
 基础计算机知识，基本软件安装，
 客户端 web 技术的基础知识
  |
| 目标 | 基本了解用户对 Web 性能的看法。 |

性能是关于用户视角的。How fast a website feels like it's loading and rendering has a greater impact on user experience than how fast the website actually loads and renders. Even if an operation is going to take a long time (because of latency or or inavailability of the main thread), it is possible to keep the user engaged while they wait by showing a loading spinner, or a series of useful hints and tips (or jokes, or whatever else you think might be appropriate). Such an approach is much better than just showing nothing, which will make it feel like it is taking a lot longer and possibly lead to your users thinking it is broken and giving up.

感知性能
----

The perception of how fast your site loads and how responsive feels to user interaction is vitally important; even more important that actual download time but difficult to quantify. There are areas of your site that you may not be able to make faster, but you can make it *feel* faster even if the metrics discussed in the othser sections can't be improved.

There is no unicorn metric that can measure what the user feels, but metrics are useful in guaging improvements (and regressions). Relevant measurements include first meaningful paint (FMP), largest contentful paint (LCP), time to interactive (TTI), render start, DOM interactive, and speed index.

**First paint** is reported by the browser and provides the time, in ms, of when the page starts changing; but this change can be a simple background color update or something even less noticable. It doesn't indicate completeness and may report a time when nothing visible is painted. **First Contentful Paint (en-US) (FCP)** reports the time when the browser first rendered anything of signifigance, be that text, foreground or background image, or a canvas or SVG; capturing the very beginning of the loading experience. But, just because there's content, doesn't mean it's useful content or that the user has content to consume. The **First Meaningful Paint (en-US)**, or FMP, is the when content appears on the screen that is actually meaningful; which is a better metric for user-perceived loading experience, but still not ideal. **Largest contentful paint (LCP)** metric, definited in the Largest Contentful Paint API, reports the render time of the largest content element visible in the viewport.

**Speed index (en-US)** is also used to approximate perceived performance: it measures the average time for pixels on the visible screen to be painted. It doesn't account for jitter, nor does it weight which content important to a user more highly, so it's not a perfect metric.

These metrics have to do with initial load and render. It is also important to ensure the site feels fast once the user begins interacting with it. For this, **time to interactive (en-US)**, is a good metric; it is the moment when the last long task (en-US) of the load process finishes and the UI is available for user interaction with delay.

UI lack or responsiveness and jank both harm perceived performance. Even though a task may take a long time, though, there are ways to make it seem faster. There are several tips to improving perceived performance.

### 提升感知性能

Understanding networking, how the browser works, user perception of time, etc., can help you better understand how to improve the user interaction. However, you don't have to know the ins and outs of how everything, including how the human mind works, to improve the perception of speed.

How fast or slow something feels like it's taking depends a lot on whether the user is actively or passively waiting for this thing to happen. Waits can have an active and passive phase. When the user is active - moving the mouse, thinking, being entertainted, they are in an active phase. The passive phase occurs when the user is passively waiting, like staring at a monochrome screen. If both the passive and active waits time were objectively equal, users would estimate that the passive waiting period was longer than the active. If a load, render, or response time can not be objectively minimized any further, turning the wait into an active wait instead of a passive wait can make it feel faster.

There are tips and tricks to follow. Some of these quick tips have full articles if you want to dive deeper.

Displaying content, or at least some part of the page with an indication that content is loading, as quickly as possible, is essential to improving perceived performance. For example, because page render is blocked by loading and parsing CSS and JavaScript, minimizing the amount of CSS and JS that needs to be loaded on initially will have a major impact on improving perceived performance. Even though the bytes might be the same, not blocking the page from rendering makes the load *feel* faster.

这里有一些技巧有助于提升性能：

### 最小化初始加载

要提升可感知性能，请最小化页面初始加载。换句话说，首先下载将实际显示的所有内容，但仅下载实际使用的内容，然后下载其余内容。因为最终要下载所有资源，所以实际上资源总量并没有改善——实际上还需要增加一些代码。但因为暂不需要的资源被延后加载了，所以用户并不会感知资源量的增加，而会感受到页面加载更快了。

为了最大程度地减少初始加载资源，请从内容中分离交互式功能，以便优先加载初始化时所需的可见内容——文本、样式和图像。延迟加载其余资源。

不要加载初始页面未使用或看不到的图像或脚本，而在页面可用后延时加载，或在需要使用时按需加载。在初始页面加载之后加载其他资源可提高感知性能。在初始请求中加载基本数据，并仅根据需要逐步加载功能部件和数据，有助于改善低带宽和低规格硬件的体验。

此外，你应该优化需加载的资源。图片和视频应以最佳格式、压缩后的大小和正确尺寸进行投放。

### 防止内容跳转和其他重排

Images or other assets causing content to be pushed down or jump to a different location, like the loading of third party advertisements, can make the page feel like it is still loading and is bad for perceived performance. Content reflowing is especially bad for user experience when not initiated by user interaction. If some assets are going to be slower to load than others, with elements loading after other content has already been painted to the screen, plan ahead and leave space in the layout for them so that content doesn't jump or resize, especially after the site has become interactive.

### 避免字体文件延迟

Font use can both help and harm user experience. Selecting the right fonts is an art form, and can greatly improve the user experience. Fonts can also harm user experience, especially if the fonts used need to be imported, and if the importing is not optimal, or if Comic Sans is used (kidding). Flicker of unstyled text and missing text both harm performance.

Make fallback fonts the same size and weight so that when fonts load the page change is less noticeable.

### 交互类元素是可交互的

Make sure visible interactive elements are always interactive and responsive. If input elements are visible, the user should be able to interact with them without a lag. Users feel that something is laggy when they take more than 50ms to react. They feel that a page is janky when content repaints slower than 16.67ms (or 60 frames per second) or repaints at uneven intervals.

Make things like type-ahead a progressive enhancement: use css to display input modal, JS to add typeahead/autocomplete as it is available

### 使任务启动器显得更具交互性

在按下按键而不是等待按键弹起时发出请求，可以使感知的内容加载减少 200 毫秒。在 KEYUP 后添加一个有趣但不显眼的 200 毫秒动画，甚至可以再降低 200 毫秒的加载感知。你并没有节省 400 毫秒的时间，但是用户直到真正等待内容时，才感觉到他们在等待内容。

总结
--

By turning as much of the download, render and wait time into active phases and reducing any passive waiting, even if the objective measurements stay the same, the user will feel like the content downloaded, rendered, and responded more quickly. Now that we know what we should be speeding up, let's take a look at some metrics and learn how we can measure these events.

* 上一页
* Overview: Performance
* 下一页


Web 游戏开发简介 - 游戏开发

Web 游戏开发简介
==========

现代的 web 已经高速发展成为一个可行可靠的平台，它不仅能够用来创建高质量的酷炫游戏，同时也能够用来发布和传播这些游戏。

采用现代网页技术和较新的浏览器，完全有可能做出令人印象深刻的顶级页面游戏。它能够制作的游戏种类可以和桌面端以及原生系统相当。我们这里所说的，并不是很久之前就采用 Flash® 制作出的简单卡牌游戏或者多人社交游戏。而是牛逼的 3D 动作射击游戏，RPG 游戏等等。得益于 JavaScript 实时编译技术性能的大幅提升，以及新开放的 API。在制作运行在浏览器（或者是基于类似 Firefox OS 的 HTML5技术支持的设备）上的游戏时，我们不用妥协。

HTML5 游戏平台
----------

你可以真正地为你的游戏考虑下 Web 来作为更好的目标平台。我们总是喜欢说，"the Web is the platform." 让我们浏览下 Web 平台的核心部分：

| 功能 | 技术 |
| --- | --- |
| **音频** | Web Audio API |
| **图形** | WebGL (OpenGL ES 2.0) |
| **输入** | Touch events, Gamepad API, 设备传感器，WebRTC (en-US), Full Screen API, Pointer Lock API |
| **语言** | JavaScript (或是 C/C++ 使用 Emscripten 来编译成 JavaScript) |
| **网络** | WebRTC (en-US) 和/或 WebSockets |
| **存储** | IndexedDB 或是 "云 (存储)" |
| **Web** | HTML, CSS, SVG, Social API (还有其他很多很多东西！) |

商业案例
----

作为一名游戏开发者，无论你是独立的个人还是大型游戏工作室，你想知道你的下一个游戏项目瞄准 Web 是有意义的。让我们看看 Web 是如何帮到你的。

1. Web 触手可及；它无处不在。如今可以看到，用 HTML5 构建的游戏运行在智能手机，平板，个人电脑和智能电视。
2. 提高营销和曝光度。你不限于在某商店推广你的游戏。相反，你可以像其他媒体一样在 Web 宣传和推广你的游戏，利用网络的固有性和共享性接触新客户。
3. 你可以掌握最重要的事项：支付。你不必交付超过收入的 30% 给他人，仅仅就因为你的游戏在他们的生态系统。相反，你可以管理任何你想要的和使用任何你喜欢的付款处理服务。
4. 拥有更多控制权的是，只要你愿意，你可以随时更新游戏。不必着急等待审核通过，仅当其他公司的某某人决定你的关键 bug 修复是否会在今天或明天交付。
5. 掌握你的数据分析！不必依靠别人作出所有决定，你需要什么分析，你可以收集自己的——或选择你最喜欢的第三方平台，来收集有关你的销售和游戏产生的信息。
6. 你可以用你的方式更密切地管理你的客户关系。再也不用苦苦等待只能通过应用商店有限的机制来过滤客户的反馈。用你想要的方式与客户交流，没有中间人。
7. 你的玩家可以随时随地玩你的游戏。因为 Web 是无处不在的，你的顾客可以在手机，平板，家庭手提，个人电脑或其他设备上关注游戏动态。
针对游戏开发者的 Web 技术
---------------

技术同行们，让我们发掘出所有关于 Web 的 APIs，将它们呈现给所有的游戏开发者们。下面是一个比较完整的列表，可以一窥 Web 究竟能够做些什么：

Full Screen API

这个简单的 API 能够让你的游戏占据整个屏幕，从而使玩家沉浸在动作中

Gamepad API

如果你想你的用户能够使用游戏手柄或其他游戏控制器来控制游戏，你需要这个 API

HTML and CSS

二者合璧，可以构建，设计并对你的游戏界面布局，HTML 有一个提供 2D 图形的元素，即`<canvas>`

HTML audio

`<audio>` 元素可以用来播放一些简单的音效和音乐。如果你想要更多的参与，可以学习Web Audio API 来深入了解音频处理的力量！

IndexedDB

一个强大的数据存储 API，用来在电脑或者设备上保存用户自己的数据。一个很好的方法用来保存游戏的状态和其他最近的信息，这样在需要的时候不用每次重新下载。也可以用来让你的游戏即使用户没有链接到网络也能继续玩（例如在飞机上的数小时）。

JavaScript

JavaScript 是网络上使用的编程语言，在现代浏览器中正在快速发展，而且一直在快速发展。使用它的力量为你的游戏编写代码，或者使用Emscripten或Asm.js等技术轻松移植你现有的游戏。

Pointer Lock API

指针锁定 API 允许你在游戏界面中锁定鼠标或其他指针设备，以便你不用绝对定位光标就可以获得坐标变化值，从而准确地判断用户正在做什么，并且还可以防止用户意外地进入另一块屏幕或别的什么地方，从而导致误操作。

SVG (可缩放矢量图形)

无论用户显示器的大小或分辨率如何，都可以构建平滑缩放的矢量图形。

Typed Arrays

JavaScript 中的类型数组可以让你访问原始二进制数据；这使你可以操纵 GL 纹理，游戏数据或其他任何东西，即使它不是原生 JavaScript 数据格式。

Web Audio API

这个 API 用于控制 JavaScript 代码中的音频的回放，合成和处理，使你可以创建出色的音效，以及实时播放和操作音乐。

WebGL

允许你从 Web 内容创建高性能，硬件加速的 3D（和 2D）图形。这是一个 Web 支持的OpenGL ES 2.0 实现。

WebRTC (en-US)

WebRTC（实时通信）API 使你能够控制音频和视频数据，包括远程会议以及两个用户之间来回传输其他应用程序数据。希望你的玩家能够在殴打怪物的同时互相交流？这是你的 API，快使用它吧。

WebSockets

WebSocket API 使你可以将你的应用程序或站点连接到服务器，实时传输数据。构建完美的多人游戏动作，聊天服务等必备。

Web Workers

Workers API 能够让你生成运行 JavaScript 代码的后台线程，以充分利用现代的多核 CPU。

XMLHttpRequest 和 File API

XMLHttpRequest 和 File API 的组合使你可以从 Web 服务器发送和接收任何类型的数据。比如下载新的游戏关卡，文件，以及传递非实时游戏状态信息等。


游戏开发技术 - 游戏开发

游戏开发技术
======

这个页面为想要使用开放的 web 技术来开发游戏的人列举出了必要的核心技术。

使用 asm.js 中的异步脚本

尤其在制作中大型游戏时，异步脚本是一项必备技术，它可以让游戏的 JavaScript 脱离主线程进行编译，并在以后运行游戏时缓存起来，从而显著提高用户的性能。本文将介绍如何使用异步脚本。

优化启动性能

如何确保游戏启动快速、流畅，并且不会占用用户的浏览器或设备太久。

使用 WebRTC 点对点数据通道 (en-US)

除了支持音频和视频通信外，WebRTC 还能建立点对点数据通道，在播放器之间主动交换文本或二进制数据。本文将解释 WebRTC 的功能，并介绍如何使用库来轻松实现这一功能。

Web 游戏音频 (en-US)

音频是任何游戏的重要组成部分——它能增加反馈，活跃气氛。基于 web 的音频技术正在迅速成熟，但仍有许多浏览器差异需要解决。本文提供了实现 web 游戏音频的详细指南，尽可能广泛地介绍了目前在各种平台上都能使用的音频。

2D 碰撞检测

简明介绍 2D 游戏中的碰撞检测。

平铺贴图 (en-US)

在 2D 游戏中，贴图是一种非常流行的构建游戏世界的技术。这些文章介绍了平铺贴图是什么，以及如何使用 Canvas API 实现平铺贴图。