约束验证

Web 表单的创建一直是一项复杂的任务。虽然标记表单本身很容易,但检查每个字段是否具有有效且一致的值却更加困难,并且向用户告知问题可能会变得令人头疼。HTML5 引入了新的表单机制:它为 <input> 元素添加了新的语义类型和约束验证,以简化客户端检查表单内容的工作。通过设置新属性,无需 JavaScript,即可检查基本的常用约束;可以使用约束验证 API 来测试更复杂的约束。

¥The creation of web forms has always been a complex task. While marking up the form itself is easy, checking whether each field has a valid and coherent value is more difficult, and informing the user about the problem may become a headache. HTML5 introduced new mechanisms for forms: it added new semantic types for the <input> element and constraint validation to ease the work of checking the form content on the client side. Basic, usual constraints can be checked, without the need for JavaScript, by setting new attributes; more complex constraints can be tested using the Constraint Validation API.

有关这些概念的基本介绍和示例,请参阅 表单验证教程

¥For a basic introduction to these concepts, with examples, see the Form validation tutorial.

注意:HTML 约束验证并不能消除服务器端验证的需要。尽管预计无效表单请求会少得多,但仍可以通过多种方式发送无效表单请求:

¥Note: HTML Constraint validation doesn't remove the need for validation on the server side. Even though far fewer invalid form requests are to be expected, invalid ones can still be sent in many ways:

  • 通过浏览器的开发者工具修改 HTML。
  • 通过不使用表单手动编写 HTTP 请求。
  • 通过以编程方式将内容写入表单(某些约束验证仅针对用户输入运行,如果你使用 JavaScript 设置表单字段的值则不会运行)。

因此,你应该始终在服务器端验证表单数据,与在客户端执行的操作保持一致。

¥Therefore, you should always validate form data on the server side, consistent with what is done on the client side.

内在和基本约束

¥Intrinsic and basic constraints

在 HTML 中,基本约束以两种方式声明:

¥In HTML, basic constraints are declared in two ways:

  • 通过为 <input> 元素的 type 属性选择语义上最合适的值,例如,选择 email 类型会自动创建一个约束,用于检查该值是否是有效的电子邮件地址。
  • 通过设置与验证相关的属性的值,允许以简单的方式描述基本约束,而不需要 JavaScript。

语义输入类型

¥Semantic input types

type 属性的内在约束是:

¥The intrinsic constraints for the type attribute are:

输入类型 约束说明 相关违规行为
<input type="URL"> 该值必须是绝对 URL,如 网址生活标准 中所定义。 TypeMismatch 约束违规
<input type="email"> 该值必须是语法上有效的电子邮件地址,通常采用 username@hostname.tld 格式,但也可以是本地格式,例如 username@hostname TypeMismatch 约束违规

对于这两种输入类型,如果设置了 multiple 属性,则可以设置多个值,作为逗号分隔的列表。如果其中任何一个不满足此处描述的条件,则会触发类型不匹配约束违规。

¥For both of these input types, if the multiple attribute is set, several values can be set, as a comma-separated list. If any of these do not satisfy the condition described here, the Type mismatch constraint violation is triggered.

请注意,大多数输入类型没有内在约束,因为有些输入类型被禁止进行约束验证,或者具有将不正确的值转换为正确的默认值的清理算法。

¥Note that most input types don't have intrinsic constraints, as some are barred from constraint validation or have a sanitization algorithm transforming incorrect values to a correct default.

验证相关属性

¥Validation-related attributes

除了上面描述的 type 属性之外,还使用以下属性来描述基本约束:

¥In addition to the type attribute described above, the following attributes are used to describe basic constraints:

属性 支持属性的输入类型 可能的值 约束说明 相关违规行为
pattern text, search, url, tel, email, password A JavaScript 正则表达式(使用 globalignoreCasemultiline 标志禁用进行编译) 该值必须与模式匹配。 patternMismatch 约束违规
min range, number 有效号码 该值必须大于或等于该值。 rangeUnderflow违反约束
date, month, week 有效日期
datetime-local, time 有效的日期和时间
max range, number 有效号码 该值必须小于或等于该值 rangeOverflow违反约束
date, month, week 有效日期
datetime-local, time 有效的日期和时间
required text, search, url, tel, email, password, date, datetime-local, month, week, time, number, checkbox, radio, file;也适用于 <select><textarea> 元素 ,因为它是布尔属性:它的存在意味着true,它的不存在意味着false 必须有一个值(如果已设置)。 valueMissing违反约束
step date 整数天数 除非步骤设置为 any 文字,否则该值必须是 min + 步骤的整数倍。 stepMismatch违反约束
month 整数月数
week 整数周
datetime-local, time 整数秒
range, number 一个整数
minlength text, search, url, tel, email, password;也在 <textarea> 元素上 整数长度 如果非空,字符(代码点)的数量不得小于属性的值。对于 <textarea>,所有换行符都标准化为单个字符(而不是 CRLF 对)。 tooShort违反约束
maxlength text, search, url, tel, email, password;也在 <textarea> 元素上 整数长度 字符(代码点)的数量不得超过属性的值。 tooLong违反约束

约束验证过程

¥Constraint validation process

约束验证是通过约束验证 API 在单个表单元素上或在表单级别上的 <form> 元素本身上完成的。约束验证通过以下方式完成:

¥Constraint validation is done through the Constraint Validation API either on a single form element or at the form level, on the <form> element itself. The constraint validation is done in the following ways:

  • 通过调用与表单相关的 DOM 接口(HTMLInputElementHTMLSelectElementHTMLButtonElementHTMLOutputElementHTMLTextAreaElement)的 checkValidity()reportValidity() 方法,该方法仅评估此元素上的约束,从而允许脚本获取此信息。checkValidity() 方法返回一个布尔值,指示元素的值是否通过其约束。(这通常由用户代理在确定应用哪个 CSS 伪类 :valid:invalid 时完成。)相比之下,reportValidity() 方法向用户报告任何约束失败。
  • 通过调用 HTMLFormElement 接口上的 checkValidity()reportValidity() 方法。
  • 通过提交表格本身。

调用 checkValidity() 称为静态验证约束,而调用 reportValidity() 或提交表单称为交互验证约束。

¥Calling checkValidity() is called statically validating the constraints, while calling reportValidity() or submitting the form is called interactively validating the constraints.

注意:

¥Note:

  • 如果在 <form> 元素上设置 novalidate 属性,则不会发生约束的交互式验证。
  • HTMLFormElement 接口上调用 submit() 方法不会触发约束验证。换句话说,即使表单数据不满足约束,该方法也会将其发送到服务器。请改为在提交按钮上调用 click() 方法。
  • 仅在用户提供的输入上检查 minlengthmaxlength 约束。如果值是通过编程方式设置的,则不会检查它们,即使明确调用 checkValidity()reportValidity() 也是如此。

使用约束验证 API 的复杂约束

¥Complex constraints using the Constraint Validation API

使用 JavaScript 和 Constraint API,可以实现更复杂的约束,例如组合多个字段的约束,或者涉及复杂计算的约束。

¥Using JavaScript and the Constraint API, it is possible to implement more complex constraints, for example, constraints combining several fields, or constraints involving complex calculations.

基本上,其想法是在某些表单字段事件(例如 onchange)上触发 JavaScript 来计算是否违反约束,然后使用方法 field.setCustomValidity() 来设置验证结果:空字符串表示满足约束,任何其他字符串表示存在错误,该字符串是要显示给用户的错误消息。

¥Basically, the idea is to trigger JavaScript on some form field event (like onchange) to calculate whether the constraint is violated, and then to use the method field.setCustomValidity() to set the result of the validation: an empty string means the constraint is satisfied, and any other string means there is an error and this string is the error message to display to the user.

组合多个字段的约束:邮政编码验证

¥Constraint combining several fields: Postal code validation

邮政编码格式因国家/地区而异。不仅大多数国家/地区允许在国家/地区代码中使用可选前缀(例如德国的 D-、法国或瑞士的 F-),而且某些国家/地区的邮政编码仅包含固定位数;其他国家,比如英国,有更复杂的结构,允许字母出现在某些特定的位置。

¥The postal code format varies from one country to another. Not only do most countries allow an optional prefix with the country code (like D- in Germany, F- in France or Switzerland), but some countries have postal codes with only a fixed number of digits; others, like the UK, have more complex structures, allowing letters at some specific positions.

注意:这不是一个全面的邮政编码验证库,而是关键概念的演示。

¥Note: This is not a comprehensive postal code validation library, but rather a demonstration of the key concepts.

作为示例,我们将添加一个脚本来检查此简单表单的约束验证:

¥As an example, we will add a script checking the constraint validation for this simple form:

html
<form>
  <label for="ZIP">ZIP : </label>
  <input type="text" id="ZIP" />
  <label for="Country">Country : </label>
  <select id="Country">
    <option value="ch">Switzerland</option>
    <option value="fr">France</option>
    <option value="de">Germany</option>
    <option value="nl">The Netherlands</option>
  </select>
  <input type="submit" value="Validate" />
</form>

这将显示以下形式:

¥This displays the following form:

首先,我们编写一个检查约束本身的函数:

¥First, we write a function checking the constraint itself:

js
function checkZIP() {
  // For each country, defines the pattern that the ZIP has to follow
  const constraints = {
    ch: [
      "^(CH-)?\\d{4}$",
      "Switzerland ZIPs must have exactly 4 digits: e.g. CH-1950 or 1950",
    ],
    fr: [
      "^(F-)?\\d{5}$",
      "France ZIPs must have exactly 5 digits: e.g. F-75012 or 75012",
    ],
    de: [
      "^(D-)?\\d{5}$",
      "Germany ZIPs must have exactly 5 digits: e.g. D-12345 or 12345",
    ],
    nl: [
      "^(NL-)?\\d{4}\\s*([A-RT-Z][A-Z]|S[BCE-RT-Z])$",
      "Netherland ZIPs must have exactly 4 digits, followed by 2 letters except SA, SD and SS",
    ],
  };

  // Read the country id
  const country = document.getElementById("Country").value;

  // Get the NPA field
  const ZIPField = document.getElementById("ZIP");

  // Build the constraint checker
  const constraint = new RegExp(constraints[country][0], "");
  console.log(constraint);

  // Check it!
  if (constraint.test(ZIPField.value)) {
    // The ZIP follows the constraint, we use the ConstraintAPI to tell it
    ZIPField.setCustomValidity("");
  } else {
    // The ZIP doesn't follow the constraint, we use the ConstraintAPI to
    // give a message about the format required for this country
    ZIPField.setCustomValidity(constraints[country][1]);
  }
}

然后我们将其链接到 <select> 的 onchange 事件和 <input> 的 oninput 事件:

¥Then we link it to the onchange event for the <select> and the oninput event for the <input>:

js
window.onload = () => {
  document.getElementById("Country").onchange = checkZIP;
  document.getElementById("ZIP").oninput = checkZIP;
};

上传前限制文件大小

¥Limiting the size of a file before its upload

另一个常见的限制是限制要上传的文件的大小。在将文件传输到服务器之前在客户端进行检查需要将约束验证 API(尤其是 field.setCustomValidity() 方法)与另一个 JavaScript API(此处为文件 API)相结合。

¥Another common constraint is to limit the size of a file to be uploaded. Checking this on the client side before the file is transmitted to the server requires combining the Constraint Validation API, and especially the field.setCustomValidity() method, with another JavaScript API, here the File API.

这是 HTML 部分:

¥Here is the HTML part:

html
<label for="FS">Select a file smaller than 75 kB : </label>
<input type="file" id="FS" />

这显示:

¥This displays:

JavaScript 读取所选文件,使用 File.size() 方法获取其大小,将其与(硬编码)限制进行比较,并调用 Constraint API 通知浏览器是否存在违规:

¥The JavaScript reads the file selected, uses the File.size() method to get its size, compares it to the (hard coded) limit, and calls the Constraint API to inform the browser if there is a violation:

js
function checkFileSize() {
  const FS = document.getElementById("FS");
  const files = FS.files;

  // If there is (at least) one file selected
  if (files.length > 0) {
    if (files[0].size > 75 * 1024) {
      // Check the constraint
      FS.setCustomValidity("The selected file must not be larger than 75 kB");
      FS.reportValidity();
      return;
    }
  }
  // No custom constraint violation
  FS.setCustomValidity("");
}

最后,我们将该方法与正确的事件钩子:

¥Finally, we hook the method with the correct event:

js
window.onload = () => {
  document.getElementById("FS").onchange = checkFileSize;
};

约束验证的视觉样式

¥Visual styling of constraint validation

除了设置约束之外,Web 开发者还希望控制向用户显示哪些消息以及它们的样式。

¥Apart from setting constraints, web developers want to control what messages are displayed to the users and how they are styled.

控制元素的外观

¥Controlling the look of elements

元素的外观可以通过 CSS 伪类控制。

¥The look of elements can be controlled via CSS pseudo-classes.

:required 与 ::optional CSS 伪类

¥:required and :optional CSS pseudo-classes

:required:optional pseudo-classes 允许编写与具有 required 属性或不具有 required 属性的表单元素相匹配的选择器。

¥The :required and :optional pseudo-classes allow writing selectors that match form elements that have the required attribute, or that don't have it.

:placeholder-shown CSS 伪类

¥:placeholder-shown CSS pseudo-class

参见 :placeholder-shown

¥See :placeholder-shown.

:valid :invalid CSS 伪类

¥:valid :invalid CSS pseudo-classes

:valid:invalid pseudo-classes 用于表示 <input> 元素,其内容根据输入的类型设置分别验证和失败。这些类允许用户设置有效或无效表单元素的样式,以便更轻松地识别格式正确或错误的元素。

¥The :valid and :invalid pseudo-classes are used to represent <input> elements whose content validates and fails to validate respectively according to the input's type setting. These classes allow the user to style valid or invalid form elements to make it easier to identify elements that are either formatted correctly or incorrectly.

控制违反约束的文本

¥Controlling the text of constraint violation

以下项目可以帮助控制违反约束的文本:

¥The following items can help with controlling the text of a constraint violation:

  • setCustomValidity(message) 方法涉及以下要素:
    • <fieldset>。注意:在字段集元素上设置自定义有效性消息不会阻止大多数浏览器中的表单提交。
    • <input>
    • <output>
    • <select>
    • 提交按钮(使用 submit 类型的 <button> 元素或 submit 类型的 input 元素创建。其他类型的按钮不参与约束验证。
    • <textarea>
  • ValidityState 接口描述了上面列出的元素类型的 validity 属性返回的对象。它表示输入值可能无效的各种方式。它们共同帮助解释为什么元素的值无效(如果它无效)。