<input type="url"> - HTML（超文本标记语言）

<input type="url">
==================

**`url`** 类型的 `<input>` 元素用来让用户输入和编辑 URL。

尝试一下
----

所输入的值在表单提交前会自动经过验证，以确认它为空，或为一个合法的 URL 格式。`:valid` 和 `:invalid` CSS 伪类会适当地自动应用，以在视觉上表示当前值是否为一个合法的 URL 值。

对于不支持类型为 `url` 的输入框的浏览器，它会回退为标准的 text 输入框。

|  |  |
| --- | --- |
| **值** | 代表 URL 的字符串，或为空 |
| **事件** | `change` 和 `input` |
| **支持的通用属性** | `autocomplete`、`list`、`maxlength`、`minlength`、`pattern`、`placeholder`、`readonly`、`required` 和 `size` |
| **IDL 属性** | `list`、`value`、`selectionEnd` 和 `selectionDirection` |
| **DOM 接口** | `HTMLInputElement` |
| **方法** | `select()`、`setRangeText()` (en-US) 和 `setSelectionRange()`。 |

值
-

`<input>` 元素的 `value` 属性包含了自动验证为 URL 格式的字符串。具体来说，有两种可能的格式可以通过验证：

1. 一个空字符串（""），代表用户没有输入任何值，或者有值被移除。
2. 一个正确的绝对 URL 格式。这并不代表 URL 所指地址实际存在，但至少要符合格式。简单来说，要类似于 `urlscheme://restofurl`。

查看验证小节，以了解 URL 如何经过验证来确保格式正确性。

附加属性
----

除了所有 `<input>` 元素上共有的可操作的属性外，`url` 输入框还支持以下属性：

### list

`list` 属性指定了一个 `<datalist>` 元素的 id，用来提供输入建议。`<datalist>` 提供了一个预定义的值列表，向用户建议这个输入。列表中任何与 `type` 不兼容的值都不包括在建议选项中。所提供的值是建议，不是要求：用户可以从这个预定义的列表中选择，或者提供不同的值。

### maxlength

用户可以输入到 `url` 输入框中的最大字符数（以 UTF-16 代码单元为单位）。必须为大于等于 0 的整数。如果未指定 `maxlength` 或指定了无效的值，则 `url` 输入框将没有最大值。这个值也必须大于等于 `minlength` 的值。

如果文本框中的字符数大于 `maxlength` UTF-16 编码单元长度，则输入将无法通过约束验证。约束验证仅作用于用户修改输入值的时候。

### minlength

用户可以输入到 `url` 输入框中的最小字符数（以 UTF-16 代码单元为单位）。该值必须是小于等于 `maxlength` 指定的值的非负整数值。如果未指定 `minlength` 或指定了无效的值，则 `url` 输入将没有最小值。

如果输入到字段中的文本的长度小于 `minlength` UTF-16 代码单元的长度，则输入将无法通过约束验证。约束验证仅作用于用户修改输入值的时候。

### pattern

如果指定了 `pattern` 属性，为了使 `value` 通过约束验证，必须满足该属性给定的正则表达式。它必须是 `RegExp` 类型的有效 JavaScript 正则表达式，并且已在我们的正则表达式指南中进行了说明；在编译正则表达式时指定了 `'u'` 标志，因此该模式被视为 Unicode 代码点的序列，而不是 ASCII。模式文本周围无需指定正斜杠。

如果模式未指定或无效，则不应用任何正则表达式，并且将完全忽略此属性。

**备注：** 使用 `title` 属性指定大多数浏览器将显示为文本的工具提示，以说明与模式匹配的要求。你还应该在附近添加其他说明性文字。

参见模式验证小节以获取细节和例子。

### placeholder

`placeholder` 属性是一个字符串，可向用户提供有关该字段中需要什么样的信息的简短提示。它应该是一个单词或短语来说明预期的数据类型，而不是说明性消息。文本中*不得*包含回车符或换行符。

如果控件的内容具有方向（LTR 或 RTL），但需要以相反的方向显示占位符，则可以使用 Unicode 双向算法来格式化字符，从而覆盖原有占位符的方向；请参见如何针对双向文本使用 Unicode 控制符获取更多信息。

**备注：** 尽可能避免使用 `placeholder` 属性。它在语义上没有其他解释表单的方式有用，并且可能导致内容出现意外的问题。请参见 `<input>` 标签以获取更多信息。

### readonly

一个布尔属性，如果存在，则表示该字段不能由用户编辑。但是，仍可以通过 JavaScript 代码直接设置 `HTMLInputElement` 的 `value` 属性来更改。

**备注：** 因为只读字段不能有值，所以 `required` 对指定了 `readonly` 属性的输入没有任何影响。

### size

`size` 属性是一个数字值，指示输入字段应有多少个字符宽。该值必须是一个大于零的数字，默认值是 20。由于字符宽度各不相同，这可能是也可能不是精确的，不应依赖于此；结果输入可能比指定的字符数更窄或更宽，这取决于字符和字体（`font` 使用中的设置）。

这并*不*限制用户可以在该字段中输入多少个字符，仅能指定一次可以看到多少个。要设置输入数据长度的上限，请使用 `maxlength` 属性。

### spellcheck

`spellcheck` 是一个全局属性，用于指示是否启用元素的拼写检查。它可以用于任何可编辑的内容，但是这里我们考虑在 `<input>` 元素上使用 `spellcheck` 的细节。 `spellcheck` 的允许值为：

`false`

禁用此元素的拼写检查。

`true`

对此元素启用拼写检查。

`""`（空字符串）或空值

遵循元素的默认行为进行拼写检查。这可能基于父元素 `spellcheck` 设置或其他因素。

如果输入字段没有设置 readonly 属性且未禁用，则可以启用拼写检查。

如果用户代理的首选项覆盖了设置，则通过读取 `spellcheck` 返回的值可能无法反映控件中拼写检查的实际状态。

非标准属性
-----

以下非标准属性在某些浏览器上也可用。一般来说，除非万不得已，否则你应该避免使用它们。

### autocorrect

Safari 扩展，`autocorrect` 属性是一个字符串，它指示在用户编辑此字段时是否激活自动更正。允许的值为：

`on`

启用拼写错误的自动更正，以及配置文本替换（如果已配置）的处理。

`off`

禁用自动更正和文本替换。

### mozactionhint

Mozilla 扩展，它提供了一个提示，提示用户在编辑字段时按 `Enter` 或 `Return` 键将采取何种操作。

此属性已经废弃：请使用全局属性 `enterkeyhint` 作为替代。

使用 URL 输入框
----------

当你用适当的 `type` 值 `url` 创建一个 URL 输入时，所输入的文本会得到自动验证，它们至少可以形成一个合法的 URL。可以避免用户输入错误的网站地址，或者提供一个无效的地址。

然而，重要的是，这并不足以确保指定的文本是一个实际存在与网站的用户相对应的 URL，或在任何其他方面可以接受。它只是确保该字段的值被正确格式化为一个 URL。

**备注：** 用户可以在背后改动你的 HTML 代码，所以你的站点*不能*依赖此验证提供安全性。*必须*在服务端验证用户所输入的 URL，以确认用户提供的文本不会产生任何额外的安全隐患。

### 简单的 URL 输入框

目前，所有浏览器都将此元素实现为带有基本认证特性的标准文本输入框。对于其最基本的形式，一个 URL 输入框看起来像这样：

```html
<input id="myURL" name="myURL" type="url" />
```

请注意，当输入为空或输入一个有效格式的 URL 地址时，认为输入是有效的，但在其他情况下则不认为有效。通过添加 `required` 属性，只允许正确格式的 URL；当输入为空时不再被视为有效。

这里没什么特别的，提交这个表单会将 `myURL=http%3A%2F%2Fwww.example.com` 数据提交至服务器，注意其中的字符是如何进行必要的转义的。

### 占位符

有时，提供一个上下文提示，说明输入数据应该采取什么形式，是很有帮助的。如果页面设计没有为每个 `<input>` 提供描述性的标签，这一点就特别重要。这就是**占位符**的作用。占位符是一个值，它通过展示一个有效值的例子来证明 `value` 应该采取的形式，当元素的 `value` 为 "" 时，它将显示在编辑框内。一旦数据被输入框中，占位符就会消失；如果框被清空，占位符会重新出现。

在这里，我们有一个具有 `http://www.example.com` 占位符的 `url` 输入框。注意在编辑输入框的值的时候，占位符如何出现和消失。

```html
<input
 id="myURL"
 name="myURL"
 type="url"
 placeholder="http://www.example.com" />
```

### 控制输入框大小

不仅可以控制输入框的物理大小，还可以控制允许输入的最大与最小文字的长度。

#### 物理元素大小

输入框的物理尺寸可以用 `size` 属性来控制。通过它，你可以指定输入框一次可以显示的字符数。例如，在这个例子中，`url` 编辑框的宽度为 30 个字符：

```html
<input id="myURL" name="myURL" type="url" size="30" />
```

#### 元素值长度

输入框的 `size` 是与输入的 URL 本身的长度限制分开的。你可以使用 `minlength` 属性为输入的 URL 指定一个最小长度，以字符为单位；同样，使用 `maxlength` 来设置输入 URL 的最大长度。如果 `maxLength` 超过 `size`，输入框的内容将根据内容操作需要滚动，以显示当前的选择或插入点。

下面的例子创建了一个 30 个字符宽的 URL 地址输入框，要求内容不能短于 10 个字符，不能长于 80 个字符。

```html
<input
 id="myURL"
 name="myURL"
 type="url"
 size="30"
 minlength="10"
 maxlength="80" />
```

**备注：** 这些属性也影响验证；小于或大于指定的最小值或最大值的长度将会判定为不合法；而且大部分浏览器也会拒绝让用户输入长度大于指定限制的值。

### 提供默认值

#### 使用 value 属性提供单个默认值

你总是可以通过设置 `value` 属性来为 `url` 输入框提供一个默认值：

```html
<input id="myURL" name="myURL" type="url" value="http://www.example.com" />
```

#### 提供一系列建议的值

让我们更进一步，你可以通过指定 `list` 属性来为用户提供一系列默认的选项。这并不使得用户限制在这些选项中，且允许他们更快选择常用的 URL 地址。它也为 `autocomplete` 提供提示。`list` 属性指定了 `<datalist>` 的 ID，对于每个可用的选项，包含一个 `<option>` 元素；每个 `option` 的 `value` 是 URL 输入框的相应建议值。

```html
<input id="myURL" name="myURL" type="url" list="defaultURLs" />

<datalist id="defaultURLs">
  <option value="https://developer.mozilla.org/"></option>
  <option value="http://www.google.com/"></option>
  <option value="http://www.microsoft.com/"></option>
  <option value="https://www.mozilla.org/"></option>
  <option value="http://w3.org/"></option>
</datalist>
```

有了 `<datalist>` 元素及其 `<option>`，浏览器将提供指定的值作为 URL 的潜在值；这通常以弹出式或下拉式菜单的形式呈现，其中包含建议。虽然具体的用户体验可能因浏览器而异，但通常情况下，点击编辑框会出现一个下拉的建议 URL。然后，随着用户的输入，列表被调整为只显示匹配的值。每个键入的字符都会缩小列表的范围，直到用户做出选择或键入一个自定义值。

#### 为建议值使用 label

你可以选择在一个或所有的 `<option>` 元素上包含 `label` 属性来提供文本标签。一些浏览器可能只显示标签，而其他浏览器可能同时显示标签和 URL。

```html
<input id="myURL" name="myURL" type="url" list="defaultURLs" />

<datalist id="defaultURLs">
  <option value="https://developer.mozilla.org/" label="MDN Web Docs"></option>
  <option value="http://www.google.com/" label="Google"></option>
  <option value="http://www.microsoft.com/" label="Microsoft"></option>
  <option value="https://www.mozilla.org/" label="Mozilla"></option>
  <option value="http://w3.org/" label="W3C"></option>
</datalist>
```

验证
--

针对 `url` 输入，有两种等级的校验方式。首先，针对每一个 `<input>` 元素，有一种标准级别的校验方式，以验证其中的内容是一个合法的 URL。其次，你也可以额外添加一些过滤器，来满足自己的需求。

**警告：** HTML 表单验证*不能*替代服务端代码，以确保输入的数据格式正确。对于某人来说，对 HTML 进行调整以使其绕过验证或完全删除验证太容易了。有人也可以完全绕开你的 HTML 并将数据直接提交到你的服务器。如果服务器端代码无法验证接收到的数据，则当将格式不正确的数据（或太大、类型错误的数据等等）输入数据库时，灾难可能会发生。

### 基本验证

支持 `url` 输入类型的浏览器会自动提供验证，以确保只有符合标准格式的 URL 文本被输入到输入框。

URL 的语法是相当复杂的。它由 WHATWG 的 URL 动态标准定义，并在我们的文章什么是 URL？中为初学者做了描述。

### 使 URL 必填

如前所述，要使一个 URL 表单项在表单提交前成为必填项（不能让该字段空白），只需要在输入项上包含 `required` 属性即可。

```html
<form>
  <input id="myURL" name="myURL" type="url" required />
  <button>提交</button>
</form>
```

试试看，不输入任何值的时候提交上面的表单会发生什么。

### 模式验证

如果你需要进一步限制输入的 URL，不仅仅需要“任何看起来像 URL 的字符串”，你可以使用 `pattern` 属性来指定一个正则表达式，该值必须与之匹配才有效。

例如，假设你正在为 Myco 公司的员工建立一个支持页面，如果他们的某个页面出现问题，他们可以联系 IT 部门寻求帮助。在我们的简化表单中，用户需要输入出现问题的页面的 URL，以及描述问题所在的信息。但我们希望只有当输入的 URL 在 Myco 域中时，才能成功地验证该 URL。

由于 `url` 类型的输入可同时进行标准的 URL *和*指定的 `pattern` 进行验证，你可以轻松实现。让我们来看看怎么做：
```

div {
  margin-bottom: 10px;
  position: relative;
}

input[type="number"] {
  width: 100px;
}

input + span {
  padding-right: 30px;
}

input:invalid + span::after {
  position: absolute;
  content: "✖";
  padding-left: 5px;
}

input:valid + span::after {
  position: absolute;
  content: "✓";
  padding-left: 5px;
}
```

```html
<form>
  <div>
    <label for="myURL">输入出问题的网址：</label>
    <input
 id="myURL"
 name="myURL"
 type="url"
 required
 pattern=".\*\.myco\..\*"
 title="URL 必须属于 myco 域" />
    <span class="validity"></span>
  </div>
  <div>
    <label for="myComment">出了什么问题？</label>
    <input id="myComment" name="myComment" type="text" required />
    <span class="validity"></span>
  </div>
  <div>
    <button>提交</button>
  </div>
</form>
```

首先，表单指定了 `required` 属性，这意味着必须输入合法的 URL 地址。

其次，我们设定 `url` 输入框必须满足的 `pattern` 为 `".*\.myco\..*"`，这个简单的正则表达式要求任意长度的字符串，跟随一个点号，再跟随“myco”，再跟随一个点号，再跟随任意长度的字符串。由于浏览器对给定文字同时运行标准的 URL 过滤器和我们自定义的模式文字过滤器，以上两层认证方法可合并为“确保这是一个合法的 Myco 域 URL”。

这并不完美，但足以满足演示场景的需求。

建议在使用 `pattern` 属性的同时使用 `title`。如果你这样做，`title` 必须描述模式；它应该解释数据应该采取什么格式，而不是任何其他信息。这是因为 `title` 可以作为验证错误信息的一部分被显示或读出。例如，浏览器可能会显示”请与请求的格式相匹配。“然后跟随指定的 `title` 。如果你的 `title` 是类似于”URL“的东西，结果就是“请与请求的格式相匹配。URL”，用户体验并不好。

这就是为什么我们要指定字符串“URL 必须属于 myco 域”的原因。通过这样做，产生的完整错误信息可能是这样的：“请与请求的格式匹配。URL 必须属于 myco 域”。

**备注：** 如果你在编写验证正则表达式时遇到麻烦，它们不能正常工作，请检查你的浏览器控制台；那里可能有有用的错误信息来帮助你解决问题。

示例
--

关于 `url` 类型的输入没有太多可说的地方了；请查看模式验证和使用 URL 输入框部分的大量例子。

你也可以找到我们的 GitHub 上的模式验证例子（试试在线运行它）。

规范
--

| Specification |
| --- |
| HTML Standard # url-state-(type=url) |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* HTML 表单指南
* `<input>`
* `<input type="tel">`
* `<input type="email">`
* CSS 属性兼容性表格


<input type="submit"> - HTML（超文本标记语言）

<input type="submit">
=====================

**`submit`** 类型的 `<input>` 元素会渲染为按钮。当 `click` 事件发生时（用户点击按钮是一个典型的点击事件）， 用户代理 尝试提交表单到服务器。

|  |  |
| --- | --- |
| **值** | 展示在按钮标签内字符串 |
| **事件** | `click` |
| **支持的公共属性** | `type` 和 `value` |
| **IDL 属性** | `value` |
| **DOM 接口** | `HTMLInputElement` |
| **方法** | 无 |

值
-

`<input type="submit">` 元素的 `value` 属性包含一个展示在按钮标签上的字符串。如果没有指定 value 属性，按钮就不会拥有真值。

### 设置 value 属性

```html
<input type="submit" value="Send Request" />
```

### 省略 value 属性

如果没有指定一个 `value` 值，按钮将会有一个通过用户代理选择的默认标签值。这个标签可能是“提交”或者“提交查询”，这里有一个默认 label 的提交按钮：

```html
<input type="submit" />
```

额外属性值
-----

除了所有 `<input>` 元素共享的属性外，`submit` 提交按钮输入还支持以下属性：

### formaction

一个字符串，指示要将数据提交到的 URL。这优先于拥有 `<input>` 的 `<form>` 元素上的 `action` 属性。

此属性在 `<input type="image">` 元素和 `<button>` 元素上也可用。

### formenctype

一个字符串，标识在将表单数据提交到服务器时要使用的编码方法。有三个允许的值：

`application/x-www-form-urlencoded`

这是默认值，它使用 `encodeURI()` 之类的算法对文本进行 URL 编码后，以字符串形式发送表单数据。

`multipart/form-data`

使用 `FormData` API 来管理数据，从而允许将文件提交到服务器。如果表单包含 `type` `file` (`<input type="file">`) 的任何 `<input>` 元素，则必须使用此编码类型。

`text/plain`

纯文本；通常仅对调试有用，调试人员可以轻松查看要提交的数据。

如果指定了 `formenctype` 属性，则该属性的值将覆盖所属表单的 `action` 属性。

此属性在 `<input type="image">` 元素和 `<button>` 元素上也可用。

### formmethod

一个字符串，指示提交表单数据时要使用的 HTTP 方法；此值将覆盖所属表单上给出的任何 `method` 属性。允许的值为：

`get`

URL 通过 `formaction` 或 `action` 属性给出的 URL 开始，加上一个问号（“?”）字符，然后加上表单的数据，按照 `formenctype` 或表单的`enctype` 属性的描述进行编码。然后使用 HTTP `get` 请求将这个 URL 发送到服务器。这种方法对于只包含 ASCII 字符且没有副作用的简单表单很有效。这是默认值。

`post`

表单的数据包含在请求的正文中，该请求的正文使用 HTTP `post` 请求发送到由 `formaction` 或 `action` 属性指定的 URL 请求。此方法支持复杂的数据和文件附件。

`dialog`

此方法用于指示按钮仅关闭与输入关联的对话框，而根本不传输表单数据。

此属性在 `<input type="image">` 元素和 `<button>` 元素上也可用。

### formnovalidate

布尔属性，如果存在，则指定在提交给服务器之前不应对表单进行验证。这将覆盖所属表单上的 `novalidate` 属性的值。

此属性在 `<input type="image">` 元素和 `<button>` 元素上也可用。

### formtarget

一个字符串，它指定一个名称或关键字，该名称或关键字指示提交表单后在何处显示收到的响应。该字符串必须是**浏览上下文**的名称（即选项卡、窗口或 `<iframe>`）。此处指定的值将覆盖 `<form>` 上拥有此输入的 `target` 属性。

除了选项卡、窗口或内联框架的实际名称之外，还可以使用一些特殊的关键字：

`_self`

将响应加载到与包含表单的浏览上下文相同的浏览上下文中。这将用接收到的数据替换当前文档。如果未指定，则使用默认值。

`_blank`

将响应加载到新的未命名浏览上下文中。这通常是一个与当前文档相同的窗口中的新选项卡，但是根据用户代理的配置可能会有所不同。

`_parent`

将响应加载到当前浏览器的父浏览上下文中。如果没有父上下文，则其行为与 `_self` 相同。

`_top`

将响应加载到顶级浏览上下文中；这是浏览上下文，它是当前上下文的最高级祖先。如果当前上下文是最顶层的上下文，则其行为与 `_self` 相同。

此属性在 `<input type="image">` 元素和 `<button>` 元素上也可用。

使用提交按钮
------

`<input type="submit">` 按钮用于提交表单。如果你想要创建一个自定义的按钮，并通过 JavaScript 自定义按钮的行为，你需要使用 `<input type="button">`，或者，使用更优解的 `<button>` 元素。

如果你选择使用 `<button>` 在表单里创建按钮，请记住：如果 `<form>` 中只有一个 `<button>`，那个按钮将被视为“提交”按钮。你应该随时保证指定哪一个是提交按钮的习惯。

### 一个简单的提交按钮

我们将通过创建含有一个简单的提交按钮的表单来开始：

```html
<form>
  <div>
    <label for="example">我们来提交一些文字吧！</label>
    <input id="example" type="text" name="text" />
  </div>
  <div>
    <input type="submit" value="发送" />
  </div>
</form>
```

渲染效果如下：

试着输入一些文字到文本字段，然后提交表单。

在提交时，键值对数据将会被发送到服务端。在此列子中，字符串将会是 `text=usertext`，其中“usertext”是用户输入的字符，它转换为保留的特殊字符。数据提交的方式和位置都由 `<form>` 的设置决定，参阅发送表单数据文章以获取更多细节。

### 添加提交的快捷键

键盘快捷键就是使用键盘上的按键，让用户使用按键或组合按键来触发按钮。为了在提交按钮上添加键盘快捷键——就像使用任何有实际意义的 `<input>` 一样——可以使用 `accesskey` 全局属性。

在此示例中，将 `s` 指定为访问键（你需要按下键盘上的 `s` 键加上浏览器或操作系统组合的特定修饰键）。为了避免与用户代理自身的键盘快捷键冲突，请使用不同的修饰键，而不是主机上其他快捷方式的访问键。有关更多详细信息，参见 `accesskey`。

这是添加了 `s` 访问键的前一个示例：

```html
<form>
  <div>
    <label for="example">我们来提交一些文字吧！</label>
    <input id="example" type="text" name="text" />
  </div>
  <div>
    <input type="submit" value="发送" accesskey="s" />
  </div>
</form>
```

例如，在 Mac 版 Firefox 中，按 `Control`-`Option`-`S` 会触发“发送”按钮，而 Windows 上的 Chrome 使用 `Alt`+`S`。

上面示例的问题是用户将不知道访问键是什么！尤其如此，因为修饰符通常是非标准的以避免冲突。在构建网站时，请确保以不干扰网站设计的方式提供此信息（例如，通过提供易于访问的链接来指向有关网站访问键的信息）。向按钮添加工具提示（使用 `title` 属性）也可以有所帮助，尽管这并不是出于无障碍目的的完整解决方案。

### 禁用和启用提交按钮

要禁用提交按钮，只需在其上指定 `disabled` 全局属性，就像这样：

```html
<input type="submit" value="发送" disabled />
```

可以在运行时通过简单地将 `disabled` 设置为 `true` 或 `false` 来启用和禁用按钮。在 JavaScript 中，这看起来像 `btn.disabled = true` 或 `btn.disabled = false`。

**备注：** 有关启用和禁用按钮的更多想法，请参见 `<input type="button">` 。

验证方式
----

提交按钮不参与约束验证；他们没有用于约束的真实值。

例子
--

我们在上面提供了一些简单的示例。关于提交按钮，实际上没有什么可说的。有时将这种控件称为“简单按钮”是有原因的。

规范
--

| Specification |
| --- |
| HTML Standard # submit-button-state-(type=submit) |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* `<input>` 元素和实现它的 `HTMLInputElement` 接口。
* 表单与按钮
* HTML 表单
* `<button>` 元素
* CSS 属性兼容性列表


<input type="month"> - HTML（超文本标记语言）

<input type="month">
====================

类型为 **`month`** 的 `<input>` 可以让你容易地创建一个方便输入年份或月份的一个 `<input>`。输入的值是一个经过“`YYYY-MM`”格式化的字符串，其中 `YYYY` 是四位数的年份，而 `MM` 是月份的数值表示。

尝试一下
----

通常来说控件的 UI 界面因浏览器的不同而有变化，到目前为止此控件还不被所有浏览器支持，在桌面浏览器中只有 Chrome/Opera 和 Edge 支持；在移动端被大部分现代浏览器所支持。在不支持的浏览器中，这个控件会被优雅的降级到 `<input type="text">`，不过对输入的文字会有自动的验证，保证它按照预期进行格式化。

对于那些使用不支持 `month` 控制器的浏览器用户，以下的截图呈现了 Chrome 和 Opera 的月份控制器。单击右侧的向下箭头会显示日期选择器，以便选择年份和月份。

![Chrome 浏览器的月份控制器](/zh-CN/docs/Web/HTML/Element/input/month/month-control-chrome.png)

Edge 的 `month` 控制器看起来像这样的：

![Edge 浏览器的月份控制器](/zh-CN/docs/Web/HTML/Element/input/month/month-control-edge.png)

|  |  |
| --- | --- |
| **值** | 代表月份和年份的字符串，或为空字符串 |
| **事件** | `change` 和 `input` |
| **支持的共有属性** | `autocomplete`、`list`、`readonly` 和 `step` |
| **IDL 属性** | `value` |
| **DOM 接口** | `HTMLInputElement` |
| **方法** | `select()`、`stepDown()` (en-US) 和 `stepUp()` (en-US) |

值
-

一个以 YYYY-MM 形式（4 个或更多位数的年，接一个连字符 `-`，再接一个两位数月份）表示月份和年份的字符串。月份字符串的格式在月份字符串中有描述。

### 设置默认值

你可以通过在 `value` 属性中包含月份和年份来设置该输入控件的默认值，像这样：

```html
<label for="bday-month">你在哪个月出生？</label>
<input id="bday-month" type="month" name="bday-month" value="2001-06" />
```

需要注意的是显示的如期格式不同于实际的 `value` ；大部分用户代理基于操作系统，根据合适的本地化形式显示月份和年份，而日期的 `value` 总是会格式化为 `yyyy-MM`。

在向服务器提交上述值的时候它们看起来像这样：`bday-month=1978-06`。

### 通过 JavaScript 代码设置 value

当然你也可以使用 JavaScript 代码通过 `HTMLInputElement.value` 属性来获取或设置日期的值。例如：

```html
<label for="bday-month">你在哪个月出生？</label>
<input id="bday-month" type="month" name="bday-month" />
```

```js
const monthControl = document.querySelector('input[type="month"]');
monthControl.value = "2001-06";
```

其他属性
----

除了 `<input>` 元素的共有属性外，month 输入还提供以下属性：

### list

列表属性的值是位于同一文档中的 `<datalist>` 元素的 `id`。`<datalist>` 提供了一个预定义的值列表，向用户建议这个输入。列表中任何与 `type` 不兼容的值都不包括在建议选项中。所提供的值是建议，不是要求：用户可以从这个预定义的列表中选择，或者提供不同的值。

### max

使用在值章节中讨论的字符串格式指定的所接受的最大年份和月份。如果输入到该元素的 `value` 超过了这个，则该元素无法通过约束验证。如果 `max` 属性的值不是格式为 `yyyy-MM` 的有效字符串，则该元素没有最大值。

该值必须晚于或等于 `min` 属性所指定的年份—月份对。

### min

使用在值章节中讨论的字符串格式指定的所接受的最小年份和月份。如果输入到该元素的 `value` 小于这个，则该元素无法通过约束验证。如果 `min` 属性的值不是格式为 `yyyy-MM` 的有效字符串，则该元素没有最小值。

该值必须早于或等于 `max` 属性所指定的年份—月份对。

### readonly

一个布尔属性，如果存在，则表示该字段不能由用户编辑。但是，仍可以通过 JavaScript 代码直接设置 `HTMLInputElement.value` 属性来更改。

**备注：** 因为只读字段不能有值，所以 `required` 对指定了 `readonly` 属性的输入没有任何影响。

### step

`step` 属性指定了值必须满足的粒度，或者是下文描述的特殊值 `any`。值必须满足基础的步进值，才有效。如果指定了 `min` 属性，则由 `min` 属性决定，否则，使用 `value` 属性的值，如果上述两个值都不存在，则提供适当的默认值。

字符串值 `any` 意味着不使用步进值，任意值都可以接受（除其他制约因素如 `min` 或 `max` 之外）。

**备注：** 当用户输入的数据不符合步进配置时，用户代理可能会四舍五入到最近的有效值，当有两个同样接近的选项时，更倾向于正方向的数字。

对于 `month` 输入，`step` 的值以月份为单位，缩放因子为 1（基础数值也是以月份为单位的）。`step` 的默认值为 1，表示 1 个月。

使用 month 输入
-----------

与日期相关的输入（包含 `month`）乍一看很方便：它们提供了一个简单的用户界面来选择日期，并且它们将发送到服务器的数据格式规范化，无论用户的本地化配置如何。但是，`<input type="month">` 还是存在兼容性问题，大多数主流浏览器还没有支持它。

我们先看看 `<input type="month">` 的基础和高级的用法，然后再讨论有关缓解浏览器支持问题的建议（请参阅处理浏览器支持问题）。

### 基本使用方法

最简单的 `<input type="month">` 涉及到基础的 `<input>` 和 `<label>` 的元素组合，像下面这样：

```html
<form>
  <label for="bday-month">你在哪个月出生？</label>
  <input id="bday-month" type="month" name="bday-month" />
</form>
```

### 设置最大和最小日期

你可以使用 `min` 和 `max` 属性来限制用户选择日期的范围。在下列的例子中我们指定最小月份 `1900-01` 和最大月份 `2013-12`：

```html
<form>
  <label for="bday-month">你在哪个月出生？</label>
  <input
 id="bday-month"
 type="month"
 name="bday-month"
 min="1900-01"
 max="2013-12" />
</form>
```

结果是这样：

* 只有从 1900 年的 1 月到 2013 年的 12 月的月份才可以选择，在这个控件里这个范围以外的月份不能滚动选择。
* 取决于你使用的浏览器，你可能会发现不能在月份选择器中选择在所指定范围之外的月份（如 Edge），或显示不合法（参见验证小节）但仍然可选（例如 Chrome）。
### 控制输入大小

`<input type="month">` 不支持诸如 `size` 的表单大小属性，你必须依靠 CSS 来确定大小。

验证
--

默认情况下，`<input type="month">` 不会对输入的值应用任何验证，用户界面实现会屏蔽所有非日期值的输入，尽管这很有用，但是仍然无法完全依赖于该值的合法性。你仍然可以不填入任何值而提交，或输入不合法的日期值（如 4 月 32 日）。

为了避免这种情况，你可以使用 `min` 和 `max` 来限制可用的日期（参见设置最大和最小日期），并使用 `required` 属性令日期必填。在支持的浏览器中，当你尝试提交超出范围的日期，或空日期值时，会显示错误信息。

我们来看一个例子，这里我们设定了最小和最大的日期，并令该字段必填：

```html
<form>
  <div>
    <label for="month"> 你愿意在哪个月拜访（6 月至 9 月）？ </label>
    <input
 id="month"
 type="month"
 name="month"
 min="2022-06"
 max="2022-09"
 required />
    <span class="validity"></span>
  </div>
  <div>
    <input type="submit" value="提交表单" />
  </div>
</form>
```

如果你尝试在未指定月份或年份，或指定超出给定范围的日期情况下提交表单，浏览器会显示错误。请试试以下实时演示：

对于那些没有使用支持的浏览器的人们来说，以下是该错误的屏幕截图：

![Chrome 浏览器中的 Month 控件必填提示](/zh-CN/docs/Web/HTML/Element/input/month/month-required.png)

下面是上述例子中使用的 CSS。这里我们使用了 `:valid` 和 `:invalid` 这两个 CSS 属性，根据当前值是否有效来为输入添加样式。我们不得不把图标放在 input 旁边的 `<span>` 上，而不是放在 input 框本身，因为在 Chrome 中，生成的内容被放在表单控件里面，无法有效地进行样式设计或显示。

```css
div {
  margin-bottom: 10px;
  position: relative;
}

input[type="number"] {
  width: 100px;
}

input + span {
  padding-right: 30px;
}

input:invalid + span::after {
  position: absolute;
  content: "✖";
  padding-left: 5px;
}

input:valid + span::after {
  position: absolute;
  content: "✓";
  padding-left: 5px;
}
```

**警告：** HTML 表单验证并*不能*替代确保输入数据格式正确的脚本。很容易对 HTML 进行调整，使他们能够绕过验证，或完全删除验证。也有可能会完全绕过 HTML 代码，直接将数据提交给你的服务器。如果你的服务器端代码不能验证它所收到的数据，那么当提交的数据格式不当（或数据过大、类型错误等等）时，灾难就会降临。

处理浏览器支持
-------

如前所述，使用 month 输入的最大问题是大部分主流浏览器还没有实现它们；只有桌面端的 Chrome/Opera、Edge 和大部分的移动端现代浏览器支持它们。例如，安卓版 Chrome 的 `month` 选择器看起来像这样：

![Android 手机上 Chrome 的月份选择器样式](/zh-CN/docs/Web/HTML/Element/input/month/month-android.png)

不支持的浏览器会优雅地降级为文本输入，但这在用户界面的一致性（呈现的控件会有所不同）和数据处理方面都会产生问题。

第二个问题更为严重；如前所述，`month` 输入的值总是被规范为 `yyyy-mm` 的格式。另一方面，对于文本输入，默认情况下，浏览器不知道时间应该是什么格式，而且人们有多种写法，如：

* `mmyyyy` (072022)
* `mm/yyyy` (07/2022)
* `mm-yyyy` (07-2022)
* `yyyy-mm` (2022-07)
* `Month yyyy` (July 2022)
* 等等

一个办法是在你的 `month` 输入上添加 `pattern` 属性。即使 `month` 输入不使用它，text 输入回退也会使用。例如，试着在一个不支持 month 输入的浏览器中查看下面的演示：

```html
<form>
  <div>
    <label for="month"> 愿意在哪个月访问（6 月至 9 月）？ </label>
    <input
 id="month"
 type="month"
 name="month"
 min="2022-06"
 max="2022-09"
 required
 pattern="[0-9]{4}-[0-9]{2}" />
    <span class="validity"></span>
  </div>
  <div>
    <input type="submit" value="提交表单" />
  </div>
</form>
```

如果你尝试提交，你会发现，如果你的输入不符合模式 `nnnn-nn`（其中 `n` 是 0 到 9 的数字），不支持的浏览器现在会显示一个错误信息（并突出显示输入无效）。当然，这并不能阻止人们输入无效的日期（如 `0000-42`），或者不正确但遵循格式的日期。

还有一个问题，就是用户不知道到底应该输入什么格式的日期。还有一些事情要做。
```

div {
  margin-bottom: 10px;
  position: relative;
}

input[type="number"] {
  width: 100px;
}

input + span {
  padding-right: 30px;
}

input:invalid + span::after {
  position: absolute;
  content: "✖";
  padding-left: 5px;
}

input:valid + span::after {
  position: absolute;
  content: "✓";
  padding-left: 5px;
}
```

目前，以跨浏览器方式处理时间的最佳方法（至少在所有主流浏览器实现它们之前）是让用户在单独的控件中输入月份和年份（特别是在 `<select>` 元素中，参见下面的示例），或使用 JavaScript 库（例如 jQuery 日期选择器插件）。

示例
--

在此示例中，我们创建了两组用于选择日期的接口元素：使用 `<input type="month">` 创建的原生选择器，以及为不支持 month 输入类型的旧版浏览器准备的两个分别用于选择月份和年份的 `<select>` 元素。

### HTML

用于请求月份和年份的表单看起来像这样：

```html
<form>
  <div class="nativeDatePicker">
    <label for="month-visit">What month would you like to visit us?</label>
    <input type="month" id="month-visit" name="month-visit" />
    <span class="validity"></span>
  </div>
  <p class="fallbackLabel">What month would you like to visit us?</p>
  <div class="fallbackDatePicker">
    <div>
      <span>
        <label for="month">Month:</label>
        <select id="month" name="month">
          <option selected>January</option>
          <option>February</option>
          <option>March</option>
          <option>April</option>
          <option>May</option>
          <option>June</option>
          <option>July</option>
          <option>August</option>
          <option>September</option>
          <option>October</option>
          <option>November</option>
          <option>December</option>
        </select>
      </span>
      <span>
        <label for="year">Year:</label>
        <select id="year" name="year"></select>
      </span>
    </div>
  </div>
</form>
```

ID 为 `nativeDatePicker` 的 `<div>` 使用 `month` 输入类型来请求月份和年份，而 ID 为 `fallbackDatePicker` 的 `<div>` 则使用一对 `<select>` 元素。第一个元素请求月份，第二个元素请求年份。

用于选择月份的 `<select>` 是硬编码的，因为月份的名称不会改变（不考虑本地化）。可用的年份值列表是根据当前年份动态生成的（关于这些函数如何工作的详细解释，见下面的代码注释）。
```

div {
  margin-bottom: 10px;
  position: relative;
}

input[type="number"] {
  width: 100px;
}

input + span {
  padding-right: 30px;
}

input:invalid + span::after {
  position: absolute;
  content: "✖";
  padding-left: 5px;
}

input:valid + span::after {
  position: absolute;
  content: "✓";
  padding-left: 5px;
}
```

### JavaScript

处理选择哪种方案并设定一系列包含于非原生的 `<select>` 的年份列表的 JavaScript 代码如下所示。

该代码中可能有趣的另一部分是特性检测代码。要检测浏览器是否支持 `<input type="month">`，我们创建一个新的 `<input>` 元素，尝试将其 `type` 设置为 `month`，然后立即检查其 `type` 值。不支持的浏览器将返回 `text`，与 month 的回退行为相符。如果不支持 `<input type="month">`，我们将隐藏原生选择器并显示作为回退的选择器 UI。

```js
// 获取 UI 元素
const nativePicker = document.querySelector(".nativeDatePicker");
const fallbackPicker = document.querySelector(".fallbackDatePicker");
const fallbackLabel = document.querySelector(".fallbackLabel");

const yearSelect = document.querySelector("#year");
const monthSelect = document.querySelector("#month");

// 最初，隐藏回退元素
fallbackPicker.style.display = "none";
fallbackLabel.style.display = "none";

// 测试一个新的 date 输入框是否会回退至 text 输入框
const test = document.createElement("input");

try {
  test.type = "month";
} catch (e) {
  console.log(e.description);
}

// 如果回退了，运行 if 代码块中的代码
if (test.type === "text") {
  // 隐藏原生选择器，显示回退元素
  nativePicker.style.display = "none";
  fallbackPicker.style.display = "block";
  fallbackLabel.style.display = "block";

  // 动态生成年份
  // 月份总是相同的，故将它们硬编码
  populateYears();
}

function populateYears() {
  // 获取当前年份的数值表示
  const date = new Date();
  const year = date.getFullYear();

  // 在年份 <select> 中，令该年和之前的 100 年可选
  for (let i = 0; i <= 100; i++) {
    const option = document.createElement("option");
    option.textContent = year - i;
    yearSelect.appendChild(option);
  }
}
```

**备注：** 请记住有些年份有 53 周（见每年的周数）！当你在开发产品应用时应当考虑这个问题。

规范
--

| Specification |
| --- |
| HTML Standard # month-state-(type=month) |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* 通用 `<input>` 元素和用于操作该元素的接口 `HTMLInputElement`
* HTML 中使用的日期和时间格式
* 日期时间选择器教程 (en-US)
* `<input type="datetime-local">`、`<input type="date">`、`<input type="time">` 和 `<input type="week">`
* 表单控件 CSS 兼容性列表


<input type="hidden"> - HTML（超文本标记语言）

<input type="hidden">
=====================

**`hidden`** 类型的 `<input>` 元素允许 Web 开发者包含用户不可见、不可改的数据，在用户提交表单时，这些数据会一并发送出。比如，正被请求或编辑的内容的 ID，或是一个唯一的安全令牌。这些隐藏的 input 元素在渲染完成的页面中完全不可见，而且没有方法可以使它重新变为可见。

|  |  |
| --- | --- |
| **值** | 代表要传递回服务器的隐藏数据的字符串值 |
| **事件** | 无 |
| **支持的公共属性** | `autocomplete` |
| **IDL 属性** | `value` |
| **DOM 接口** | `HTMLInputElement` |
| **方法** | 无 |

**备注：** `input` 和 `change` 事件不适用于此输入类型。即使使用 JavaScript（如 `hiddenInput.focus()`），隐藏的 input 也无法获得焦点。

值
-

`<input>` 元素的 `value` 属性包含要提交到服务器的隐藏数据。尽管可以通过浏览器开发者工具编辑该值，但明确要求用户不能通过用户界面看到或编辑这个值。

**警告：** 尽管该值并未作为页面内容中显示给用户，但仍然可以使用任何浏览器的开发者工具或“查看源代码”功能来查看并进行编辑。请不要将表单的安全性依赖于使用 `hidden` 输入上。

额外属性
----

除所有 ``<input>`` 元素所共有的属性之外，带有 `hidden` 属性的 input 还提供以下额外属性：

### name

这实际上是常见属性之一，但它对于隐藏的 ``<input>`` 具有一个特殊含义。一般来说，`name` 属性在隐藏的 input 输入中与在非隐藏的 input 输入的表现是一样的；但是，在提交表单时，会自动包含一个 `name` 属性被设置为 `_charset_` 的隐藏 `input`，其值会被自动设置为被提交表单的字符编码。

使用隐藏 input
----------

如上所述，隐藏的 `input` 可以用于任何有需要提交给服务器的，且要求用户无法查看或编辑的数据的地方。让我们看一些说明其用法的例子吧。

### 跟踪编辑的内容

隐藏输入的最常见用途之一是当被编辑的表单提交时，保持跟踪数据库数据的更新。典型的工作流程如下所示：

1. 用户决定编辑他们可以控制的某些内容，例如博客文章或产品条目。他们可以通过按编辑按钮开始。
2. 要被编辑的内容从数据库中提取并加载到 HTML 表单中，以允许用户进行更改。
3. 编辑后，用户提交表单，并将更新后的被数据发送回服务器以在数据库中进行更新。

在这里的步骤 2 思路中，要被更新的 ID 记录保留在隐藏的输入中。在步骤 3 中提交表单后，该 ID 随记录内容自动发送回服务器。该 ID 使站点的服务器端组件确切知道哪些记录需要被提交的数据更新的。

你可以在下面的示例部分中看到完整的示例。

### 提升网站安全性

隐藏输入表单还用于存储和提交安全令牌或机密信息，以提高网站的安全性。基本思路是，如果用户正在填写敏感的表单，例如在银行网站上将某笔款项转入另一个账户的表格，所提供的机密信息将证明他们的身份是真实的，并且使用了正确的表单来提交转账请求。

这将阻止恶意用户创建伪造的表单，伪装成银行，然后通过电子邮件将表单发送给毫无戒备心的用户，以诱骗他们将钱转入错误的位置。这种攻击称为跨站点请求伪造（CSRF）；几乎任何知名的服务器端框架都使用隐藏机密来防止此类攻击。

**备注：** 如前所述，将密钥放置在隐藏的输入中并不能使其固有安全性。密钥的组成和编码已经可以做到这一点。隐藏输入的值是保持它与数据关联性，并在将表单发送到服务器时自动将其包含在内。你需要使用精心设计的密钥来真正保护你的网站。

验证
--

隐藏的 input 不参与约束验证；它们没有可受到约束的属性值。

示例
--

让我们看一下如何使用隐藏输入保存正在被编辑的记录的 ID，实现我们先前描述的编辑表单的简单版本（请参阅跟踪编辑的内容）。

编辑表单的 HTML 可能看起来像这样：

```html
<form>
  <div>
    <label for="title">文章标题：</label>
    <input type="text" id="title" name="title" value="My excellent blog post" />
  </div>
  <div>
    <label for="content">文章内容：</label>
    <textarea id="content" name="content" cols="60" rows="5">
This is the content of my excellent blog post. I hope you enjoy it!
    </textarea>
  </div>
  <div>
    <button type="submit">更新文章</button>
  </div>
  <input type="hidden" id="postId" name="postId" value="34657" />
</form>
```

我们还添加一些简单的 CSS：

```css
html {
  font-family: sans-serif;
}

form {
  width: 500px;
}

div {
  display: flex;
  margin-bottom: 10px;
}

label {
  flex: 2;
  line-height: 2;
  text-align: right;
  padding-right: 20px;
}

input,
textarea {
  flex: 7;
  font-family: sans-serif;
  font-size: 1.1rem;
  padding: 5px;
}

textarea {
  height: 60px;
}
```

在将表单发送给用户的浏览器之前，服务器会将 ID 为 `postID` 的隐藏 input 的值设置为其数据库中发送的 ID，并在返回表单时使用该信息来知道要向哪个数据库记录更新改变后的信息。内容中无需脚本即可处理。

输出看起来像这样：

**备注：** 你还可以在 GitHub 上找到示例（请参见源代码，并可以实时运行）。

提交后，发送到服务器的表单数据将如下所示：

`title=My+excellent+blog+post&content=This+is+the+content+of+my+excellent+blog+post.+I+hope+you+enjoy+it!&postId=34657`

即使根本看不到隐藏的 `<input>`，它所包含的数据仍然会被提交给服务器。

规范
--

| Specification |
| --- |
| HTML Standard # hidden-state-(type=hidden) |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* HTML 表单指南
* 通用 `<input>` 元素和用于操作该元素的 `HTMLInputElement` 接口


<input type="radio"> - HTML（超文本标记语言）

<input type="radio">
====================

**`radio`** 类型的 `<input>` 元素通常用于一个**单选组**中，其中包含一组描述一系列相关选项的单选按钮。

在给定单选组中，同时只可以选择一个选项。单选按钮通常渲染为小圆圈，当选中该选项时，圆圈被填充或高亮。

尝试一下
----

它们被称为单选按钮，因为它们的外观和操作方式与老式无线电上的按钮类似，如下图所示。

![旧时收音机上的按钮。](/zh-CN/docs/Web/HTML/Element/input/radio/old-radio.jpg)

**备注：** 复选框类似于单选按钮，但是有个重要的区别——单选按钮为选择集合中的其中一项而设计，然而复选框允许决定单个值的选定情况。当多个控件存在时，单选按钮允许选择其中的一个，而多选按钮允许选择其中多个。

值
-

`value` 属性是包含该单选按钮值的字符串。这个值不会在用户代理显示，而是用于辨别单选组中所选的单选按钮。

### 定义一个单选按钮组

一个单选按钮组由具有相同 `name` 属性的单选按钮组成。一旦单选按钮组被建立，选择那按钮组的任何单选按钮将会自动取消同组当前选择的任何按钮。

你可以在一个页面上创建多个单选按钮组，只要每一组拥有唯一的 `name` 属性即可。

举个例子，如果你需要在表单中询问用户的首选联系方式，你可以创建三个 `name` 属性都为 `contact` 的单选按钮，只不过一个按钮的 `value` 属性为 `email` ，另一个的 `value` 属性为 `phone`，还有一个的 `value` 属性为 `mail`。用户不会看见 `value` 属性或 `name` 属性（除非你故意用代码显示它们）。

最终的 HTML 可能看起来像这样：

```html
<form>
  <fieldset>
    <legend>请选择首选的联系方式：</legend>
    <div>
      <input type="radio" id="contactChoice1" name="contact" value="email" />
      <label for="contactChoice1">电子邮件</label>

      <input type="radio" id="contactChoice2" name="contact" value="phone" />
      <label for="contactChoice2">电话</label>

      <input type="radio" id="contactChoice3" name="contact" value="mail" />
      <label for="contactChoice3">邮件</label>
    </div>
    <div>
      <button type="submit">提交</button>
    </div>
  </fieldset>
</form>
```

你可以在这里看到三个单选按钮，每个按钮的 `name` 属性都设置为 `contact`，并且都含有在单选按钮组中唯一的 `value` 属性。每一个单选按钮也有一个给 `<label>` 元素的 `for` 属性使用的 `id`，将标签（label）和单选按钮关联。

你可以在这里查看这个例子：

### 单选组的数据表示

当上面的表单在提交时选择了一个单选框，表单的数据就包括了 `contact=value` 项。例如，若用户点击“电话”单选按钮，然后提交表单，提交的数据中将包括 `contact=phone` 项。

如果你在 HTML 中省略了 `value` 属性，那么提交的表单数据就会将该值分配到 `on` 上。在这种情况下，如果用户选中了“电话”选项并提交了表单，提交的表单数据将是 `contact=on`，这并没有什么用。所以别忘了设置你的 `value` 属性！

**备注：** 如果在提交表单时没有选择任何单选按钮，则提交的表单数据中根本不包括单选组，因为没有要报告的值。

在没有选择任何一组单选按钮的情况下提交表单的情况并不多见，所以通常明智的做法是让一个单选按钮默认为“选中”状态。参见下面的默认选择单选按钮。

让我们给我们的例子添加一点代码，这样我们就可以检查这个表单所产生的数据。对 HTML 进行修改，增加一个 `<pre>` 块，将表单数据输出到其中：

```html
<form>
  <fieldset>
    <legend>请选择首选的联系方式：</legend>
    <div>
      <input type="radio" id="contactChoice1" name="contact" value="email" />
      <label for="contactChoice1">电子邮件</label>
      <input type="radio" id="contactChoice2" name="contact" value="phone" />
      <label for="contactChoice2">电话</label>
      <input type="radio" id="contactChoice3" name="contact" value="mail" />
      <label for="contactChoice3">邮件</label>
    </div>
    <div>
      <button type="submit">提交</button>
    </div>
  </fieldset>
</form>
<pre id="log"></pre>
```

然后我们添加一些 JavaScript 代码，设置监听 `submit` 事件的监听器，当用户点击“提交”按钮时会触发该事件：

```js
const form = document.querySelector("form");
const log = document.querySelector("#log");

form.addEventListener(
  "submit",
  (event) => {
    const data = new FormData(form);
    let output = "";
    for (const entry of data) {
      output = `${output}${entry[0]}=${entry[1]}\r`;
    }
    log.innerText = output;
    event.preventDefault();
  },
  false,
);
```

试试这个例子，会发现 `contact` 组的输出结果永远不会多于一个。

额外属性
----

除了所有 `<input>` 元素共通的属性外，`radio` 输入还支持以下属性：

`checked`

一个布尔值属性，如果出现，代表这个单选按钮是组中被选中的那个。

不像其他浏览器那样，Firefox 默认在不同页面之间保持了 `<input>` 动态选择的状态。使用 `autocomplete` 属性来控制这个特性。

`value`

`value` 属性是所有 `<input>` 元素共有的属性之一；然而，对于 `radio` 类型的输入，有特殊用途：当表单提交时，只有当前选定的单选按钮才会提交到服务器，并且报告的值为 `value` 属性的值。如果没有指定 `value`，默认为字符串 `on`。这个行为已经在值这一节中描述。

`required`

`required` 属性是所有 `<input>` 元素共有的属性之一。如果含有相同 name 的单选按钮组中任意一个按钮包含 `required` 属性，那么该组的单选按钮必须被选定，即使它并不需要应用该属性。

使用单选输入
------

我们已经说明了单选按钮的基本使用，现在来看看其他你可能需要知道的与单选按钮相关的特性和技术。

### 默认选择单选按钮

为了让单选按钮默认被选择，需要包含 `checked` 属性，就像这个修改过的示例一样：

```html
<form>
  <fieldset>
    <legend>请选择首选的联系方式：</legend>
    <div>
      <input
 type="radio"
 id="contactChoice1"
 name="contact"
 value="email"
 checked />
      <label for="contactChoice1">电子邮件</label>

      <input type="radio" id="contactChoice2" name="contact" value="phone" />
      <label for="contactChoice2">电话</label>

      <input type="radio" id="contactChoice3" name="contact" value="mail" />
      <label for="contactChoice3">邮件</label>
    </div>
    <div>
      <button type="submit">提交</button>
    </div>
  </fieldset>
</form>
```

在这个示例中，第一个单选按钮默认是选择状态。

**备注：** 如果你在多于一个单选按钮上指定了 `checked` 属性，后面的实例将覆盖前面的实例；也就是说，最后一个包含 `checked` 属性的单选按钮是当前选择的按钮。这是因为一个单选按钮组只能同时选取一个单选按钮，用户代理会在选择新的单选按钮时自动取消选择其他单选按钮。

### 为你的单选按钮提供一个较大的选择区域

在前述示例中，你可能会注意到，可以通过点击其关联的 `<label>` 元素来选取一个单选按钮，与选取单选按钮本身表现一致。这是 HTML 表单标签的一个有用的特性，使用户更容易点击他们想要的选项，特别是在智能手机等小屏幕设备上。

除了无障碍之外，这也是在表单上正确设置 `<label>` 元素的另一个好理由。

验证
--

单选按钮不参与约束验证；它们没有真正的值可供约束。

为单选按钮添加样式
---------

下面的示例显示了我们在文章中所看到的例子的一个稍微彻底的版本，有一些额外的样式，并且通过使用专门的元素建立了更好的语义。HTML 代码看起来像这样：

```html
<form>
  <fieldset>
    <legend>请选择首选的联系方式：</legend>
    <div>
      <input
 type="radio"
 id="contactChoice1"
 name="contact"
 value="email"
 checked />
      <label for="contactChoice1">电子邮件</label>

      <input type="radio" id="contactChoice2" name="contact" value="phone" />
      <label for="contactChoice2">电话</label>

      <input type="radio" id="contactChoice3" name="contact" value="mail" />
      <label for="contactChoice3">邮件</label>
    </div>
    <div>
      <button type="submit">提交</button>
    </div>
  </fieldset>
</form>
```

这个示例中涉及到的 CSS 样式就比较重要：

```css
html {
  font-family: sans-serif;
}

div:first-of-type {
  display: flex;
  align-items: flex-start;
  margin-bottom: 5px;
}

label {
  margin-right: 15px;
  line-height: 32px;
}

input {
  appearance: none;

  border-radius: 50%;
  width: 16px;
  height: 16px;

  border: 2px solid #999;
  transition: 0.2s all linear;
  margin-right: 5px;

  position: relative;
  top: 4px;
}

input:checked {
  border: 6px solid black;
}

button,
legend {
  color: white;
  background-color: black;
  padding: 5px 10px;
  border-radius: 0;
  border: 0;
  font-size: 14px;
}

button:hover,
button:focus {
  color: #999;
}

button:active {
  background-color: white;
  color: black;
  outline: 1px solid black;
}
```

这里最值得注意的是 `appearance` 属性的使用（为了支持某些浏览器，需要加前缀）。默认情况下，单选按钮（和复选框）的样式使用了操作系统对这些控件的本地样式。通过指定 `appearance: none`，你可以完全删除本地样式，并为它们创建自己的样式。这里我们使用了 `border` 和 `border-radius`，以及 `transition` 来创建一个漂亮的带动画单选。请注意 `:checked` 伪类是如何用来指定单选按钮被选中时的外观样式的。

**备注：** 如果你想使用 `appearance` 属性，你需要小心测试。即使大多数流行浏览器都支持它，它的实现变化很广。在老式浏览器中，即使是关键字 `none`，不同浏览器之间差异也很大，有些浏览器根本不支持。在新的浏览器中，差别就会小得多。

注意，当点击单选按钮时，有一个流畅的渐入、渐出效果，此外，图例和提交按钮的风格和颜色都是自定义的，具有强烈的对比。这可能不是你在真正的 web 应用中想要的外观，但它展示了实现的可能性。

技术总结
----

|  |  |
| --- | --- |
| **值** | 代表单选按钮值的字符串。 |
| **事件** | `change` 和 `input` |
| **支持的通用属性** | `checked`、`value` 和
 `required (en-US)` |
| **IDL 属性** | `checked` 和 `value` |
| **DOM 接口** | `HTMLInputElement` |
| **方法** | `select()` |

规范
--

| Specification |
| --- |
| HTML Standard # radio-button-state-(type=radio) |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* `<input>` 和实现它的 `HTMLInputElement` 接口。
* `RadioNodeList` (en-US)：描述一系列单选按钮的接口
* CSS 属性兼容性


<input type="range"> - HTML（超文本标记语言）

<input type="range">
====================

**`range`** 类型的 `<input>` 元素允许用户指定一个数值，该数值必须不小于给定值，并且不得大于另一个给定值。但是，其精确值并不重要。通常使用滑块或拨号控件而不是像 number 输入类型这样的文本输入框来表示。

由于这种小部件不精确，因此除非控件的确切值不重要，否则通常不应使用它。

尝试一下
----

如果用户的浏览器不支持 `range` 类型，它将回退并将其视为 `text` 输入。

|  |  |
| --- | --- |
| **值** | 一个代表已选择数值的字符串表示，使用 `valueAsNumber` 来将此值作为数值获取。 |
| **事件** | `change` 和 `input` |
| **支持的常用属性** | `autocomplete`、`list`、`max`、`min` 和 `step` |
| **IDL 属性** | `list`、`value` 和 `valueAsNumber` |
| **DOM 接口** | `HTMLInputElement` |
| **方法** | `stepDown()` (en-US) 和 `stepUp()` (en-US) |

### 验证方式

没有可用的模式验证。但是，执行以下形式的自动验证：

* 如果将 `value` 设置为无法转换为有效浮点数的值，则验证将失败，因为输入框的值是错误的。
* 该值不得小于 `min`，默认值为 0。
* 该值将不大于 `max`，默认值为 100。
* 该值将是 `step` 的整倍数，默认值为 1。
### 值

`value` 属性包含所选数字的字符串表示形式。该值绝不能为空字符串（`""`）。默认值介于指定的最小值和最大值之间，除非最大值实际上小于最小值，在这种情况下，默认值将设置 `min` 属性的值。确定默认值的算法是：

```js
defaultValue =
  rangeElem.max < rangeElem.min
    ? rangeElem.min
    : rangeElem.min + (rangeElem.max - rangeElem.min) / 2;
```

如果尝试将值设置为小于最小值，则将其设置为最小值。类似地，尝试将值设置为大于最大值会导致将其设置为最大值。

其他属性
----

除了所有 `<input>` 元素共享的属性之外，range 输入还提供以下属性。

### list

list 属性指定了一个 `<datalist>` 元素的 `id`，用来提供输入建议。`<datalist>` 提供了一个预定义的值列表，向用户建议这个输入。列表中任何与 type 不兼容的值都不包括在建议选项中。所提供的值是建议，不是要求：用户可以从这个预定义的列表中选择，或者提供不同的值。

有关在支持的浏览器中如何表示范围中的选项的示例，请参见下面的添加井号标记。

### max

允许值范围内的最大值。如果输入到元素中的 `value` 超过此值，则该元素将无法通过约束验证。如果 `max` (en-US) 属性的值不是数字，则元素没有最大值。

此值必须大于或等于 `min` 属性的值。请参见 HTML `max` (en-US) 属性。

### min

允许值范围内的最小值。如果元素的`value` 小于此值，则该元素将无法通过约束验证。如果 `min` 指定的值不是有效数字，则输入没有最小值。

该值必须小于或等于 `max` (en-US) 属性的值。请参见 HTML `min` (en-US) 属性。

### step

`step` 属性指定了值必须满足的粒度，或者是下文描述的特殊值 `any`。值必须满足基础的步进值，才有效。如果指定了 `min` 属性，则由 min 属性决定，否则，使用 `value` 属性的值，如果上述两个值都不存在，则提供适当的默认值。

字符串值 `any` 意味着不使用步进值，任意值都可以接受（除其他制约因素如 `min` 或 `max` 之外）。

**备注：** 当用户输入的数据不符合步进配置时，用户代理可能会四舍五入到最近的有效值，当有两个同样接近的选项时，更倾向于正方向的数字。

`range` 输入的默认步进值为 1，*除非*步进基数不是整数，否则仅允许输入整数；否则，默认值为 1。例如，如果将 `min` 设置为 -10 并将 `value` 设置为 1.5，则 1 的 `step` 将只允许正方向上的值为 1.5、2.5、3.5 等，以及负方向上的值为 -0.5，-1.5，-2.5 等。请参阅 HTML `step` 属性 (en-US)。

非标准属性
-----

### orient

类似于影响 `<progress>` 和 `<meter>` 元素的非标准 CSS 属性 -moz-orient，`orient` 属性定义范围滑块的方向。值包括 `horizontal` 和 `vertical`，分别代表范围的水平呈现和垂直呈现。

**备注：** 以下输入属性不适用于 range 输入：`accept`、`alt`、`checked`、`dirname`、`formaction`、`formenctype`、`formmethod`、`formnovalidate`、`formtarget`、`height`、`maxlength`、`minlength`、`multiple`、`pattern`、`placeholder`、`readonly`、`required`、`size`、`src` 和 `width`。如果包含了这些属性中的任何一个，都将被忽略。

示例
--

虽然 `number` 类型允许用户输入带有可选约束的数字，以强制其值介于最小值和最大值之间，但它确实要求输入特定值。`range` 输入类型可以在用户甚至不关心或不知道所选的特定数字值是什么的情况下，向用户询问一个值。

常用范围输入的一些情况示例：

* 音频控件（例如音量和平衡）或过滤器控件。
* 颜色配置控件，例如颜色通道、透明度、亮度等。
* 游戏配置控件，例如难度、可见性距离、世界范围等等。
* 密码管理员生成的密码的密码长度。

通常，如果用户对最小值和最大值之间的距离的百分比比实际数字本身更感兴趣，则 range 输入是一个不错的选择。例如，在家庭立体声音量控制的情况下，用户通常会想到“将音量设置为最大音量的一半”而不是“将音量设置为 0.5”。

### 指定最小值和最大值

默认情况下，最小值为 0，最大值为 100。如果这不是想要的值，则可以通过更改 `min` 和/或 `max` 属性。这些可以是任何浮点值。

例如，要要求用户输入介于 -10 和 10 之间的值，可以使用：

```html
<input type="range" min="-10" max="10" />
```

### 设置值的粒度

默认情况下，粒度为 1，表示该值始终是整数。可以通过更改 `step` 属性控制粒度。例如，如果需要一个介于 5 到 10 之间的值（精确到两位小数），则应将 `step` 的值设置为 0.01：

#### 设置 step 属性

```html
<input type="range" min="5" max="10" step="0.01" />
```

#### 设置 step 为“any”

如果要接受任何值而不论扩展到小数点后多少位，可以为 `step` 属性指定值 `any`。

```html
<input type="range" min="0" max="3.14" step="any" />
```

该示例使用户可以选择 0 到 π 之间的任何值，而对所选值的小数部分没有任何限制。

### 添加井号标记

为了在 range 控制器中添加井号标记，要包含 `list` 属性，赋予它某个定义了一系列控件上的井号标记 `<datalist>` 元素的 `id`。每个点使用带有 `value` 属性的 `<option>` 元素设置需要添加标记的点。

#### HTML

```html
<label for="temp">选择一个适宜的温度：</label><br />
<input type="range" id="temp" name="temp" list="tickmarks" />

<datalist id="tickmarks">
  <option value="0"></option>
  <option value="25"></option>
  <option value="50"></option>
  <option value="75"></option>
  <option value="100"></option>
</datalist>
```

#### 结果

### 添加标签

你可以通过给 `<option>` 元素赋予 `label` 属性来标记这些井号标记，然而，你需要使用 CSS 令标签显示且定位正确。以下是可以实现这点的方法之一。

#### HTML

```html
<label for="temp">选择一个适宜的温度：</label><br />
<input type="range" id="temp" name="temp" list="tickmarks" />

<datalist id="tickmarks">
  <option value="0" label="很冷！"></option>
  <option value="25" label="凉爽"></option>
  <option value="50" label="适中"></option>
  <option value="75" label="变得暖和了！"></option>
  <option value="100" label="很热！"></option>
</datalist>
```

#### CSS

```css
datalist {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  writing-mode: vertical-lr;
  width: 200px;
}

option {
  padding: 0;
}

input[type="range"] {
  width: 200px;
  margin: 0;
}
```

#### 结果

### 创建垂直 range 控件

默认情况下，浏览器将 range 输入渲染为可左右滑动的旋钮。

要创建一个垂直滑动的 range 输入，请将 CSS `appearance` 属性设置为 `slider-vertical`，并包含 Firefox 的非标准 `orient` 属性。

#### 水平 range 控件

考虑以下 range 控件：

```html
<input type="range" id="volume" min="0" max="11" value="7" step="1" />
```

该控件是水平的（至少在大部分主流浏览器中，其他的根据情况会有所不同）。

#### 使用 appearance 属性

`appearance` 属性包含了一个非标准值 `slider-vertical` 使得滑块垂直。

我们使用与前例相同的 HTML 代码：

```html
<input type="range" min="0" max="11" value="7" step="1" />
```

我们以 range 类型的 input 作为目标：

```css
input[type="range"] {
  appearance: slider-vertical;
}
```

#### 使用 orient 属性

仅在 Firefox 中，有一个非标准的 `orient` 属性。

使用与前例类似的代码，只是添加一个值为 `vertical` 的该属性：

```html
<input type="range" min="0" max="11" value="7" step="1" orient="vertical" />
```

#### writing-mode: bt-lr

`writing-mode` 属性一般不应用于改变文本方向以达到国际化或本地化的目的，但可用于特殊效果。

我们使用与前例类似的 HTML 代码：

```html
<input type="range" min="0" max="11" value="7" step="1" />
```

我们以 range 类型的 input 作为目标，将写作模式从默认改为 `bt-lr`（从底部向上，从左到右）：

```css
input[type="range"] {
  writing-mode: bt-lr;
}
```

#### 将它们放在一起

由于以上这些示例会在不同的浏览器中奏效，你可以将它们放在一起，使得纵向 range 输入可以跨浏览器工作：

我们会在 Firefox 浏览器中保持使用 `orient` 属性的 `vertical` 值：

```html
<input type="range" min="0" max="11" value="7" step="1" orient="vertical" />
```

我们只对 range 类型的 input 施加 CSS 样式，将写作模式从默认改为 `bt-lr`，或 bottom-to-top 和 left-to-right（对于 Edge 和 Internet Explorer 浏览器）。对 Blink 和 Webkit 浏览器可以添加 `appearance: slider-vertical` 配置：

```css
input[type="range"] {
  writing-mode: bt-lr;
  appearance: slider-vertical;
}
```

规范
--

| Specification |
| --- |
| HTML Standard # range-state-(type=range) |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

另请参考
----

* HTML 表单
* `<input>` 元素和其基于的 `HTMLInputElement` 接口
* `<input type="number">`
* `validityState.rangeOverflow` (en-US) 和 `validityState.rangeUnderflow` (en-US)
* 使用 ConstantSourceNode 控制多个参数 (en-US)
* 设置范围元素的样式
* 表单控件 CSS 兼容性列表


<input type="password"> - HTML（超文本标记语言）

<input type="password">
=======================

**`"password"`** 类型的 `<input>` 元素可以让用户更加安全的输入密码。

这个元素是作为单行纯文本编辑器控件呈现的，其中文本被遮蔽，无法读取。通常通过用诸如星号（“\*”）或点（“•”）等符号替换每个字符来实现。根据用户的用户代理和操作系统不同，显示的字符也会不同。

尝试一下
----

输入过程的具体细节可能因浏览器而异。例如，有的浏览器经常在隐藏它之前显示键入的字符一段时间，而另外一些浏览器允许用户切换明文显示模式。移动端设备上检查是否输入了预期的密码通常比较困难，而这两种机制都能解决这个问题。

**备注：** 任何涉及密码等敏感信息的表单（例如登录表格）都应通过 HTTPS 提供。许多浏览器现在都实现了警告不安全登录表单的机制，参阅不安全的密码 (en-US)。

值
-

`value` 属性包含了用于输入密码的文本编辑控件的当前内容的字符串。如果用户还没有输入任何内容，则此值为空字符串（`""`）。如果指定了 `required` 属性，则密码编辑框必须包含一个非空的有效值。

如果指定了 `pattern` 属性，则 `password` 中的内容只有通过验证才有效；参见验证一节以获得更多信息。

**备注：** 换行（U+000A）和回车（U+000D）符不允许在 `password` 中出现。当对密码控件设置值时，换行和回车符都会从值中删除。

额外属性
----

除了所有 `<input>` 元素支持的属性外，密码字段输入还支持以下属性：

### maxlength

用户可以输入到密码输入框中的最大字符数（以 UTF-16 代码单元为单位）。必须为大于等于 0 的整数。如果未指定 `maxlength` 或指定了无效的值，则密码输入框将没有最大长度。这个值也必须大于等于 `minlength` 的值。

如果文本框中的字符数大于 `maxlength` UTF-16 编码单元长度，则输入将无法通过约束验证。

### minlength

用户可以输入到密码输入框中的最小字符数（以 UTF-16 代码单元为单位）。该值必须是小于等于 `maxlength` 指定的值的非负整数值。如果未指定 `minlength` 或指定了无效的值，则密码输入将没有最小长度。

如果输入到字段中的文本的长度小于 `minlength` UTF-16 代码单元的长度，则输入将无法通过约束验证。

### pattern

如果指定了 `pattern` 属性，为了使 `value` 通过约束验证，必须满足该属性给定的正则表达式。它必须是 `RegExp` 类型的有效 JavaScript 正则表达式，并且已在我们的正则表达式指南中进行了说明；在编译正则表达式时指定了 `'u'` 标志，因此该模式被视为 Unicode 代码点的序列，而不是 ASCII。模式文本周围无需指定正斜杠。

如果没有指定模式，或指定了无效的模式，则不会应用任何正则表达式，且该属性将被完全忽略。

**备注：** 使用 `title` 属性来指定大多数浏览器将作为工具提示的文本，以解释匹配该模式的要求是什么。还应该在附近包括其他解释性的文本。

强烈建议在密码输入中使用模式，以帮助确保用户选择和使用有效的密码，并使用广泛的字符类别。通过一个模式，你可以规定大小写规则、要求使用一些数字和/或标点符号，等等。参阅验证一节以获取详细说明和示例。

### placeholder

`placeholder` 属性是一个字符串，可向用户提供有关该字段中需要什么样的信息的简短提示。它应该是一个单词或短语来说明预期的数据类型，而不是说明性消息。文本中*不得*包含回车符或换行符。

如果控件的内容具有方向（LTR 或 RTL），但需要以相反的方向显示占位符，则可以使用 Unicode 双向算法来格式化字符，从而覆盖原有占位符的方向；请参见如何针对双向文本使用 Unicode 控制符获取更多信息。

**备注：** 尽可能避免使用 `placeholder` 属性。它在语义上没有其他解释表单的方式有用，并且可能导致内容出现意外的问题。请参见 `<input>` 标签以获取更多信息。

### readonly

一个布尔属性，如果存在，则表示该字段不能由用户编辑。但是，仍可以通过 JavaScript 代码直接设置 `HTMLInputElement.value` 的 `value` 属性来更改。

**备注：** 因为只读字段不能有值，所以 `required` 对指定了 `readonly` 属性的输入没有任何影响。

### size

`size` 属性是一个数字值，指示输入字段应有多少个字符宽。该值必须是一个大于零的数字，默认值是 20。由于字符宽度各不相同，这可能是也可能不是精确的，不应依赖于此；结果输入可能比指定的字符数更窄或更宽，这取决于字符和字体（使用的 `font` 设置）。

这并*不*限制用户可以在该字段中输入多少个字符，仅能指定一次可以看到多少个。要设置输入数据长度的上限，请使用 `maxlength` 属性。

使用密码输入框
-------

密码输入框的工作方式通常与其他文本输入框一样；主要区别在于遮挡内容，以防止用户附近的人读取密码。

### 一个简单的密码输入框

在这里，我们看到了最基本的密码输入，并使用了 `<label>` 元素创建了相应的标签。

```html
<label for="userPassword">密码：</label>
<input id="userPassword" type="password" />
```

### 允许自动补全

为了让用户的密码管理器自动输入密码，可以指定 `autocomplete` 属性。对于密码输入框，通常是下列值之一：

`on`

允许浏览器或密码管理器自动填写密码字段。这不像使用 `current-password` 或 `new-password` 那样提供信息。

`off`

不允许浏览器或密码管理器自动填写密码字段。请注意，某些软件会忽略此值，因为它通常会损害用户维护安全密码操作的能力。

`current-password`

允许浏览器或密码管理器输入网站的当前密码。这提供了比 `on` 更多的信息，因为它允许浏览器或密码管理器自动在该字段中输入当前已知的网站密码，但不建议新密码。

`new-password`

允许浏览器或密码管理器自动输入网站的新密码；这用于“更改密码”和“新用户”表单，在该字段询问用户新密码。新密码可能以多种方式生成，具体取决于使用的密码管理器。它可能只是填写新的建议密码，或者它可能会向用户显示创建密码的界面。

```html
<label for="userPassword">密码：</label>
<input id="userPassword" type="password" autocomplete="current-password" />
```

### 让密码必须输入

为了告知用户的浏览器在提交表单前，密码字段必须拥有一个合法的值，只要指定布尔属性 `required` 就可以了。

```html
<label for="userPassword">密码：</label>
<input id="userPassword" type="password" required />
<input type="submit" value="Submit" />
```

### 指定输入模式

如果你推荐（或要求）的密码语法规则会因替代的文字输入接口受益，你可以使用 `inputmode` 属性来请求一个特定的输入模式。这方面最明显的用例是如果密码被要求是数字的（如 PIN）。此时，带有虚拟键盘的移动设备可能会选择切换到数字键盘布局，而不是全键盘，以使输入密码更容易。如果 PIN 码是一次性使用的，请将 `autocomplete` 属性设置为 `off` 或 `one-time-code`，以暗示它不被保存。

```html
<label for="pin">PIN：</label>
<input id="pin" type="password" inputmode="numeric" />
```

### 设置长度要求

你仍然可以使用 `minlength` 和 `maxlength` 属性来设定密码的最小和最大可接受长度。此示例通过指定用户的 PIN 必须至少为 4 且不超过 8 位来扩展前一个示例。这里使用了 `size` 属性来确保密码输入控件的宽度为 8 个字符。

```html
<label for="pin">PIN：</label>
<input
 id="pin"
 type="password"
 inputmode="numeric"
 minlength="4"
 maxlength="8"
 size="8" />
```

### 选中字符

与其他文本输入控件一样，你可以使用 `select()` 方法来选择密码字段的所有字符。

#### HTML

```html
<label for="userPassword">密码：</label>
<input id="userPassword" type="password" size="12" />
<button id="selectAll">全选</button>
```

#### JavaScript

```js
document.getElementById("selectAll").onclick = () => {
  document.getElementById("userPassword").select();
};
```

#### 结果

你还可以使用 `selectionStart` 和 `selectionEnd` 来获取（或设置）当前选中的字符范围，以及 `selectionDirection` 来了解选择发生在哪个方向（或将被扩展，取决于你的平台；见其文档的解释）。然而，考虑到文本被遮挡，这些的用处就有些有限了。

验证
--

如果你的应用程序对输入密码的实际内容有字符集限制或任何其他要求，你可以使用 `pattern` 属性来建立一个正则表达式，用来自动确保你的密码符合这些要求。

在这个示例中，只有包含至少四个和不超过八个十六进制数字的值才是有效的。

```html
<label for="hexId">十六进制 ID：</label>
<input
 id="hexId"
 type="password"
 pattern="[0-9a-fA-F]{4,8}"
 title="输入包含 4~8 个十六进制字符的 ID"
 autocomplete="new-password" />
```

示例
--

### 申请社会安全号码

此示例仅接受与有效的美国社会安全号码格式相匹配的输入。这些用于美国税务和识别目的的数字的格式为“123-45-6789”。还存在针对每个组中允许的值的各种规则。

#### HTML

```html
<label for="ssn">SSN：</label>
<input
 type="password"
 id="ssn"
 inputmode="numeric"
 minlength="9"
 maxlength="12"
 pattern="(?!000)([0-6]\d{2}|7([0-6]\d|7[012]))([ -])?(?!00)\d\d\3(?!0000)\d{4}"
 required
 autocomplete="off" />
<br />
<label for="ssn">值：</label>
<span id="current"></span>
```

此示例使用了 `pattern` 属性，限制了输入为一个合法的社会安全号码。很显然，这个正则表达式并不能保证有效的 SSN（因为我们没有访问到社会保障局的数据库），但它确实保证输入的值可能是合法值之一。它通常会避免无效的值。此外，它允许三组数字由空格、短划线（“-”）分隔，或者没有分隔符。

`inputmode` 被设置为 `numeric`，以鼓励有虚拟键盘的设备切换到数字键盘布局，以方便输入。`minlength` 和 `maxlength`属性分别设置为 9 和 12，以要求值的长度至少为 9 个，不超过 12 个字符（前者在数字组之间没有分隔符，后者则有）。`required` 属性被用来表示该控件必须有一个值。最后，`autocomplete` 被设置为 `off`，以避免密码管理器和会话恢复功能试图设置其值，因为这根本就不是一个密码。

#### JavaScript

这只是一些简单的代码，用于在屏幕上显示输入的 SSN，以便你可以看到它。显然这会破坏密码字段的目的，但它有助于试验 `pattern`。

```js
const ssn = document.getElementById("ssn");
const current = document.getElementById("current");

ssn.oninput = (event) => {
  current.textContent = ssn.value;
};
```

#### 结果

技术总结
----

|  |  |
| --- | --- |
| **值** | 一个代表密码的字符串，或空值 |
| **Events** | `change` 和
 `input` |
| **支持的通用属性** | `autocomplete`、`inputmode`、`maxlength`、`minlength`、`pattern`、`placeholder`、`readonly`、`required` 和 `size` |
| **IDL 属性** | `selectionStart`、`selectionEnd`、`selectionDirection` 和 `value` |
| **DOM 接口** | `HTMLInputElement` |
| **方法** | `select()`、`setRangeText()` (en-US) 和 `setSelectionRange()` |

规范
--

| Specification |
| --- |
| HTML Standard # password-state-(type=password) |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* CSS 属性兼容性表格


<input type="date"> - HTML（超文本标记语言）

<input type="date">
===================

**`type="date"`** 类型的 `<input>` 元素会创建一个让用户输入一个日期的输入区域，可以使用自动验证内容的文本框，也可以使用特殊的日期选择器界面。结果值包括年份，月份和日期，但*不*包括时间。time 和 datetime-local 类型支持时间和日期时间输入。

尝试一下
----

通常来说控件的 UI 界面因浏览器的不同而有变化，到目前为止此控件还不被所有浏览器支持，具体细节请参阅浏览器兼容性。在不支持的浏览器当中，控件因此会被优雅的降级为普通的 `<input type="text">` 输入框。

|  |  |
| --- | --- |
| **值** | 按照 YYYY-MM-DD 格式化过的代表日期的字符串，或者为空字符串 |
| **事件** | `change` 事件和 `input` 事件 |
| **支持的常用属性** | `autocomplete`、`list`、`readonly` 和 `step` |
| **IDL 属性** | `list`、`value`、`valueAsDate` 和 `valueAsNumber` |
| **DOM 接口** | `HTMLInputElement` |
| **方法** | `select()`、`stepDown()` (en-US) 和 `stepUp()` (en-US) |

值
-

一个字符串，代表着输入到输入框的日期值。输入的日期按照日期字符串格式所描述的 ISO8601 标准进行格式化。

你可以通过在 `value` 属性中包含日期来为输入设置默认值，如下所示：

```html
<input type="date" value="2017-06-01" />
```

**备注：** 有一点需要注意的是，在格式方面显示的日期与实际的 `value` 不一样——显示的日期格式*取决于用户浏览器的区域设定*，而经解析的 `value` 的格式始终为 `yyyy-mm-dd`。

当然你也可以通过 JavaScript 代码获取和设置 `HTMLInputElement` 的 `value` 和 `valueAsNumber` 属性，例如：

```js
const dateControl = document.querySelector('input[type="date"]');
dateControl.value = "2017-06-01";
console.log(dateControl.value); // prints "2017-06-01"
console.log(dateControl.valueAsNumber); // prints 1496275200000, a JavaScript timestamp (ms)
```

这行代码查找 `type` 为 `date` 的第一个 `<input>` 元素，并且将其值设置为 `2017-06-01`（2017 年 6 月 1 日）。稍后将以字符串和数字的形式读取出这个值。

其他属性
----

对于所有 `<input>` 元素的大部分通用属性也对 `date` 输入起作用，但可能不会影响它的表现，如 `size` 和 `placeholder` 属性。除了共有属性外，`date` 输入还提供以下属性。

### max

所接受最新的日期。如果输入到该元素中的 `value` 发生在此之后，则元素将无法通过约束验证。如果 `max` 属性的值不是格式为 `yyyy-mm-dd` 的有效日期星期字符串，则该元素没有最大日期值。

如果同时设置了 `max` 和 `min` 值，此值必须**晚于或等于** `min` 属性指定的日期值。

### min

所接受最早的日期。如果输入到该元素中的 `value` 发生在此之前，则元素将无法通过约束验证。如果 `min` 属性的值不是格式为 `yyyy-mm-dd` 的有效日期星期字符串，则该元素没有最小日期值。

如果同时设置了 `max` 和 `min` 值，此值必须**早于或等于** `max` 属性指定的日期值。

### step

`step` 属性指定了值必须满足的粒度，或者是下文描述的特殊值 `any`。值必须满足基础的步进值，才有效。如果指定了 `min` 属性，则由 `min` 属性决定，否则，使用 `value` 属性的值，如果上述两个值都不存在，则提供适当的默认值。

字符串值 `any` 意味着不使用步进值，任意值都可以接受（除其他制约因素如 `min` 或 `max` 之外）。

**备注：** 当用户输入的数据不符合步进配置时，用户代理可能会四舍五入到最近的有效值，当有两个同样接近的选项时，更倾向于正方向的数字。

对于 `date` 输入，`step` 的值以天为单位，并被视为等于 86,400,000 乘以 `step` 值的毫秒数（基础数值是毫秒）。`step` 的默认值为 1，表示 1 天。

**备注：** 将 `date` 输入的 `step` 值指定为 `any` 与指定为 `1` 的效果相同。

使用日期输入控件
--------

日期控件提供了一个简单的日期选择 UI 界面，还标准化了发送到服务器的数据格式，而与用户的浏览器或区域设置无关。

在本章，我们将研究 `<input type="date">` 基础的和更复杂的用法。

### 日期控件基本用法

`<input>` 和 `<label>` 元素组合是 `<input type="date">` 最简单的使用方法，如下所示：

```html
<form action="https://example.com">
  <label>
    Enter your birthday:
    <input type="date" name="bday" />
  </label>

  <p><button>Submit</button></p>
</form>
```

这段 HTML 代码将所输入的日期作为 `bday` 的参数发送至 `https://example.com`，获得了像这样的 URL `https://example.com/?bday=1955-06-08` 。

### 设置日期最大和最小值

你可以通过 `min` 和 `max` 属性去限制用户的可选日期范围。在随后的例子中，我们将设定日期最小值为 `2017-04-01`，最大值为 `2017-04-30`：

```html
<form>
  <label>
    Choose your preferred party date:
    <input type="date" name="party" min="2017-04-01" max="2017-04-30" />
  </label>
</form>
```

在结果中我们可以看到，只有 2017 年 4 月份的日期可选——输入框中可以编辑的部分只有“日”这部分，并且超出 4 月份以外的日期不能通过日期控件的选择组件选择。

**备注：** 你*应该*可以使用 `step` 属性来改变每次最佳日期时步进（增加值）的天数（例如：或许你只希望使周六可以选）。但是，在撰写这篇文章的时候，还没有可靠的实现。

### 控制输入框大小

`<input type="date">` 不支持诸如 `size` 的表单大小属性。你必须依靠 CSS 来确定大小。

验证
--

默认情况下，`<input type="date">` 对输入的值不会做任何校验。接口实现通常不会让你输入任何非日期的东西（这一点很有帮助），但是你仍然可以留空或者（在被优雅降级为 `text` 类型的输入框时）输入一个不合法的值（例如 4 月 32 日）。

如果你使用 `min` 和 `min` 属性去限制可用日期（参见设置日期最大和最小值），对于支持的浏览器来说如果你尝试提交一个超出给定范围的日期，那么它将抛出一个错误。然而，你必须检查这些结果以确保他们在这些日期范围内，因为只有在用户设备上完全支持日期选择器的情况下，才能执行这些操作。

另外，你可以使用 `required` 属性强制填写日期，如果你尝试提交一个未填写日期的字段，将会抛出错误。即使优雅降级为 `text` 类型输入框，在大多数浏览器也是可以工作的。

让我们看一个例子——我们设置了日期的最大和最小值，并且将日期字段设定为必填：

```html
<form>
  <label>
    Choose your preferred party date (required, April 1st to 20th):
    <input
 type="date"
 name="party"
 min="2017-04-01"
 max="2017-04-20"
 required />
    <span class="validity"></span>
  </label>

  <p>
    <button>Submit</button>
  </p>
</form>
```

如果你尝试提交一个不完整日期的表单（或者超出日期选择设定范围），浏览器将会显示一个错误。尝试一下这个例子：

这是上面例子使用的 CSS。我们基于 `:valid` 和 `:invalid` 伪类来在输入框旁边添加小图标。我们必须把图标放在 input 旁边的 `<span>` 里面，而并不是它本身，因为在 Chrome 中被放置在表单中生成的内容不能有效的样式化或者显示。

```css
label {
  display: flex;
  align-items: center;
}

span::after {
  padding-left: 5px;
}

input:invalid + span::after {
  content: "✖";
}

input:valid + span::after {
  content: "✓";
}
```

**警告：** 客户端表单验证*不能替代*服务端验证。对于某人来说，对 HTML 进行调整以使其绕过验证或完全删除验证太容易了，甚至也可以完全绕开 HTML 并将数据直接提交到服务器。如果服务器端代码无法验证其接收到的数据，则在提交格式不正确的（或太大，类型错误……）的数据时，灾难可能会发生。

处理浏览器支持问题
---------

在不支持的浏览器上，日期输入框会被降级为文本输入框，但这同时带来了用户界面不统一（呈现的控件不同）和数据处理方面的问题。

第二个问题更为严重；正如我们早些时候提到的，对于日期输入框，实际值总是会被格式化为 `yyyy-mm-dd`。另一方面对于文本输入框，默认情况下浏览器并不知道日期格式应该怎么样被格式化，而且人们书写日期格式的方式有很多，例如：

* `ddmmyyyy`
* `dd/mm/yyyy`
* `mm/dd/yyyy`
* `dd-mm-yyyy`
* `mm-dd-yyyy`
* `Month dd yyyy`

解决这些问题的方法之一就是对日期控件设置 `pattern` 属性。即使日期输入不使用它，文本输入也会用到它。例如，请尝试在不支持的浏览器上看下面的例子：

```html
<form>
  <label>
    Enter your birthday:
    <input type="date" name="bday" required pattern="\d{4}-\d{2}-\d{2}" />
    <span class="validity"></span>
  </label>
  <p>
    <button>Submit</button>
  </p>
</form>
```

如果你尝试提交不符合模式 `####-##-##`（其中 `#` 是 0 到 9 的数字）的输入，你将会看到浏览器显示一个错误，并且高亮显示输入无效。当然，这并不能阻止人们输入无效的日期或者格式不正确的日期，因此我们需要更进一步。
```

span {
  position: relative;
}

span::after {
  right: -18px;
  position: absolute;
}

input:invalid + span::after {
  content: "✖";
}

input:valid + span::after {
  content: "✓";
}
```

目前以跨浏览器方式处理表单中日期的最佳方式是让用户在单独的控件中输入日、月和年，或者使用 JavaScript 库，例如 jQuery 日期选择器。

示例
--

在这个例子中，我们创建了两组用于选择日期的 UI 元素：一个原生 `<input type="date">` 选择器和一组由三个 `<select>` 元素用于在不支持本地输入的旧浏览器中提供日期选择。

### HTML

HTML 看起来像这样：

```html
<form>
  <div class="nativeDatePicker">
    <label for="bday">Enter your birthday:</label>
    <input type="date" id="bday" name="bday" />
    <span class="validity"></span>
  </div>
  <p class="fallbackLabel">Enter your birthday:</p>
  <div class="fallbackDatePicker">
    <span>
      <label for="day">Day:</label>
      <select id="day" name="day"></select>
    </span>
    <span>
      <label for="month">Month:</label>
      <select id="month" name="month">
        <option selected>January</option>
        <option>February</option>
        <option>March</option>
        <option>April</option>
        <option>May</option>
        <option>June</option>
        <option>July</option>
        <option>August</option>
        <option>September</option>
        <option>October</option>
        <option>November</option>
        <option>December</option>
      </select>
    </span>
    <span>
      <label for="year">Year:</label>
      <select id="year" name="year"></select>
    </span>
  </div>
</form>
```

月份是写死的（因为月份是固定的），而日和年的值是根据当前选择的月份和年份（日的判定需要月份和年份）以及当前年份动态生成的。请参阅下面的代码注释，它们详细的阐释了这些功能是如何工作的。
```

span {
  padding-left: 5px;
}

input:invalid + span::after {
  content: "✖";
}

input:valid + span::after {
  content: "✓";
}
```

### JavaScript

该代码中可能有趣的另一部分是特性检测代码——检测浏览器是否支持 `<input type="date">`。

我们创建一个新的 `<input>` 元素，尝试将其 `type` 设置为 `date`，然后立即检查其 `type` 值。不支持的浏览器将返回 `text`，因为 `date` 类型将回退为 `text` 类型。如果不支持 `<input type="date">`，我们将隐藏原生选择器并显示后备选择器 UI（`<select>` 元素）。

```js
// 获取 UI 元素
const nativePicker = document.querySelector(".nativeDatePicker");
const fallbackPicker = document.querySelector(".fallbackDatePicker");
const fallbackLabel = document.querySelector(".fallbackLabel");

const yearSelect = document.querySelector("#year");
const monthSelect = document.querySelector("#month");
const daySelect = document.querySelector("#day");

// 最初，隐藏回退元素
fallbackPicker.style.display = "none";
fallbackLabel.style.display = "none";

// 测试一个新的 date 输入框是否会回退至 text 输入框
const test = document.createElement("input");

try {
  test.type = "date";
} catch (e) {
  console.log(e.message);
}

// 如果回退了，运行 if 代码块中的代码
if (test.type === "text") {
  // 隐藏原生选择器，显示回退元素
  nativePicker.style.display = "none";
  fallbackPicker.style.display = "block";
  fallbackLabel.style.display = "block";

  // 动态生成天数和年数
  // 由于硬编码的缘故，月份总是相同的
  populateDays(monthSelect.value);
  populateYears();
}

function populateDays(month) {
  // 从当前的 <select> 中删除当前的 <option> 元素集，
  // 为下一个集合的注入做准备。
  while (daySelect.firstChild) {
    daySelect.removeChild(daySelect.firstChild);
  }

  // 创建保存注入新的天数的变量
  let dayNum;

  // 31 天还是 30 天？
  if (
    [
      "January",
      "March",
      "May",
      "July",
      "August",
      "October",
      "December",
    ].includes(month)
  ) {
    dayNum = 31;
  } else if (["April", "June", "September", "November"].includes(month)) {
    dayNum = 30;
  } else {
    // 如果是 2 月，计算其是否为闰年
    const year = yearSelect.value;
    const isLeap = new Date(year, 1, 29).getMonth() === 1;
    dayNum = isLeap ? 29 : 28;
  }

  // 将适当数量的新 <option> 元素注入到当前的 <select> 元素中。
  for (let i = 1; i <= dayNum; i++) {
    const option = document.createElement("option");
    option.textContent = i;
    daySelect.appendChild(option);
  }

  // 如果之前的天数已经设定，将 daySelect 的值设置为那一天，以防止当年数改变时天数被重置为1
  if (previousDay) {
    daySelect.value = previousDay;

    // 如果前一天被设定为一个较高的数字，例如31，然后你选择了一个总天数较少的月份（例如2月），
    // 这部分代码就会确保可用的最大日期被选中，而不是显示一个空白的 daySelect
    if (daySelect.value === "") {
      daySelect.value = previousDay - 1;
    }

    if (daySelect.value === "") {
      daySelect.value = previousDay - 2;
    }

    if (daySelect.value === "") {
      daySelect.value = previousDay - 3;
    }
  }
}

function populateYears() {
  // 获取今年的年份数字
  const date = new Date();
  const year = date.getFullYear();

  // 使今年以及之前的 100 年都能在 <select> 中选取。
  for (let i = 0; i <= 100; i++) {
    const option = document.createElement("option");
    option.textContent = year - i;
    yearSelect.appendChild(option);
  }
}

// 当月或年的 <select> 值改变时，重新运行 populateDays()
// 使得该变化影响到可用的天数
yearSelect.onchange = () => {
  populateDays(monthSelect.value);
};

monthSelect.onchange = () => {
  populateDays(monthSelect.value);
};

// 保留选择的天数
let previousDay;

// 更新之前设定的日期
// 用法请查看 populateDays() 的结尾
daySelect.onchange = () => {
  previousDay = daySelect.value;
};
```

**备注：** 请记住有些年份有 53 周（见**每年的周数**）！当你在开发产品应用时应当考虑这个问题。

规范
--

| Specification |
| --- |
| HTML Standard # date-state-(type=date) |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* 通用的 `<input>` 元素和用于操作该元素的接口 `HTMLInputElement`
* 日期时间选择器教程
* HTML 中使用的日期和时间格式
* 表单控件 CSS 兼容性列表


使用 dns-prefetch - Web 性能

使用 dns-prefetch
===============

**`DNS-prefetch`** 尝试在请求资源之前解析域名。这可能是后面要加载的文件，也可能是用户尝试打开的链接目标。

为什么要使用 dns-prefetch？
--------------------

当浏览器从（第三方）服务器请求资源时，必须先将该跨源域名解析为 IP 地址，然后浏览器才能发出请求。此过程称为 DNS 解析。虽然 DNS 缓存可以帮助减少此延迟，但 DNS 解析可能会给请求增加明显的延迟。对于打开了与许多第三方的连接的网站，此延迟可能会大大降低加载性能。

`dns-prefetch` 可帮助开发人员掩盖 DNS 解析延迟。HTML `<link>` 元素通过设置 `rel` 属性值为 `dns-prefetch` 提供此功能。然后在 href 属性中指明要跨源的域名：

语法
--

```html
<link rel="dns-prefetch" href="https://fonts.googleapis.com/" />
```

示例
--

```html
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link rel="dns-prefetch" href="https://fonts.googleapis.com/" />
    <!-- 其他 head 元素 -->
  </head>
  <body>
    <!-- 你的页面内容 -->
  </body>
</html>
```

每当站点引用跨源资源时，都应在 `<head>` 元素中放置 `dns-prefetch` 提示，但是要记住一些注意事项。

最佳实践
----

请记住以下三点：

**首先**，`dns-prefetch` 仅对跨源域上的 DNS 查找有效，因此请避免使用它来指向你的站点或域。这是因为，到浏览器看到提示时，你的站点背后的 IP 已经被解析了。

**其次**，还可以通过使用 HTTP Link 字段将 `dns-prefetch`（以及其他资源提示）指定为 HTTP 标头之一：

```http
Link: <https://fonts.googleapis.com/>; rel=dns-prefetch
```

**再其次**，考虑将 `dns-prefetch` 与 `preconnect` 提示配对。`dns-prefetch` 只执行 DNS 查询，而 `preconnect` 则是建立与服务器的连接。这个过程包括 DNS 解析，以及建立 TCP 连接，如果是 HTTPS 网站，就进一步执行 TLS 握手。将这两者结合起来，可以进一步减少跨源请求的感知延迟。你可以像这样安全地将它们结合起来使用：

```html
<link rel="preconnect" href="https://fonts.googleapis.com/" crossorigin />
<link rel="dns-prefetch" href="https://fonts.googleapis.com/" />
```

**备注：** 如果页面需要建立与许多第三方域的连接，则将它们预先连接会适得其反。`preconnect` 提示最好仅用于最关键的连接。对于其他的连接，只需使用 `<link rel="dns-prefetch">` 即可节省第一步——DNS 查询——的时间。

配对这些提示的逻辑是因为对 dns-prefetch 的支持比对预连接的支持要好。不支持预连接的浏览器仍然可以通过回退到 dns-prefetch 来获得更多好处。由于这是 HTML 特性，因此容错性很强。如果不支持的浏览器遇到 dns-prefetch 提示（或任何其他资源提示），则网站不会中断，只是不会获得它提供的好处。

一些资源（如字体）以匿名模式加载。在这种情况下，应使用预连接提示设置 crossorigin 属性。如果省略了它，则浏览器将仅执行 DNS 查找。

参见
--

* <link>
* HTML 属性：rel
* crossorigin
* 跨源资源共享（CORS）
* HTTP 标头
* HTTP 标头 Link


CSS 动画与 JavaScript 动画的性能 - Web 性能

CSS 动画与 JavaScript 动画的性能
========================

对众多应用程序而言，动画对提供友好的用户体验有着关键的作用。我们有很多方式生成 web 动画，比如 CSS `transition` 和 `animation` 或者基于 JavaScript 的动画（使用 `requestAnimationFrame()`）。在这篇文章中，我们分析 CSS 动画和 JavaScript 动画的性能差异。

CSS 过渡和动画
---------

CSS 中的 transition 和 animation 都可以用于编写动画，它们都有各自的使用场景：

* CSS `transition` 提供了一个简单的的方式去创造当前样式与结束状态样式之间的动画，比如一个 button 的普通状态和 hover 状态。尽管一个元素处于过渡状态中，新的过渡动画也会立即从当前样式开始，而不是直接跳转到 CSS 的最终状态。浏览使用 CSS transition 以获取更多细节。
* 另一方面，CSS `animation` 允许开发者去通过一个初始状态属性值集合与最终状态属性值集合创造动画，而不是单单的初始和最终状态。CSS animations 由两部分组成：描述 CSS 动画的样式，以及一组关键帧，表示动画样式的开始和结束状态，以及可能的中间状态。浏览使用 CSS animation 以获取更多细节。

就性能方面来说，无论通过 CSS animation 还是 transition 创造动画，都没有区别。在这篇文章中二者都归类为基于 CSS 的动画。

requestAnimationFrame
---------------------

`requestAnimationFrame()` 提供了一种用 JavaScript 代码制作动画的高效方式。本方法的回调函数在绘制下一帧之前由浏览器调用。与需要一个延迟参数的 `setTimeout()` 或 `setInterval()` 相比，`requestAnimationFrame()` 效率高得多。开发人员可以在 `requestAnimationFrame()` 回调函数中通过简单地改变元素的样式（或者更新画布绘制，等等）来创建动画。

**备注：** 像 CSS transitions 和 animations 一样，当页面在后台运行时，`requestAnimationFrame()` 会暂停。

更多细节请阅读 animating with JavaScript from setinterval to requestAnimationFrame.

性能对比：transitions vs. requestAnimationFrame
------------------------------------------

事实上，大多数场景下，基于 CSS 的动画几乎是跟 JavaScript 动画表现一致——至少在 FireFox 上是如此。一些基于 Javascript 的动画库，像 GSAP 和 Velocity.JS，甚至声称他们在性能上可以做得比原生 CSS transition/animation 更好。这是可能的，因为在重绘事件发生之前，CSS transition 和 animation 在主的 UI 线程仅仅是重新采集元素的样式，这跟通过 `requestAnimationFrame()` 回调获取重新采集元素样式是一样的，也是在下一次重绘之前触发。假如二者都是在主 UI 线程创建的动画，那它们在性能方面没有差异。

在这一节，我们将会使用 FireFox 通过一个性能测试，去看看哪种动画方式更好。

### 启用 FPS 工具

在进行示例之前，请启用 FPS 工具先查看当前帧速率：

1. 在地址栏中，输入 *about:config*，点击 `I'll be careful, I promise!` 按钮，以进入配置屏幕。

 ![](/zh-CN/docs/Web/Performance/CSS_JavaScript_animation_performance/pic1.png)
2. 在搜索栏中搜索 `layers.acceleration.draw-fps` 首选项。
3. 双击该条目将值设置为 `true`。现在你可以在 Firefox 窗口的左上角看到三个紫色的框。第一个框代表 FPS。

 ![](/zh-CN/docs/Web/Performance/CSS_JavaScript_animation_performance/pic2.png)
### 运行性能测试

开始在下面的测试中，总共 1000 个 `<div>` 元素通过 CSS 动画进行坐标转换。

```js
const boxes = [];
const button = document.getElementById("toggle-button");
const boxContainer = document.getElementById("box-container");
const animationType = document.getElementById("type");

// create boxes
for (let i = 0; i < 1000; i++) {
  const div = document.createElement("div");
  div.classList.add("css-animation");
  div.classList.add("box");
  boxContainer.appendChild(div);
  boxes.push(div.style);
}

let toggleStatus = true;
let rafId;
button.addEventListener("click", () => {
  if (toggleStatus) {
    animationType.textContent = " requestAnimationFrame";
    for (const child of boxContainer.children) {
      child.classList.remove("css-animation");
    }
    rafId = window.requestAnimationFrame(animate);
  } else {
    window.cancelAnimationFrame(rafId);
    animationType.textContent = " CSS animation";
    for (const child of boxContainer.children) {
      child.classList.add("css-animation");
    }
  }
  toggleStatus = !toggleStatus;
});

const duration = 6000;
const translateX = 500;
const rotate = 360;
const scale = 1.4 - 0.6;
let start;
function animate(time) {
  if (!start) {
    start = time;
    rafId = window.requestAnimationFrame(animate);
    return;
  }

  const progress = (time - start) / duration;
  if (progress < 2) {
    let x = progress \* translateX;
    let transform;
    if (progress >= 1) {
      x = (2 - progress) \* translateX;
      transform = `translateX(${x}px) rotate(${
 (2 - progress) \* rotate
 }deg) scale(${0.6 + (2 - progress) \* scale})`;
    } else {
      transform = `translateX(${x}px) rotate(${progress \* rotate}deg) scale(${
 0.6 + progress \* scale
 })`;
    }

    for (const box of boxes) {
      box.transform = transform;
    }
  } else {
    start = null;
  }
  rafId = window.requestAnimationFrame(animate);
}
```

```
<div id="header">
  <button id="toggle-button">切换</button>
  <span id="type">CSS Animation</span>
</div>
<div id="box-container"></div>
```

```
#header {
  position: sticky;
  top: 0.5rem;
  margin: 0 0.5rem;
  z-index: 100;
  background-color: lightgreen;
}

#box-container {
  margin-top: 1.5rem;
  display: grid;
  grid-template-columns: repeat(40, 1fr);
  gap: 15px;
}

.box {
  width: 30px;
  height: 30px;
  background-color: red;
}

.css-animation {
  animation: animate 6s linear 0s infinite alternate;
}

@keyframes animate {
  0% {
    transform: translateX(0) rotate(0deg) scale(0.6);
  }
  100% {
    transform: translateX(500px) rotate(360deg) scale(1.4);
  }
}
```

动画可以通过点击“切换”按钮切换到 `requestAnimationFrame()`。

试着两个都运行一下，比较两者的 FPS 值（第一个紫色框），可以看到 CSS 动画和 `requestAnimationFrame()` 是非常接近的。

### 脱离主线程的动画

即使是上面给出的测试结果，我们仍然认为 CSS 动画是更好的选择。为什么？关键是只要动画涉及的属性不引起 reflow（重新布局）（参考 CSS trigger 获得更多信息），我们可以把采样操作移出主线程。最常见的属性是 CSS transform。如果一个元素被提升为一个 layer，transform 属性动画就可以在 GPU 中进行。这意味着更好地性能，特别是在移动设备上。在 OffMainThreadCompositing 上寻找更多细节。

要在火狐中激活 OMTA（脱离主线程的动画） ，你需要前往 *about:config* 然后搜索 `layers.offmainthreadcomposition.async-animations`，将其切换到 `true`。

![](/zh-CN/docs/Web/Performance/CSS_JavaScript_animation_performance/pic3.png)

激活之后，再次运行上面的例子。可以看到 CSS 动画的帧速率现在高多了。

**备注：** 在 Nightly 和 Developer 版本，你可能看到 OMTA 是默认激活的，所以你需要反过来测试（先测试激活 OMTA 的情况，然后是没有激活的情况）。

总结
--

浏览器可以优化渲染流程。总之，我们总是可以尽可能通过 CSS transition 和 animation 创建动画。如果你的动画真的很复杂，你可能不得不依赖于 JavaScript 动画。


渲染页面：浏览器的工作原理 - Web 性能

渲染页面：浏览器的工作原理
=============

页面内容快速加载和流畅的交互是用户希望得到的 Web 体验，因此，开发者应力争实现这两个目标。

了解如何提升性能和感知性能，有助于了解浏览器的工作原理。

概述
--

快速响应的网站提供更好的用户体验。用户期待内容快速加载和交互流畅的 Web 体验。

等待资源加载时间和大部分情况下的浏览器单线程执行是影响 Web 性能的两大主要原因。

等待时间是需要去克服来让浏览器快速加载资源的主要威胁。为了实现快速加载，开发者的目标就是尽可能快的发送请求的信息，至少看起来相当快。网络等待时间是在链路上传送二进制到电脑端所消耗的链路传输时间。Web 性能优化需要做的就是尽可能快的使页面加载完成。

大部分情况下，浏览器是单线程执行的。为了有流畅的交互，开发者的目标是确保网站从流畅的页面滚动到点击响应的交互性能。渲染时间是关键要素，确保主线程可以完成所有给它的任务并且仍然一直可以处理用户的交互。通过了解浏览器单线程的本质与最小化主线程的责任可以优化 Web 性能，来确保渲染的流畅和交互响应的及时。

导航
--

*导航*是加载 web 页面的第一步。它发生在以下情形：用户通过在地址栏输入一个 URL、点击一个链接、提交表单或者是其他的行为。

Web 性能优化的目标之一就是缩短导航完成所花费的时间，在理想情况下，它通常不会花费太多的时间，但是等待时间和带宽会导致它的延时。

### DNS 查询

对于一个 web 页面来说导航的第一步是要去寻找页面资源的位置。如果导航到 `https://example.com`，HTML 页面被定位到 IP 地址为 `93.184.216.34` 的服务器。如果以前没有访问过这个网站，就需要进行 DNS 查询。

浏览器向**名称服务器**发起 DNS 查询请求，最终得到一个 IP 地址。第一次请求之后，这个 IP 地址可能会被缓存一段时间，这样可以通过从缓存里面检索 IP 地址而不是再通过名称服务器进行查询来加速后续的请求。

通过主机名加载一个页面通常仅需要一次 DNS 查询。但是，对于页面指向的不同的主机名，则需要多次 DNS 查询。如果字体（font）、图像（image）、脚本（script）、广告（ads）和网站统计（metric）都有不同的主机名，则需要对每一个主机名进行 DNS 查询。

![移动终端的请求先发送到基站，接着发送到运营商的中心计算机，然后再发送到互联网](/zh-CN/docs/Web/Performance/How_browsers_work/latency.jpg)

DNS 查询可能存在性能问题，特别是对于移动网络。当一个用户使用了移动网络，每一个 DNS 查询必须从手机发送到基站，然后到达一个认证的 DNS 服务器。手机、信号塔、名称服务器之间的距离可能是一个大的时间等待。

### TCP 握手

一旦获取到服务器 IP 地址，浏览器就会通过 TCP“三次握手” (en-US)与服务器建立连接。这个机制的是用来让两端尝试进行通信——在浏览器和服务器通过上层协议 HTTPS 发送数据之前，可以协商网络 TCP 套接字连接的一些参数。

TCP 的“三次握手”技术经常被称为“SYN-SYN-ACK”——更确切的说是 SYN、SYN-ACK、ACK——因为通过 TCP 首先发送了三个消息进行协商，然后在两台电脑之间开始一个 TCP 会话。是的，这意味着终端与每台服务器之间还要来回发送三条消息，而请求尚未发出。

### TLS 协商

对于通过 HTTPS 建立的安全连接，还需要另一次 "握手"。这种握手，或者说 TLS 协商，决定使用哪种密码对通信进行加密，验证服务器，并在开始实际数据传输前建立安全连接。这就需要在实际发送内容请求之前，再往返服务器五次。

![DNS 查询、TCP 握手和 TLS 5 步握手（包括服务器和客户端之间的 clienthello、serverhello 以及证书、clientkey 和完成消息）。](/zh-CN/docs/Web/Performance/How_browsers_work/ssl.jpg)

虽然建立安全连接对增加了加载页面的等待时间，对于建立一个安全的连接来说，以增加等待时间为代价是值得的，因为在浏览器和 web 服务器之间传输的数据不可以被第三方解密。

经过 8 次往返，浏览器终于可以发出请求。

响应
--

一旦我们建立了到 web 服务器的连接，浏览器就代表用户发送一个初始的 HTTP `GET` 请求，对于网站来说，这个请求通常是一个 HTML 文件。一旦服务器收到请求，它将使用相关的响应头和 HTML 的内容进行回复。

```html
<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <title>简单的页面</title>
    <link rel="stylesheet" href="styles.css" />
    <script src="myscript.js"></script>
  </head>
  <body>
    <h1 class="heading">我的页面</h1>
    <p>含有<a href="https://example.com/about">链接</a>的段落。</p>
    <div>
      <img src="myimage.jpg" alt="图像描述" />
    </div>
    <script src="anotherscript.js"></script>
  </body>
</html>
```

初始请求的响应包含所接收数据的第一个字节。首字节时间（TTFB）是用户通过点击链接进行请求与收到第一个 HTML 数据包之间的时间。第一个内容分块通常是 14KB 的数据。

上面的示例中，这个请求肯定是小于 14KB 的，但是直到浏览器在解析阶段遇到链接时才会去请求链接的资源，下面有进行描述。

### 拥塞控制 / TCP 慢启动

TCP 数据包在传输过程中被分成若干段。由于 TCP 保证数据包的顺序，因此服务器在发送一定数量的数据包后，必须以 ACK 数据包的形式收到客户端的确认。

如果服务器在每个网段后都等待 ACK，则会导致客户端频繁发出 ACK，即使在网络负荷较低的情况下也会增加传输时间。

另一方面，一次性发送过多网段可能会导致这样的问题：在繁忙的网络中，客户端无法接收到网段，只能长时间不停地回应 ACK，服务器不得不不断重新发送网段。

为了平衡传输段的数量，TCP 慢启动算法用于逐渐增加传输数据量，直到确定最大网络带宽，并在网络负载较高时减少传输数据量。

传输段的数量由拥塞窗口（CWND）的值控制，该值可初始化为 1、2、4 或 10 MSS（以太网协议中的 MSS 为 1500 字节）。该值是发送的字节数，客户端收到后必须发送 ACK。

如果收到 ACK，那么 CWND 值将加倍，这样服务器下次就能发送更多的数据段。相反，如果没有收到 ACK，那么 CWND 值将减半。因此，这种机制在发送过多网段和过少网段之间取得了平衡。

解析
--

一旦浏览器收到数据的第一块，它就可以开始解析收到的信息。“解析”是浏览器将通过网络接收的数据转换为 DOM 和 CSSOM 的步骤，通过渲染器把 DOM 和 CSSOM 在屏幕上绘制成页面。

DOM 是浏览器标记的内部表示。DOM 也是被暴露的，可以通过 JavaScript 中的各种 API 进行 DOM 操作。

即使请求页面的 HTML 大于初始的 14KB 数据包，浏览器也将开始解析并尝试根据其拥有的数据进行渲染。这就是为什么在前 14KB 中包含浏览器开始渲染页面所需的所有内容，或者至少包含页面模板（第一次渲染所需的 CSS 和 HTML）对于 web 性能优化来说是重要的。但是在渲染到屏幕上面之前，HTML、CSS、JavaScript 必须被解析完成。

### 构建 DOM 树

我们在关键渲染路径这篇文章中描述了五个步骤。

第一步是处理 HTML 标记并构造 DOM 树。HTML 解析涉及到符号化和树的构造。HTML 标记包括开始和结束标记，以及属性名和值。如果文档格式良好，则解析它会简单而快速。解析器将标记化的输入解析到文档中，构建文档树。

DOM 树描述了文档的内容。`<html>` 元素是第一个标签也是文档树的根节点。树反映了不同标记之间的关系和层次结构。嵌套在其他标记中的标记是子节点。DOM 节点的数量越多，构建 DOM 树所需的时间就越长。

![我们示例代码的 DOM 树，显示了所有节点（包括文本节点）。](/zh-CN/docs/Web/Performance/How_browsers_work/dom.gif)

当解析器发现非阻塞资源，例如一张图片，浏览器会请求这些资源并且继续解析。当遇到一个 CSS 文件时，解析也可以继续进行，但是对于 `<script>` 标签（特别是没有 `async` 或者 `defer` 属性的）会阻塞渲染并停止 HTML 的解析。尽管浏览器的预加载扫描器加速了这个过程，但过多的脚本仍然是一个重要的瓶颈。

### 预加载扫描器

浏览器构建 DOM 树时，这个过程占用了主线程。当这种情况发生时，预加载扫描仪将解析可用的内容并请求高优先级资源，如 CSS、JavaScript 和 web 字体。多亏了预加载扫描器，我们不必等到解析器找到对外部资源的引用来请求它。它将在后台检索资源，以便在主 HTML 解析器到达请求的资源时，它们可能已经在运行，或者已经被下载。预加载扫描仪提供的优化减少了阻塞。

```html
<link rel="stylesheet" href="styles.css" />
<script src="myscript.js" async></script>
<img src="myimage.jpg" alt="图像描述" />
<script src="anotherscript.js" async></script>
```

在这个例子中，当主线程在解析 HTML 和 CSS 时，预加载扫描器将找到脚本和图像，并开始下载它们。为了确保脚本不会阻塞进程，当 JavaScript 解析和执行顺序不重要时，可以添加 `async` 属性或 `defer` 属性。

等待获取 CSS 不会阻塞 HTML 的解析或者下载，但是它确实会阻塞 JavaScript，因为 JavaScript 经常用于查询元素的 CSS 属性。

### 构建 CSSOM 树

第二步是处理 CSS 并构建 CSSOM 树。CSS 对象模型和 DOM 是相似的。DOM 和 CSSOM 是两棵树。它们是独立的数据结构。浏览器将 CSS 规则转换为可以理解和使用的样式映射。浏览器遍历 CSS 中的每个规则集，根据 CSS 选择器创建具有父、子和兄弟关系的节点树。

与 HTML 一样，浏览器需要将接收到的 CSS 规则转换为可以使用的内容。因此，它重复了 HTML 到对象的过程，但对于 CSS。

CSSOM 树包括来自用户代理样式表的样式。浏览器从适用于节点的最通用规则开始，并通过应用更具体的规则递归地优化计算的样式。换句话说，它级联属性值。

构建 CSSOM 非常快，并且在当前的开发工具中没有以独特的颜色显示。相反，开发人员工具中的“重新计算样式”显示解析 CSS、构建 CSSOM 树和递归计算计算样式所需的总时间。在 web 性能优化方面，它是可轻易实现的，因为创建 CSSOM 的总时间通常小于一次 DNS 查询所需的时间。

### 其他过程

#### JavaScript 编译

在解析 CSS 和创建 CSSOM 的同时，包括 JavaScript 文件在内的其他资源也在下载（这要归功于预加载扫描器）。JavaScript 会被解析、编译和解释。脚本被解析为抽象语法树。有些浏览器引擎会将抽象语法树输入编译器，输出字节码。这就是所谓的 JavaScript 编译。大部分代码都是在主线程上解释的，但也有例外，例如在 web worker 中运行的代码。

#### 构建无障碍树

浏览器还构建辅助设备用于分析和解释内容的无障碍树。无障碍对象模型（AOM）类似于 DOM 的语义版本。当 DOM 更新时，浏览器会更新辅助功能树。辅助技术本身无法修改无障碍树。

在构建 AOM 之前，屏幕阅读器 (en-US)无法访问内容。

渲染
--

渲染步骤包括样式、布局、绘制，在某些情况下还包括合成。在解析步骤中创建的 CSSOM 树和 DOM 树组合成一个渲染树，然后用于计算每个可见元素的布局，然后将其绘制到屏幕上。在某些情况下，可以将内容提升到它们自己的层并进行合成，通过在 GPU 而不是 CPU 上绘制屏幕的一部分来提高性能，从而释放主线程。

### 样式

关键呈现路径的第三步是将 DOM 和 CSSOM 组合成渲染树。计算样式树或渲染树的构建从 DOM 树的根开始，遍历每个可见节点。

不会被显示的元素，如 `<head>` 元素及其子元素，以及任何带有 `display: none` 的节点，如用户代理样式表中的 `script { display: none; }`，都不会包含在渲染树中，因为它们不会出现在渲染输出中。应用了 `visibility: hidden` 的节点会包含在渲染树中，因为它们会占用空间。由于我们没有给出任何指令来覆盖用户代理默认值，因此上述代码示例中的 `script` 节点不会包含在渲染树中。

每个可见节点都应用了 CSSOM 规则。渲染树包含所有可见节点的内容和计算样式，将所有相关样式与 DOM 树中的每个可见节点匹配起来，并根据 CSS 级联，确定每个节点的计算样式。

### 布局

第四步是在渲染树上运行布局以计算每个节点的几何体。*布局*是确定呈现树中所有节点的宽度、高度和位置，以及确定页面上每个对象的大小和位置的过程。回流是对页面的任何部分或整个文档的任何后续大小和位置的确定。

构建渲染树后，开始布局。渲染树标识显示哪些节点（即使不可见）及其计算样式，但不标识每个节点的尺寸或位置。为了确定每个对象的确切大小和位置，浏览器从渲染树的根开始遍历它。

在网页上，大多数东西都是一个盒子。不同的设备和不同的桌面意味着无限数量的不同的视区大小。在此阶段，考虑到视口大小，浏览器将确定屏幕上所有不同框的尺寸。以视口的大小为基础，布局通常从 body 开始，用每个元素的框模型属性排列所有 body 的子孙元素的尺寸，为不知道其尺寸的替换元素（例如图像）提供占位符空间。

第一次确定节点的大小和位置称为布局。随后对节点大小和位置的重新计算称为回流。在我们的示例中，假设初始布局发生在返回图像之前。由于我们没有声明图像的尺寸，因此一旦知道图像的尺寸，就会出现回流。

### 绘制

最后一步是将各个节点绘制到屏幕上，第一次出现的节点称为 first meaningful paint (en-US)。在绘制或光栅化阶段，浏览器将在布局阶段计算的每个框转换为屏幕上的实际像素。绘画包括将元素的每个可视部分绘制到屏幕上，包括文本、颜色、边框、阴影和替换的元素（如按钮和图像）。浏览器需要非常快地完成这项工作。

为了确保平滑滚动和动画，占据主线程的所有内容，包括计算样式，以及回流和绘制，必须让浏览器在 16.67 毫秒内完成。在 2048x1536 分辨率的 iPad 上，有超过 314.5 万像素将被绘制到屏幕上。那是很多像素需要快速绘制。为了确保重绘的速度比初始绘制的速度更快，屏幕上的绘图通常被分解成数层。如果发生这种情况，则需要进行合成。

绘制可以将布局树中的元素分解为多个层。将内容提升到 GPU 上的层（而不是 CPU 上的主线程）可以提高绘制和重新绘制性能。有一些特定的属性和元素可以实例化一个层，包括 `<video>` 和 `<canvas>`，任何 CSS 属性为 `opacity` 、3D `transform`、`will-change` 的元素，还有一些其他元素。这些节点将与子节点一起绘制到它们自己的层上，除非子节点由于上述一个（或多个）原因需要自己的层。

分层确实可以提高性能，但是它以内存管理为代价，因此不应作为 web 性能优化策略的一部分过度使用。

### 合成

当文档的各个部分以不同的层绘制，相互重叠时，必须进行合成，以确保它们以正确的顺序绘制到屏幕上，并正确显示内容。

当页面继续加载资源时，可能会发生回流（回想一下我们迟到的示例图像），回流会触发重新绘制和重新组合。如果我们定义了图像的大小，就不需要重新绘制，只需要重新绘制需要重新绘制的层，并在必要时进行合成。但我们没有包括图像大小！从服务器获取图像后，渲染过程将返回到布局步骤并从那里重新开始。

交互
--

一旦主线程绘制页面完成，你会认为我们已经“准备好了”，但事实并非如此。如果加载包含 JavaScript（并且延迟到 `onload` 事件触发后执行），则主线程可能很忙，无法用于滚动、触摸和其他交互。

可交互时间（TTI） (en-US)是测量从第一个请求导致 DNS 查询和 SSL 连接到页面可交互时所用的时间——可交互是 First Contentful Paint (en-US) 之后的时间点，页面在 50ms 内响应用户的交互。如果主线程正在解析、编译和执行 JavaScript，则它不可用，因此无法及时（小于 50ms）响应用户交互。

在我们的示例中，可能图像加载很快，但 `anotherscript.js` 文件可能是 2MB，而且用户的网络连接很慢。在这种情况下，用户可以非常快地看到页面，但是在下载、解析和执行脚本之前，就无法滚动。这不是一个好的用户体验。避免占用主线程，如下面的网页测试示例所示：

![通过快速连接，主线程被 JavaScript 文件下载、解析和执行占用](/zh-CN/docs/Web/Performance/How_browsers_work/visa_network.png)

在本例中，DOM 内容加载过程花费了超过 1.5 秒的时间，主线程在这段时间内完全被占用，对单击事件或屏幕点击没有响应。

参见
--

* Web 性能


优化启动性能 - Web 性能

优化启动性能
======

一个在软件应用开发中经常被忽视的方面—即便在那些专注于性能优化的软件中—就是启动时的表现。你的应用需要花费多长时间启动？当应用加载时是否会卡住用户的设备或浏览器？这会让用户担心你的应用崩溃了，或者哪儿出错了。花时间确保你的应用能够更好地启动总是一个好主意。这篇文章提供了一些技巧和建议来帮助你达成这个目标，不管是在写一个新的应用还是从其他平台向 Web 移植一个应用。

更好地启动
-----

不论在什么平台上，尽可能**快**地启动总是一个好主意。因为这是个很宽泛的问题，在这里我们不会着重关注。相反我们会关注构建 Web 应用时更重要的一个问题：尽可能**异步地**启动。这意味着不要将你所有的启动代码在应用主线程中的唯一一个事件处理函数中运行。

相反，你应该这样写你的代码，让你的应用在后台线程创建一个 Web worker ，做尽可能多的工作（比如，获取和处理数据）。然后，所有必须在主线程中完成的事情（比如用户事件和渲染用户界面）应该被分成小的片段，这样，当应用启动时，应用的事件循环就可以持续地运行下去。这可以避免应用、浏览器以及/或者设备出现锁死。

为什么异步性很重要？除了上面提出的原因，还考虑了无响应页面或用户界面的影响。如果用户错误地启动了应用，将不能取消。如果应用正在浏览器中运行，用户可能会收到一个“应用无响应”或“加载缓慢”的提醒。你应该显示几种界面，比如进度条，这样用户可以在应用启动时知道他们需要等待多长时间。

如果有这样的打算
--------

如果你从头开始你的项目，通常很容易把所有的东西都写成“正确的方式”，使得代码片段具有合适的异步性。所有纯粹在启动时的计算应该在后台线程中执行，同时使主线程事件的运行时间尽可能缩短。应包含进度指示器，以便用户知道发生了什么以及他们将要等待多久。从理论上来说，无论如何，设计新的应用程序并能很好地启动应该很容易。

但是，另一方面，当你将现有应用程序移植到 Web 上时，问题会变得棘手。桌面应用程序不需要以异步方式编写，因为通常操作系统会为你处理该问题；或者应用程序当前是唯一正在运行的主要任务，而这具体取决于操作环境。源程序可能有一个主循环，可以被轻松地改成异步操作（通过分别运行每个主循环）；启动通常只是一个持续的整体过程，过程中可能会定期更新进度表。

虽然你可以使用 Web workers 异步运行体积巨大，持续时间长的 JavaScript 代码块，但还是要给出一个重大警告：workers 不具备访问 WebGL 或音频的能力，亦不能向主线程发送同步消息，所以你甚至不能将这些 API 代理到主线程中。所有的这一切意味着，除非你够轻松地抽取启动过程中的“纯计算”代码块，加入到 workers 中，否则你最后还是得在主线程上运行大部分或全部的启动代码。

但是即便是那样的代码，通过一点点工作，也可以变为异步的。

异步化
---

关于如何构建你的启动过程，使得其尽可能异步执行，这里有些建议。（不论是新应用还是移植的）：

* 启动时，在需要异步执行的脚本标签上使用 `defer` 或 `async` 属性。这会允许 HTML 解析器更高效地处理文档。 Async scripts for asm.js 中有更多关于这方面的信息。
* 如果你需要解码资源文件（比如，解码 JPEG 文件并将其转换为原始纹理数据，以便随后在 WebGL 中使用），最好在 workers 里做这件事。
* 当处理浏览器支持的数据格式时（例如，解析图像数据），使用设备或浏览器内置的解码器而不是运行你自己的或者使用 or using one from the original codebase。预先提供的那个基本上一定会快得多，并且能够减小你的应用的启动体积。另外，浏览器可以自动并行化这些解码器的工作。
* 所有能并行的数据处理都应该并行化。不要一团接一团地处理数据，如果可能的话，同时处理它们！
* 在你启动的 HTML 文件中，不要包含不会在关键路径下出现的脚本或样式表。只在需要时加载他们。
* 不要强迫 Web 引擎构建不需要的 DOM，一种简单的“hack”的方式是把你的 HTML 留在文档里，但是在外层包裹注释。

```html
<div id="foo">
  <!--
 <div> ...
 -->
</div>
```

* 当文档的一部分需要被渲染时，加载被注释的 HTML
```

foo.innerHTML = foo.firstChild.nodeValue;
```

你能通过异步的方式做的事越多，应用就能更好的利用多核处理器。

### 移植问题

一旦初始加载完成，应用的主程序开始运行，有可能你的应用一定会是单线程的，尤其当它是个移植的程序时。尝试改善主程序的启动过程时，最重要的事情是将代码重构为小片段。这些小片段可以在你应用的主循环中通过多个调用在分散的代码段中执行，这样主线程就能处理输入和类似的事件。

Emscripten 提供了一个 API 帮助处理这种重构；比如你可以用 `emscripten_push_main_loop_blocker()` 创建一个函数，在主线程可以继续运行前执行。通过创建一个可以依序调用的函数队列，你可以更轻松地在不阻塞主线程地情况下管理代码的运行。

但是这留下了不得不重构现有代码，使程序真正这样运行的问题。这可能需要一些时间。

### 我究竟要异步化到什么程度？

最好记住大多数浏览器，在你的代码阻塞主线程太长时间——大约 10 秒左右的时候，就会开始抱怨。理想情况下，你不会在任何地方阻塞那么长时间，但是只要你把时间保持在这之下，就不会有问题。不过要记住，如果有人有个比你更旧更慢的电脑，他们或许会比你经历更长的等待！

其他建议
----

除了异步化之外，还有一些其他的事情，可以帮助你改善应用启动时间。这是其中的一部分：

下载时间

一定要记住用户需要花多少时间下载你游戏的数据。如果你的游戏非常大，很受欢迎，或者需要频繁的重下内容，你应该考虑买个速度越快越好托管服务器。你还应该考虑压缩数据，尽可能缩小其体积。

GPU 因素

编译着色器，以及将纹理传输到 GPU 会占用时间，特别是在比较复杂的游戏中。尽管这也会发生在本地 (非 Web ) 游戏中，但还是会很恼人。不要不告诉用户游戏实际上还在启动中就这么做。

数据大小

尽力优化游戏数据的体积，小一些的文件下载和处理都比大文件快。

主观因素

你可以在启动过程中做一些事情来使用户专注于其上，这会让时间看起来过得更快些。就游戏而言，可以考虑播放一些背景音乐或者显示漂亮的启动画面；在运算执行期间，更新你的进度提示，改变显示内容，或者做任何你想做的事情：这有助于用户知晓应用正在做一些工作，而不是一声不吭地呆在那儿。

参见
--

* Apps
* Games


懒加载 - Web 性能

懒加载
===

**延迟加载**（懒加载）是一种将资源标识为非阻塞（非关键）资源并仅在需要时加载它们的策略。这是一种缩短关键渲染路径长度的方法，可以缩短页面加载时间。

延迟加载可以在应用程序的不同时刻发生，但通常会在某些用户交互（例如滚动和导航）上发生。

概览
--

随着网络的发展，发送给用户的资源数量和规模都在急剧增加。从 2011 年到 2019 年，桌面端的资源中位数从 **~100KB** 增至 **~400KB**，移动端的资源中位数从 **~50KB** 增至 **~350KB**。桌面端上图片资源的大小已经从 **~250KB** 增至 **~900KB**，而移动设备上的图片 **~100KB** 增至 **~850KB**。

解决这个问题的方法之一是延迟加载对第一次渲染并不重要的资源来缩短关键渲染路径的长度。一个实际例子是，当用户打开一个电商网站的主页时，该页面有一个指向购物车页面/区域的链接，并且**只有**在用户导航到购物车页面/区域时才会下载其所有资源（如 JavaScript、CSS 和图片）。

策略
--

延迟加载可以通过多种策略应用于多个资源。

### 常规

#### 代码拆分

JavaScript、CSS 和 HTML 可以被分割成较小的代码块。这样就可以在前期发送所需的最小代码，改善页面加载时间。其余的可以按需加载。

* 入口点分离：通过应用的入口点分离代码
* 动态分离：通过动态 import() 语句分离代码
### JavaScript

#### 脚本类型模块

任何类型为 `type="module"` 的脚本标签都被视为一个 JavaScript 模块，并且默认情况下会被延迟。

### CSS

默认情况下，CSS 被视为渲染阻塞资源，因此，在 CSSOM 构造完成之前，浏览器不会渲染任何已处理的内容。CSS 必须尽量小，才能尽快送达，建议使用媒体类型和查询实现非阻塞渲染。

```html
<link href="style.css" rel="stylesheet" media="all" />
<link href="portrait.css" rel="stylesheet" media="(orientation:portrait)" />
<link href="print.css" rel="stylesheet" media="print" />
```

可以执行一些 CSS 优化来实现这一目标。

### 字体

默认情况下，字体请求会延迟到构造渲染树之前，这可能会导致文本渲染延迟。

可以使用 `<link rel="preload">`、CSS font-display 属性和字体加载 API 来覆盖默认行为并预加载网络字体资源。

参见 Link 元素。

### 图片和 iframe

很多时候，网页包含许多图片，这些图片会影响数据的使用和网页的加载速度。这些图片大部分都在屏幕之外（非关键），需要用户互动，如滚动，才能看到它们。

#### Loading 属性

`<img>` 元素上的 `loading` 属性（或 `<iframe>` 上的 `loading` 属性）可用于指示浏览器推迟加载屏幕外的图像/iframe，直到用户滚动到其附近。

```html
<img src="image.jpg" alt="..." loading="lazy" />
<iframe src="video-player.html" title="..." loading="lazy"></iframe>
```

当早期加载的内容全部加载完毕时，`load` 事件就会触发；这时，完全有可能（甚至有可能）在可视视口 (en-US)范围内有一些延迟加载的图片还没有加载。

你可以通过检查其布尔属性 `complete` (en-US) 的值来确定某个图片是否已经完成加载。

#### Polyfill

包含这个 polyfill，以提供对旧的和目前不兼容的浏览器的支持：loading-attribute-polyfill。

#### Intersection Observer API

Intersection Observer 允许用户得知被观察的元素何时进入或退出浏览器的视口。

#### 事件处理器

当浏览器的兼容性至关重要时，有以下几种选择：

* polyfill intersection observer
* 回退到滚动、调整大小或方向变化事件处理器，以确定特定元素是否在视口中
规范
--

| Specification |
| --- |
| HTML Standard # lazy-loading-attributes |

参见
--

* 渲染阻塞 CSS
* 使用延迟加载来提高加载速度


关键渲染路径 - Web 性能

关键渲染路径
======

**关键渲染路径**是浏览器将 HTML，CSS 和 JavaScript 转换为屏幕上的像素所经历的步骤序列。优化关键渲染路径可提高渲染性能。关键渲染路径包含了 文档对象模型(DOM)，CSS 对象模型 (CSSOM)，渲染树和布局。

在解析 HTML 时会创建文档对象模型。HTML 可以请求 JavaScript，而 JavaScript 反过来，又可以更改 DOM。HTML 包含或请求样式，依次来构建 CSS 对象模型。浏览器引擎将两者结合起来以创建渲染树。布局确定页面上所有内容的大小和位置。确定布局后，将像素绘制到屏幕上。

优化关键渲染路径可以缩短首次渲染的时间。了解和优化关键渲染路径对于确保重排和重绘可以每秒 60 帧的速度进行，以确保高效的用户交互并避免卡顿是很重要的。

理解 CRP
------

Web 性能包含了服务器请求和响应、加载、执行脚本、渲染、布局和绘制每个像素到屏幕上。

网页请求从 HTML 文件请求开始。服务器返回 HTML——响应头和数据。然后浏览器开始解析 HTML，转换收到的数据为 DOM 树。浏览器每次发现外部资源就初始化请求，无论是样式、脚本或者嵌入的图片引用。有时请求会阻塞，这意味着解析剩下的 HTML 会被终止直到重要的资源被处理。浏览器接着解析 HTML，发请求和构造 DOM 直到文件结尾，这时开始构造 CSS 对象模型。等到 DOM 和 CSSOM 完成之后，浏览器构造渲染树，计算所有可见内容的样式。一旦渲染树完成布局开始，定义所有渲染树元素的位置和大小。完成之后，页面被渲染完成，或者说是绘制到屏幕上。

### 文档对象模型

DOM 构建是增量的。HTML 响应变成令牌（token），令牌变成节点，而节点又变成 DOM 树。单个 DOM 节点以 startTag 令牌开始，以 endTag 令牌结束。节点包含有关 HTML 元素的所有相关信息。该信息是使用令牌描述的。节点根据令牌层次结构连接到 DOM 树中。如果另一组 startTag 和 endTag 令牌位于一组 startTag 和 endTag 之间，则你在节点内有一个节点，这就是我们定义 DOM 树层次结构的方式。

节点数量越多，关键渲染路径中的后续事件将花费的时间就越长。测一下吧！几个额外的节点不会有什么区别，但“DIV 癖”（divitis）可能会导致问题。

### CSS 对象模型

DOM 包含页面所有的内容。CSSOM 包含了页面所有的样式，也就是如何展示 DOM 的信息。CSSOM 跟 DOM 很像，但是不同。DOM 构造是增量的，CSSOM 却不是。CSS 是渲染阻塞的：浏览器会阻塞页面渲染直到它接收和执行了所有的 CSS。CSS 是渲染阻塞是因为规则可以被覆盖，所以内容不能被渲染直到 CSSOM 的完成。

CSS 有其自身的规则集合用来定义标识。注意 CSS 中的 C 代表的是“层叠”。CSS 规则是级联的。随着解析器转换标识为节点，节点的后代继承了样式。像处理 HTML 那样的增量处理功能没有被应用到 CSS 上，因为后续规则可能被之前的所覆盖。CSS 对象模型随着 CSS 的解析而被构建，但是直到完成都不能被用来构建渲染树，因为样式将会被之后的解析所覆盖而不应该被渲染到屏幕上。

从选择器性能的角度，更少的特定选择器是比更多的要快。例如，`.foo {}` 是比 `.bar .foo {}` 更快的因为当浏览器发现 `.foo` ，接下来必须沿着 DOM 向上走来检查 `.foo` 是不是有一个祖先 `.bar`。越是具体的标签浏览器就需要更多的工作，但这样的弊端未必值得优化。

如果你测量过解析 CSS 的时间，你将会被浏览器实在地快所震惊。更具体的规则更昂贵因为它必须遍历更多的 DOM 树节点，但这所带来的额外的消耗通常很小。先测量一下。然后按需优化。特定化或许不是你的低垂的果实。在 CSS 中选择器的性能优化，提升仅仅是毫秒级的。有其他一些方式来优化 CSS，例如压缩和使用媒体查询来异步处理 CSS 为非阻塞的请求。

### 渲染树

渲染树包括了内容和样式：DOM 和 CSSOM 树结合为渲染树。为了构造渲染树，浏览器检查每个节点，从 DOM 树的根节点开始，并且决定哪些 CSS 规则被添加。

渲染树只包含了可见内容。头部（通常）不包含任何可见信息，因此不会被包含在渲染树中。如果有元素上有 `display: none;`，它本身和其后代都不会出现在渲染树中。

### 布局

一旦渲染树被构建，布局变成了可能。布局取决于屏幕的尺寸。布局这个步骤决定了在哪里和如何在页面上放置元素，决定了每个元素的宽和高，以及他们之间的相关性。

什么是一个元素的宽？块级元素，根据定义，默认有父级宽度的 100%。一个宽度 50% 的元素，将占据父级宽度的一半。除非另外定义，body 有 100% 的宽，意味着它占据视窗的 100%。设备的宽度影响布局。

视窗的元标签定义了布局视窗的宽度，从而影响布局。没有的话，浏览器使用视窗的默认宽度，默认全屏浏览器通常是 960px。在默认情况下像你的手机浏览器的全屏浏览器，通过设置 `<meta name="viewport" content="width=device-width">`，宽度将会是设备的宽度而不是默认的视窗宽度。设备宽度当用户在横向和纵向模式旋转他们的手机时将会改变。布局发生在每次设备旋转或浏览器缩放时。

布局性能受 DOM 影响——节点数越多，布局就需要更长的时间。布局将会变成瓶颈，如果期间需要滚动或者其他动画将会导致迟滞。20ms 的延迟在加载或者方向改变时或许还可以接受，但在动画或滚动时就会迟滞。任何渲染树改变的时候，像添加节点、改变内容或者在一个节点更新盒模型样式的时候布局就会发生。

为了减小布局事件的频率和时长，批量更新或者避免改动盒模型属性。

### 绘制

最后一步是将像素绘制在屏幕上。一旦渲染树创建并且布局完成，像素就可以被绘制在屏幕上。加载时，整个屏幕被绘制出来。之后，只有受影响的屏幕区域会被重绘，浏览器被优化为只重绘需要绘制的最小区域。绘制时间取决于何种类型的更新被附加在渲染树上。绘制是一个非常快的过程，所以聚焦在提升性能时这大概不是最有效的部分，重点要记住的是当测量一个动画帧需要的时间需要考虑到布局和重绘时间。添加到节点的样式会增加渲染时间，但是移除样式增加的 0.001ms 或许不能让你的优化物有所值。记住先测量。然后你可决定它的优化优先级。

优化 CRP
------

提升页面加载速度需要通过被加载资源的优先级、控制它们加载的顺序和减小这些资源的体积。性能提示包含 1）通过异步、延迟加载或者消除非关键资源来减少关键资源的请求数量，2）优化必须的请求数量和每个请求的文件体积，3）通过区分关键资源的优先级来优化被加载关键资源的顺序，来缩短关键路径长度。


媒体和 Web Audio API 的自动播放指南 - Web 媒体技术

媒体和 Web Audio API 的自动播放指南
=========================

网页加载完成后立即播放音频（或带有音频轨道的视频）可能会意外地打扰到用户。尽管自动播放媒体文件是一个很实用的功能，但是我们也应该谨慎地使用它，保证只有在它被需要的时候才使用。为了让用户拥有控制权，通常浏览器会提供各种方式禁用自动播放音频功能。在这篇文章中，我们将介绍各种媒体和 Web Audio API 的自动播放功能，包括关于如何使用自动播放功能、如何优雅的处理阻止自动播放功能的一些简短的介绍。

当源媒体没有音轨或音轨静音时，阻止自动播放*不会*应用于 `<video>` 元素。具有活动音轨的媒体被认为是**可听的**，并且阻止自动播放适用于它们。**听不见的**媒体不受阻止自动播放的影响。

自动播放和阻止自动播放
-----------

术语**自动播放**是指无需用户明确请求开始播放即可导致音频开始播放的任何功能。这包括使用 HTML 属性自动播放媒体以及用户使用 JavaScript 代码在处理用户输入的上下文之外开始播放。

这意味着以下两种行为都被视为自动播放行为，因此受到浏览器阻止自动播放策略的约束：

```html
<audio src="/music.mp4" autoplay></audio>
```

和

```js
audioElement.play();
```

以下 web 特性和 API 可能会受到阻止自动播放的影响：

* HTML `<audio>` 和 `<video>` 元素
* Web Audio API

从用户的角度来看，网页或应用程序自动地发出噪音而没有警告可能会令人讨厌、不便或令人反感。因此，浏览器通常仅允许在特定情况下进行自动播放。

### 自动播放的可用性

据一般规则，媒体内容将在满足以下*至少一个*的条件下自动播放：

* 音频被静音或其音量设置为 0
* 用户和网页已有交互行为（包括点击、触摸、按下某个键等等）
* 网站已被列入白名单；如果浏览器确定用户经常与媒体互动，这可能会自动发生，也可能通过首选项或其他用户界面功能手动发生
* 自动播放权限策略被应用于 `<iframe>` 或者其文档上，从而获得了自动播放的权限。

否则，播放可能会被阻止。导致播放被阻塞的确切情况以及将网站列入白名单的具体方法因浏览器而异，但最好是遵循以上的原则。

详情，请参阅 Google Chrome 和 WebKit 的自动播放政策。

**备注：** 换句话说，如果在尚无任何用户交互的页面中通过编程方式启动播放，则通常会阻止任何包含音频在内的媒体的播放。

能自动播放的媒体元素
----------

既然我们已经介绍了什么是自动播放以及什么可以阻止自动播放，接下来我们将介绍你的网站或应用程序如何在页面加载时自动播放媒体，如何检测何时自动播放失败，以及当自动播放被浏览器拒绝时的应对技巧。

### autoplay 属性

想让内容自动播放的最简单方法是将 `autoplay` 属性添加到 `<audio>` 或 `<video>` 元素。并将 `autoplay` 属性设置为 `true`，当 `autoplay` 的属性为 `true` 时，媒体元素将在发生以下情况后尽快自动开始播放：

* 页面允许使用自动播放功能
* 媒体元素已在页面加载期间创建
* 假设网络性能或带宽没有显着变化，且已收到足够的媒体流并已开始播放，继续播放直至媒体结束而不会中断。

#### 示例 1: autoplay 属性

使用 `autoplay` 属性的 `<audio>` 元素可能看起来像这样：

```html
<audio id="musicplayer" autoplay>
  <source src="/music/chapter1.mp4" />
</audio>
```

#### 示例 2：检测是否允许自动播放

如果你依靠自动播放功能去做一些重要的事情，或者自动播放失败会以任何方式影响你的应用程序，那你可能会想知道自动播放什么时候没有开始。不幸的是，对于 `autoplay` 属性，识别自动播放是否成功开始是很棘手的。自动播放失败时**不会触发**任何事件。也没有抛出异常或可以设置回调，甚至在媒体元素上都没有标记来告诉你自动播放是否起作用。你实际能做的就是检查一些值，然后根据这些值猜测自动播放是否起作用。

如果你能够调整查看内容的方向，那么更好的方法是，依靠知道媒体播放已成功开始，而不是在媒体启动失败时知道。你可以通过侦听要在媒体元素上触发的 `play` 事件来轻松实现此目的。

当媒体暂停后恢复时*以及*发生自动播放时都会发送 `play` 事件。这意味着第一次触发 `play` 事件时，你知道你的媒体是在页面打开后第一次启动的。

考虑以下 HTML 媒体元素：

```html
<video src="myvideo.mp4" id="video" autoplay></video>
```

这里我们有一个 `<video>` 元素，它设置了 `autoplay` 属性，并设置了 `play` 事件处理器；该事件由名为 `handleFirstPlay()` 的函数处理，该函数接收 `play` 事件作为输入。

`handleFirstPlay()` 看起来像这样：

```js
function handleFirstPlay(event) {
  let vid = event.target;

  vid.onplay = null;

  // 播放开始后开始执行你需要执行的操作
}
```

从 `Event` 对象的 `target` 获取对视频元素的引用后，该元素的 `onplay` 处理程序将设置为 `null`。这将阻止任何未来的播放事件被传递给处理程序。当文档位于后台标签页时，如果用户暂停并恢复视频或浏览器自动暂停和恢复视频，则可能会发生这种情况。

此时，你的网站或应用程序可以开始执行依赖于视频启动的任何操作。

**备注：** 此方法不区分自动播放和用户手动开始播放。

### play() 方法

术语“自动播放”还指脚本尝试在处理用户输入事件的上下文之外触发包含音频的媒体播放的场景。这是通过调用媒体元素的 `play()` 方法来完成的。

**备注：** 强烈建议你尽可能使用 `autoplay` 属性，因为 `autoplay` 属性对自动播放首选项的支持比其他自动播放媒体的方式更广泛。它还让浏览器负责开始播放，并优化播放的时间。

#### 示例：播放视频

这个简单的示例播放在文档中找到的第一个 `<video>` 元素。除非文档有权自动播放媒体，否则 `play()` 不会让播放开始。

```js
document.querySelector("video").play();
```

#### 示例：处理 play() 失败

当你使用 `play()` 方法启动媒体时，更容易检测到自动播放媒体的故障。`play()` 返回一个 `Promise`，一旦媒体成功开始播放，该 Promise 就会被兑现；当播放无法开始时（例如自动播放被拒绝），该 Promise 将被拒绝。当自动播放失败时，你可能希望为用户提供一种手动告诉浏览器要求用户授予播放媒体权限的方法。

你可以使用这样的代码来完成这项工作：

```js
let startPlayPromise = videoElem.play();

if (startPlayPromise !== undefined) {
  startPlayPromise
    .catch((error) => {
      if (error.name === "NotAllowedError") {
        showPlayButton(videoElem);
      } else {
        // 处理加载或播放错误
      }
    })
    .then(() => {
      // 仅在播放开始后才开始执行你需要执行的操作。
    });
}
```

我们对 `play()` 的结果做的第一件事是确保它不是 `undefined`。我们检查这一点是因为在早期版本的 HTML 规范中，`play()` 没有返回值。最近添加了返回一个允许你确定操作成功或失败的 promise。检查 `undefined` 可防止此代码在旧版本的 Web 浏览器上失败并出现错误。

然后我们向 Promise 添加一个 `catch()` 处理器。这将查看错误的 `name` 以查看它是否为 `NotAllowedError`。这表示由于权限问题导致播放失败，例如自动播放被拒绝。如果是这样的话，我们应该提供一个用户界面，让用户手动开始播放；这是由函数 `showPlayButton()` 处理的。

任何其他错误都会被适当处理。

如果 `play()` 返回的 Promise 已正确解决，则 `then()` 子句将运行，并且可以在自动播放开始时开始执行任何需要执行的操作。

使用 Web Audio API 自动播放
---------------------

在 Web Audio API 中，网站或应用程序可以使用链接到 `AudioContext` 的源节点上的 `start()` 方法开始播放音频。在处理用户输入事件的上下文之外执行此操作受自动播放规则的约束。

自动播放功能策略
--------

除了上述的浏览器端管理和对自动播放功能的控制之外，web 服务器也可以表示愿意让自动播放功能发挥作用。HTTP `Permissions-Policy` 标头的 `autoplay` 指令用于控制哪些域（如果有）可用于自动播放媒体。默认情况下， `autoplay` 功能策略设置为 `'self'`（*包括单引号字符*），表示允许自动播放，因为它们与文档托管在同一域中。

你还可以指定 `'none'` 以完全禁用自动播放，`'*'` 以允许来自所有域的自动播放，或指定一个或多个可以自动播放媒体的特定来源。这些来源由空格字符分隔。

**备注：** 指定的功能策略适用于文档以及嵌套在其中的每个 `<iframe>`，除非这些框架包含为该框架以及嵌套在其中的所有框架设置新的功能策略的 `allow`。

当使用 `<iframe>` 上的 `allow` 属性指定该框架及其嵌套框架的功能策略时，你还可以指定值 `'src'` 以允许仅自动播放来自与该框架的 `src` 属性指定的域相同的域的媒体。

### 示例：仅允许来自文档域的自动播放

使用 `Permissions-Policy` 标头来仅允许媒体从文档的 origin 自动播放：

```http
Permissions-Policy: autoplay 'self'
```

对 `<iframe>` 执行相同操作：

```html
<iframe src="mediaplayer.html" allow="autoplay 'src'"> </iframe>
```

### 示例：允许自动播放和全屏模式

如果无论域如何都允许全屏访问，则向前面的示例添加 Fullscreen API 权限会产生如下所示的 `Permissions-Policy` 标头；可以根据需要添加域限制。

```http
Permissions-Policy: autoplay 'self'; fullscreen
```

使用 `<iframe>` 元素的 `allow` 属性授予相同的权限，如下所示：

```html
<iframe src="mediaplayer.html" allow="autoplay 'src'; fullscreen"> </iframe>
```

### 示例：允许来自特定源的自动播放

允许从文档（或 `<iframe>`）自己的域和 `https://example.media` 播放媒体的 `Permissions-Policy` 标头如下所示：

```http
Permissions-Policy: autoplay 'self' https://example.media
```

可以编写 `<iframe>` 来指定此自动播放策略应该应用于其自身，并且任何子框架都将这样编写：

```html
<iframe
 width="300"
 height="200"
 src="mediaplayer.html"
 allow="autoplay 'src' https://example.media">
</iframe>
```

### 示例：禁用自动播放

将 `autoplay` 功能策略设置为 `'none'` 会完全禁用文档或 `<iframe>` 以及所有嵌套框架的自动播放。HTTP 标头是：

```http
Permissions-Policy: autoplay 'none'
```

使用 `<iframe>` 的 `allow` 属性：

```html
<iframe src="mediaplayer.html" allow="autoplay 'none'"> </iframe>
```

最佳实践
----

此处提供了提示和推荐的最佳实践，可帮助你充分利用自动播放功能。

### 使用媒体控件处理自动播放失败

自动播放的一个常见用例是自动开始播放伴随文章、广告或页面主要功能预览的视频剪辑。要自动播放此类视频，你有两种选择：没有音轨，或者有音轨但配置 `<video>` 元素为静音，如下所示：

```html
<video src="/videos/awesomevid.webm" controls autoplay muted></video>
```

该视频元素配置为包括用户控件（通常是播放/暂停、浏览视频时间线、音量控制和静音）；此外，由于包含 `muted` 属性，视频将自动播放，但音频静音。不过，用户可以选择通过单击控件中的取消静音按钮来重新启用音频。

浏览器配置选项
-------

浏览器可能具有控制自动播放工作方式或如何处理阻止自动播放的首选项。此处列出了对你作为 Web 开发人员可能具有特殊意义或重要性的任何此类首选项。其中包括任何可能有助于测试或调试的内容，以及任何可以以你需要准备处理的方式设置的内容。

### Firefox

`media.allowed-to-play.enabled`

布尔首选项，指定 `HTMLMediaElement.allowedToPlay` 属性是否向 Web 公开。目前默认情况下这是 `false`（除了在夜间构建中，默认情况下为 `true`）。如果此值为 `false`，则 `HTMLMediaElement` 接口中缺少 allowedToPlay 属性，因此该属性不会出现在 `<audio>` 或 `<video>` 元素上。

`media.autoplay.allow-extension-background-pages`

布尔首选项，如果为 `true`，则允许浏览器扩展的后台脚本自动播放音频媒体。将此值设置为 `false` 将禁用此功能。默认值是 `true`。

`media.autoplay.allow-muted`

布尔首选项，如果为 `true`（默认值），则允许自动播放当前静音的音频媒体。如果将其更改为 `false`，则即使静音，也不允许播放带有音轨的媒体。

`media.autoplay.block-webaudio`

布尔首选项，指示是否对 Web Audio API 应用阻止自动播放。默认为 `true`。

`media.autoplay.default`

整数首选项，指定默认情况下是否允许（`0`）、阻止（`1`）或使用提示（`2`）的自动播放支持的每个域配置。默认值为 `0`。

`media.autoplay.enabled.user-gestures-needed`（仅每夜构建版）

布尔首选项，用于控制是否允许通过检测用户手势来覆盖 `media.autoplay.default` 的设置。如果 `media.autoplay.default` （默认情况下允许自动播放）未设为 `0`，则如果页面已被用户手势激活，则此首选项设置为 `true` 时，无论如何都允许自动播放带音轨的媒体，而不带音轨的媒体则完全不受限制。

`media.block-autoplay-until-in-foreground`

布尔首选项，指示在后台选项卡上启动时是否阻止媒体播放。默认值 `true` 意味着即使其他方式可用，在选项卡置于前台之前也不会进行自动播放。这可以防止出现分散注意力的情况，即选项卡开始播放声音并且用户无法在所有选项卡和窗口中找到该选项卡。

参见
--

* Web 媒体技术
* 视频和音频内容（学习指南）
* 使用 Web Audio API
* 跨浏览器音频基础 (en-US)


在 HTML 中使用图像 - Web 媒体技术

在 HTML 中使用图像
============

HTML `<img>` 元素可让你在 HTML 文档中嵌入图像，而 `<picture>` 元素则可启用响应式图像。在本指南中，你可以找到有关在网站中添加图像的资源链接。

参考
--

这些文章介绍了一些 HTML 元素和 CSS 属性，它们用于控制图像在 web 上的显示方式。

### HTML

`<img>`

**HTML `<img>` 元素**用于在网页中嵌入图像。它支持多种属性，可控制图像的行为方式，并允许为看不到图像的人添加 `alt` 文本等重要信息。

`<picture>`

**HTML `<picture>` 元素**包含零个或多个 `<source>` 元素和一个 `<img>` 元素，为不同的显示/设备场景提供不同版本的图像。浏览器会考虑每个子 `<source>` 元素，并选择其中最匹配的元素。

### CSS

`object-fit`

**`object-fit`** CSS 属性设置了如何调整替换元素的内容大小以适应其容器，例如 `<img>` 或 `<video>`。

`object-position`

**`object-position`** CSS 属性指定所选替换元素中的内容在元素框内的对齐方式。替换元素对象未覆盖的框内区域将显示该元素的背景。

`background-image`

**`background-image`** CSS 属性为元素设置一个或多个背景图像。

指南
--

这些文章为选择和配置图像类型提供了指导。

图片文件类型和格式指南

本指南介绍 web 浏览器通常支持的各种图像文件类型，包括它们各自的使用情况、功能和兼容性因素的详细信息。此外，本文还提供了针对特定情况选择最佳图像文件类型的指导。

包含 `width` 和 `height` 属性以防止卡顿

这就改变了浏览器加载图像的方式，使浏览器可以在加载图像前就计算出图像的宽高比，然后再利用宽高比预留图像的显示尺寸。

其他主题
----

你可能感兴趣的相关主题。

学习 HTML：响应式图片

在本文中，我们将了解响应式图像（能在屏幕尺寸、分辨率和其他特性差异较大的设备上正常显示的图像）的概念并了解 HTML 提供了哪些工具来帮助我们实现这项技术。


音频与视频处理 - Web 媒体技术

音频与视频处理
=======

Web 的迷人之处在于你可以结合各种技术创造出新的形式。拥有浏览器中的原生音频和视频意味着我们可在像 `<canvas>`、WebGL 或 Web Audio API 等技术的辅助下使用这类数据流，例如：为音频添加混响和压缩效果，或为视频添加灰度/暗色滤镜。本文将为你提供相关的参考。

视频处理
----

读取视频中每帧的像素值的能力是十分实用的。

### 视频与 canvas

`<canvas>` 元素能提供一个平面，让你能在网页上画图，该功能十分强大并且可以与视频紧密结合。

总体技术过程为：

1. 从 `<video>` 元素中读取一帧写到 `<canvas>` 元素中。
2. 读取 `<canvas>` 元素中的数据并处理。
3. 将处理完成的数据写入“显示”的 `<canvas>` 中（实际上可以是相同的元素）。
4. 暂停并重复。

例如，我们要处理一个视频，将其灰度显示。在本例中，我们需展示出源视频和输出的灰度帧。正常情况下，如果你要实现“添加灰度滤镜”功能，那也许可以在 `<video>` 元素的样式里添加 `dispaly: none`，目的是防止源视频被绘制到屏幕上，同时只展示显示已被更改了帧的画布。

#### HTML

我们可将视频播放器和 `<canvas>` 元素如此设置：

```html
<video
 id="my-video"
 controls="true"
 width="480"
 height="270"
 crossorigin="anonymous">
  <source
 src="https://jplayer.org/video/webm/Big\_Buck\_Bunny\_Trailer.webm"
 type="video/webm" />
  <source
 src="https://jplayer.org/video/m4v/Big\_Buck\_Bunny\_Trailer.m4v"
 type="video/mp4" />
</video>

<canvas id="my-canvas" width="480" height="270"></canvas>
```

#### JavaScript

该代码负责更变帧数。

```js
const processor = {
  timerCallback() {
    if (this.video.paused || this.video.ended) {
      return;
    }
    this.computeFrame();
    setTimeout(() => {
      this.timerCallback();
    }, 16); // roughly 60 frames per second
  },

  doLoad() {
    this.video = document.getElementById("my-video");
    this.c1 = document.getElementById("my-canvas");
    this.ctx1 = this.c1.getContext("2d");

    this.video.addEventListener(
      "play",
      () => {
        this.width = this.video.width;
        this.height = this.video.height;
        this.timerCallback();
      },
      false,
    );
  },

  computeFrame() {
    this.ctx1.drawImage(this.video, 0, 0, this.width, this.height);
    const frame = this.ctx1.getImageData(0, 0, this.width, this.height);
    const l = frame.data.length / 4;

    for (let i = 0; i < l; i++) {
      const grey =
        (frame.data[i \* 4 + 0] +
          frame.data[i \* 4 + 1] +
          frame.data[i \* 4 + 2]) /
        3;

      frame.data[i \* 4 + 0] = grey;
      frame.data[i \* 4 + 1] = grey;
      frame.data[i \* 4 + 2] = grey;
    }
    this.ctx1.putImageData(frame, 0, 0);

    return;
  },
};
```

一旦网页加载完成，可以调用

```js
processor.doLoad();
```

#### 结果

这是个十分简单的例子，展示出了如何用画布来处理视频帧。为提升效率，你应该在支持 `requestAnimationFrame` 的浏览器上运行代码时，选择使用 `requestAnimationFrame()`，而不是 `setTimeout()`。

你还可通过在源 `<video>` 元素中采用 `grayscale()` CSS 功能以达到同样的效果。

**备注：** 由于存在潜在安全问题，如果你的视频和代码处在不同域，你需要在视频服务器上启用 CORS（跨源资源共享）。

### 视频与 WebGL

WebGL 是个很强大的 API，它使用画布来绘制出硬件加速的 3D 或 2D 场景。你可结合 WebGL 与 `<video>` 元素来创造视频纹理，这意味着你可将视频放置到 3D 场景内。

**备注：** 你可在 GitHub 上找到演示的源代码（查看在线演示）。

### 播放速度

我们也可使用一个 `<audio>` 与 `<video>` 元素的属性来调整视频播放的速度，这个属性叫做 `playbackRate`。`playbackRate` 是一个数值，表示回放速率的倍数，例如 0.5 表示半速，而 2 表示倍速。

请注意，`playbackRate` 属性适用于 `<audio>` 和 `<video>`，但在这两种情况下，它改变的是播放速度，而不是音高。要调整音频的音调则需使用 Web Audio API。参见 `AudioBufferSourceNode.playbackRate` (en-US) 属性。

#### HTML

```html
<video
 id="my-video"
 controls
 src="https://jplayer.org/video/m4v/Big\_Buck\_Bunny\_Trailer.m4v"></video>
```

#### JavaScript

```js
const myVideo = document.getElementById("my-video");
myVideo.playbackRate = 2;
```

#### 可编辑示例
```

<video id="my-video" controls="true" width="480" height="270">
  <source
 src="https://jplayer.org/video/webm/Big\_Buck\_Bunny\_Trailer.webm"
 type="video/webm" />
  <source
 src="https://jplayer.org/video/m4v/Big\_Buck\_Bunny\_Trailer.m4v"
 type="video/mp4" />
</video>
<div class="playable-buttons">
  <input id="edit" type="button" value="Edit" />
  <input id="reset" type="button" value="Reset" />
</div>
<textarea id="code" class="playable-code">
const myVideo = document.getElementById('my-video');
myVideo.playbackRate = 2;</textarea
>
```

```
const textarea = document.getElementById("code");
const reset = document.getElementById("reset");
const edit = document.getElementById("edit");
const code = textarea.value;

function setPlaybackRate() {
  eval(textarea.value);
}

reset.addEventListener("click", () => {
  textarea.value = code;
  setPlaybackRate();
});

edit.addEventListener("click", () => {
  textarea.focus();
});

textarea.addEventListener("input", setPlaybackRate);
window.addEventListener("load", setPlaybackRate);
```

**备注：** 尝试在线的 playbackRate 示例。

音频处理
----

除 `playbackRate` 以外，处理音频通常要使用 Web Audio API。

### 选择音频源

Web Audio API 可接收各种音源的音频，处理好音频后将其发送回 `AudioDestinationNode`，代表的是声音处理后发送到的那个输出设备。

| 如果音频源是... | 使用这种网页音频节点类型 |
| --- | --- |
| 来自 HTML `<audio>` 或 `<video>` 元素的音频轨道 | `MediaElementAudioSourceNode` |
| 内存中的简单原始音频数据缓冲区 | `AudioBufferSourceNode` |
| 产生正弦波或其他计算波形的振荡器 | `OscillatorNode` |
| 来自 WebRTC 的音频轨道（比如麦克风输入，可使用 `getUserMedia()` | `MediaStreamAudioSourceNode` |

### 音频滤镜

Web Audio API 拥有多种不同的滤镜和效果，可以使用 `BiquadFilterNode` 将其应用于音频中，比如

#### HTML

```html
<video id="my-video" controls src="myvideo.mp4" type="video/mp4"></video>
```

#### JavaScript

```js
const context = new AudioContext();
const audioSource = context.createMediaElementSource(
  document.getElementById("my-video"),
);
const filter = context.createBiquadFilter();
audioSource.connect(filter);
filter.connect(context.destination);

// Configure filter
filter.type = "lowshelf";
filter.frequency.value = 1000;
filter.gain.value = 25;
```

#### 可编辑示例
```

<video
 id="my-video"
 controls="true"
 width="480"
 height="270"
 crossorigin="anonymous">
  <source
 src="https://jplayer.org/video/webm/Big\_Buck\_Bunny\_Trailer.webm"
 type="video/webm" />
  <source
 src="https://jplayer.org/video/m4v/Big\_Buck\_Bunny\_Trailer.m4v"
 type="video/mp4" />
</video>
<div class="playable-buttons">
  <input id="edit" type="button" value="Edit" />
  <input id="reset" type="button" value="Reset" />
</div>
<textarea id="code" class="playable-code">
  filter.type = "lowshelf";
  filter.frequency.value = 1000;
  filter.gain.value = 25;
</textarea>
```

```
const context = new AudioContext();
const audioSource = context.createMediaElementSource(
  document.getElementById("my-video"),
);
const filter = context.createBiquadFilter();
audioSource.connect(filter);
filter.connect(context.destination);

const textarea = document.getElementById("code");
const reset = document.getElementById("reset");
const edit = document.getElementById("edit");
const code = textarea.value;

function setFilter() {
  eval(textarea.value);
}

reset.addEventListener("click", () => {
  textarea.value = code;
  setFilter();
});

edit.addEventListener("click", () => {
  textarea.focus();
});

textarea.addEventListener("input", setFilter);
window.addEventListener("load", setFilter);
```

**备注：** 除非你启用了 CORS，否则，为避免安全问题，你的视频应与你的代码处于同一个域中。

#### 常用音频滤镜

以下为一些可以使用的常用音频滤镜类型：

* 低通（Low Pass）：允许低于截止频率的信号通过，并减弱高于截止频率的信号。
* 高通（High Pass）：允许高于截止频率的信号通过，并减弱低于截止频率的信号。
* 带通（Band Pass）：允许一定范围内的频率通过，并减弱低于或高于该频率范围的信号。
* 低频搁架（Low Shelf）：允许所有频率通过，但增强（或减弱）较低频率的信号。
* 高频搁架（High Shelf）：允许所有频率通过，但增强（或减弱）较高频率的信号。
* 峰值（Peaking）：允许所有频率的信号通过，但增强（或减弱）一定频率范围的信号。
* 陷波（Notch）：允许所有频率的信号通过，但一组频率除外。
* 全通（All Pass）：允许所有频率的信号通过，但改变个频率之间的相位关系。

**备注：** 参见 `BiquadFilterNode` 以了解详情。

### 卷积和脉冲

也可使用 `ConvolverNode` 将脉冲响应运用到音频中。**脉冲响应**指短暂声音脉冲后产生的声音（比如拍掌）。脉冲响应表示产生脉冲的环境（比如在隧道中鼓掌产生的回声）。

#### 示例

```js
const convolver = context.createConvolver();
convolver.buffer = this.impulseResponseBuffer;
// Connect the graph.
source.connect(convolver);
convolver.connect(context.destination);
```

参见 Codepen 以获取应用示例（示例较为尴尬）。

### 空间音频

我们也可使用 **panner 节点**来对音频定位。panner 节点——`PannerNode` (en-US)——可以让我们界定出一个源锥（位置和方向）元素，所有这些都是使用 3D 笛卡尔坐标在 3D 空间中界定的。

#### 示例

```js
const panner = context.createPanner();
panner.coneOuterGain = 0.2;
panner.coneOuterAngle = 120;
panner.coneInnerAngle = 0;

panner.connect(context.destination);
source.connect(panner);
source.start(0);

// Position the listener at the origin.
context.listener.setPosition(0, 0, 0);
```

**备注：** 你可以在 GitHub 仓库中找到示例（也可以查看在线演示）。

### JavaScript 编解码器

还可以使用 JavaScript 在底层操控音频。如果你想创建音频编解码器（codec），那这会很有用。

目前存在以下格式的库：

* AAC：AAC.js
* ALAC：alac.js
* FLAC：flac.js
* MP3：mp3.js
* Opus：Opus.js
* Vorbis：vorbis.js

**备注：** 你可以在 Audiocogs 尝试一些演示；Audiocogs 还提供了一个框架——Aurora.js，旨在帮助你使用 JavaScript 创建自己的编解码器。

示例
--

* 各种 Web Audio API（及其他）示例
* THREE.js 视频立方体示例
* 实时卷积效应
参见
--

### 教程

* 使用 Canvas 处理视频
* HTML playbackRate 解释
* 使用 Web Audio API
* Web 空间音效基础
* 将视频帧作为 WebGL 纹理（你也可以使用 THREE.js WebGL 库（或其他库）来实现这个效果）
* WebGL 动画纹理
* 使用 Web Audio API 开发游戏音频（房间效果和滤镜）（2012）
### 参考

* `<audio>` 和 `<video>` 元素
* `HTMLMediaElement` API
* `<canvas>` 元素
* Web Audio API
* AudioContext
* 空间音效 (en-US)的更多相关内容
* Web 媒体技术


媒体类型和格式指南：图片、音频和视频内容 - Web 媒体技术

媒体类型和格式指南：图片、音频和视频内容
====================

几乎从一开始，Web 就包含了对某种形式的视觉媒体呈现的支持。最初，这些功能是有限的，但是不同的浏览器厂商扩展了这些功能，并且找到了自己的解决方案来解决网络上的静态和视频图像等问题。现代 Web 具有强大的功能来支持媒体的呈现和操作，多个与媒体相关的 API 支持各种类型的内容。通常，浏览器支持的媒体格式完全取决于浏览器的创建者，这可能会使 Web 开发人员的工作复杂化。

本指南概述了组成 web 中使用的媒体的媒体文件类型、编解码器以及算法。本指南还提供了浏览器对这些格式的各种组合的支持信息、格式优先级建议，以及哪些格式擅长于特定类型的内容。

参考
--

### 图片

图片文件类型和格式指南

互联网上使用的主要图像文件类型和内容格式指南。其中包括对浏览器支持、优点和限制的高度概述，以及最佳实践指南，以帮助网页设计人员为特定类型的内容选择正确的图像文件格式。

### 媒体文件格式和编解码器

媒体容器（文件类型）

包含媒体数据的文件类型指南。其中一些是音频专用文件，而另一些既可用于音频，也可用于电影等综合视听内容。包括主要 web 浏览器支持的每种文件类型的概述，以及浏览器支持信息和支持的特性。

Web 音频编解码器指南 (en-US)

常用媒体容器和主要浏览器允许使用的音频编解码器指南。包括优点、限制、主要规范和功能以及使用案例。还包括各浏览器对在特定容器中使用编解码器的支持。

Web 视频编解码器指南

本文提供了有关主要浏览器支持的视频编解码器的基本信息，以及一些不常支持但仍可能遇到的编解码器。文章还介绍了编解码器的功能、优点、限制以及浏览器支持级别和限制。

不同媒体类型的编解码器 (en-US)

在指定描述媒体格式的 MIME 类型时，可以使用作为类型字符串一部分的 `codecs` 参数提供详细信息。本指南介绍常见媒体类型的 `codecs` 参数的格式和可能的值。

WebRTC 使用的编解码器 (en-US)

WebRTC 不使用容器，而是使用 `MediaStreamTrack` 对象在点对点之间流式传输编码媒体本身，以表示每个音频或视频音轨。本指南将讨论 WebRTC 常用的编解码器。

指南
--

### 概念

数字音频概念 (en-US)

介绍如何将音频转换成数字形式并存储起来供计算机使用。它解释了音频采样的基本概念，以及采样率、音频帧和音频压缩等概念。

数字视频概念 (en-US)

Web 数字视频基本概念指南，包括色彩格式、色度二次抽样、人类感知如何影响视频编码等基础知识。

### 教程

学习区：视频和音频内容

本教程介绍了在 web 上使用媒体的方法和细节。

处理 web 内容的媒体支持问题 (en-US)

在本指南中，我们将探讨如何通过明智地选择媒体格式，并在有帮助的地方提供回退和替代格式，在提供尽可能广泛的兼容性的同时，最大限度地提高网页内容的质量或性能。

其他主题
----

媒体能力 API (en-US)

媒体能力 API 可让你发现应用程序或网站所运行设备的编码和解码能力。这样，你就可以实时决定何时使用何种格式。