Skip to content
This page has been auto-translated and may contain errors.View in English

表单验证

docs.scrimba.com

表单收集来自用户的输入,而用户会留下空白字段、输入错误的电子邮件或在应该是数字的地方填入字母。你不希望这些数据未经检查就到达你的代码。在提交任何内容之前,浏览器可以为你检查其中大部分内容,但前提是你要描述每个字段需要什么。这个描述直接写在你的 HTML 中,本章就是关于如何写它的。

必填字段和输入类型

你可以添加的两个最简单的检查是说字段必须被填充,以及说什么样的值属于它。

向输入框添加 required 这个词,浏览器就会拒绝提交表单,直到该字段有内容为止。你不需要写任何代码来实现这个功能。该属性就是指令。

html
<input type="text" name="full_name" required>

另一半是 type 属性。它告诉浏览器该值应该是什么样子,浏览器为你检查它。使用 type="email",浏览器会确保该值看起来像电子邮件地址。使用 type="number",它只接受数字。还有日期类型、网址类型等。

想象一下医生办公室的表格,其中某些框被标记为"必须填写",日期框已经有了日、月、年的小格子。这张纸在你写任何东西之前告诉你什么放在哪里。

required 是一个 布尔属性:仅仅它的存在就启用该规则,不需要任何值。标记为 required 的控件会在为空时阻止表单提交。

type 属性有两个作用。它选择浏览器呈现的控件(日期选择器、数字调节器),并对值设置内置约束:

  • type="email" 要求有 @ 和域名形式的后缀。
  • type="url" 要求解析为绝对网址的内容。
  • type="number" 只接受数字输入并解锁 minmaxstep
  • type="tel" 不约束值,但在移动设备上会弹出电话键盘。

首先选择具体的类型,然后再考虑其他事项。它用最少的代码换取最多的检查,并改进了人们在触摸设备上获得的屏幕键盘。

requiredtype 是平台所称 约束验证 的前两层:浏览器的内置系统,用于根据你在标记中声明的规则检查控件的值。required 设置"缺少值"规则。type 设置与该类型相关的格式规则。

关于 type 的两点生产备注。首先,type="email" 不会根据完整的电子邮件规范进行验证。浏览器使用故意宽松的模式(一串字符、@,然后是以点分隔的域),因为更严格的检查会拒绝实际可传递的地址。把它当作形状检查,而不是证明地址存在。其次,未知或不支持的 type 会回退到 type="text",所以字段在较旧的浏览器上从不会变得不可用,它只是失去额外的检查。正是这种优雅的回退让你可以不用对照兼容性表就采用更新的类型。

该类型还驱动屏幕键盘,通过相关的 inputmode 属性(一个提示,指示显示哪个键盘布局而不改变验证),你可以独立调整该键盘。选择正确的类型既是验证决定,也是易用性和人体工程学决定。

这是一个使用两个属性的小注册表单:

html
<form>
  <label>
    电子邮件地址
    <input type="email" name="email" required>
  </label>
  <label>
    网站
    <input type="url" name="website">
  </label>
  <button>注册</button>
</form>

电子邮件字段必须填写且必须看起来像电子邮件。网站字段是可选的,但如果有人输入任何内容,它必须看起来像网址。浏览器在按下按钮时检查两者。

Juno必填字段和输入类型 两个小词能做很多工作。在字段上放 required,浏览器就不会让空字段通过。设置 type,比如 emailnumber,它会检查值的形状是否正确。选择与字段匹配的类型,你就能获得免费的检查。
Juno必填字段和输入类型required 是一个布尔属性:它要么存在,要么不存在,不需要值。type 既选择控件又设置格式规则,所以 type="email" 需要 @ 和域名。总是从最具体的类型开始;它用最小的标记换取最多的检查,移动键盘也会变好。
Juno必填字段和输入类型 这些是约束验证的前两层,浏览器的内置规则检查器。值得记住的是:type="email" 是一个宽松的形状检查,不是证明地址是真实的,而不支持的类型静默回退到 text 而不是出现故障。选择类型也是无障碍决定,因为它设置了人们获得的屏幕键盘。

约束属性

除了"被填充"和"正确的类型",你通常需要限制:至少这么长的值,不超过那么大的数字,特定格式的代码。一些属性可以覆盖这些。

你可以设置字段接受的限制。两个有用的开始:

html
<input type="text" name="username" maxlength="20">
<input type="number" name="quantity" min="1" max="10">

maxlength="20" 在字段包含二十个字符后停止。minmax 设置允许的最小和最大数字。浏览器将人们限制在这些边界内,所以你不必自己检查它们。

有一小组约束属性,每一个都对应一个规则:

  • minlength / maxlength 设置文本值可能有的最少和最多字符。
  • min / max 为数字和日期设置最低和最高值。
  • step 为数字设置允许的增量,所以 step="5" 接受 0、5、10,拒绝 3。
  • pattern 包含一个 正则表达式(一种紧凑记号法来描述允许文本的确切形状),值必须完全匹配它。
html
<input type="text" name="username" minlength="3" maxlength="20" required>
<input type="number" name="quantity" min="1" max="10" step="1">
<input type="text" name="pin" pattern="[0-9]{4}" title="四个数字">

patterntitle 配对:某些浏览器在错误消息中显示其文本,它告诉视力正常的用户该字段需要什么。在 CSS 中,:valid:invalid 让你根据控件是否当前通过其约束来设置其样式。

每个约束属性对应浏览器可以对控件提出的一个标记,这在你从 JavaScript 读取验证状态时很重要。违反 min 会引发"范围下溢",超过 maxlength 会引发"过长",pattern 不匹配会引发"模式不匹配",以此类推。该属性是声明;标记是结果的报告方式。

有两个细节会让人困惑。pattern 是隐式锚定的:表达式必须匹配整个值,就好像它被包装以覆盖开始到结束,所以 pattern="[0-9]{4}" 意味着恰好四个数字,不是"包含四个数字"。step 从基数(min,或如果没有则为零)使用浮点算术测量,所以 step="0.1" 可能会拒绝你期望通过的值,因为二进制表示小数的方式;当精度很重要时,设置明确的 min 作为基数。

:valid:invalid 伪类(基于元素状态而非位置匹配元素的 CSS 选择器)很有用但很钝:空的 required 字段从第一次绘制就匹配 :invalid,所以将其设置为红色在用户输入任何内容之前就用错误来欢迎用户。解决方案是 :user-invalid,它仅在人与字段交互后匹配,所以反馈在有用时到达而不是在到达时。

节省最多困惑的规则:pattern 必须匹配整个值,而不仅仅是其一部分。

html
<input
  type="text"
  name="product_code"
  pattern="[A-Z]{2}-[0-9]{3}"
  title="两个字母,一个连字符,三个数字,例如 AB-123"
  required>
Juno约束属性 一旦字段是必填的且有类型,你可以添加限制。maxlength 限制可以输入多少字符,minmax 限制数字范围。浏览器自动将人们保留在这些界限内,这是你永远不必写的大量检查。
Juno约束属性 这个集合很小:文本用 minlength/maxlength,数字和日期用 min/max,增量用 step,精确形状用 pattern。给 pattern 一个 title 使消息有意义。pattern 匹配整个值,所以 [0-9]{4} 是总共四个数字,不是内部四个数字。
Juno约束属性 每个属性映射到一个稍后可以读取的有效性标记,所以标记和报告一致。两个陷阱:pattern 是端到端锚定的,step 使用浮点数学从基数计算,所以当精度很重要时设置明确的 min。使用 :user-invalid 而不是 :invalid 来设置样式,否则你会在任何人输入前将未触及的字段涂成红色。

原生验证反馈

声明规则是故事的一半。另一半是当值违反规则时用户看到什么。

当有人按下提交按钮且字段有问题时,浏览器会停止提交,并在第一个有问题的字段旁显示一条小消息,然后将光标跳转到它。你不需要构建这条消息。浏览器写它,用访问者自己的语言,并自动显示它。

所以一个 required 电子邮件字段留空会产生类似"请填写此字段"的内容,表单在修复前不会发送。

验证在提交时启动。浏览器按顺序遍历控件,找到第一个无效的控件,聚焦它,并显示一个小消息气泡描述问题。如果一切都通过,表单像往常一样提交。

你可以通过 CSS 获得一些样式控制。:required:valid:invalid:in-range 让你在视觉上标记字段。你无法轻易重新设置样式的是消息气泡本身,它的外观是浏览器的,不是你的。你也可以使用 novalidate 属性为表单关闭整个系统,这在你打算完全在 JavaScript 中验证时很有用。

css
input:user-invalid {
  border-color: #c0392b;
}
input:user-valid {
  border-color: #2d7a3f;
}

原生反馈方便且几乎免费,但它有一些值得在依赖它在已发布的产品中之前计划的限制。

消息气泡是一个 短暂的 元素:它在提交时出现,在下一次交互时消失,不是你可以选择或设置样式的文档的一部分。各浏览器对它的屏幕阅读器支持不均匀,所以仅一个气泡不是向看不见的人可靠宣布错误的依赖方式。它的文本也被本地化为浏览器的语言,而不是你页面的 lang,所以用英文写的表单可能会向浏览器设置为法语的访问者显示法语错误消息。这对用户是正确的,对开发者是令人惊讶的。

你可以在没有提交的情况下自己触发相同的流程:reportValidity() 运行检查并根据需要显示气泡。但对于任何超越快速原型的东西,可访问的模式是抑制原生气泡(用 novalidate)并在页面中呈现你自己的错误文本,与字段相关,所以它是可见、可设置样式和可靠宣布的。下一部分将介绍如何实现。

Juno原生验证反馈 这是所有那些属性的回报:按提交键字段为坏时,浏览器会弹出一条小消息,指向你该字段,并拒绝发送。你没有写任何这样的文本;浏览器用访问者的语言写的。对于很多表单,这是你需要的所有反馈。
Juno原生验证反馈 检查在提交时运行:第一个坏字段获得焦点和消息气泡,表单被保留直到它通过。你可以用 :user-valid:user-invalid 设置字段样式,但气泡本身是浏览器设计的。当你计划在 JavaScript 中处理时,用 novalidate 关闭整个事物。
Juno原生验证反馈 气泡是短暂的和不可设置样式的,它的屏幕阅读器支持不均匀,它的文本遵循浏览器的语言,不是你页面的。对于原型很好,对于生产不足。当无障碍很重要时,添加 novalidate 并在页面中呈现你自己的错误文本,使其可以被看到、设置样式和正确宣布。

仍需要 JavaScript 的时候

原生验证根据字段自己的规则检查一个字段。很多真实的检查不符合这个形式,那就是 JavaScript 派上用场的地方。

有些东西浏览器无法自己检查。两个密码字段是否相匹配。用户名是否已被其他人占用。折扣代码是否真实。这些都不是关于一个字段的形状,所以没有属性,你需要用 JavaScript 来做检查。

这不是 HTML 的失败。内置检查用无代码处理常见情况,JavaScript 处理其余情况。

差距是任何依赖多个字段或页面还没有的信息的东西。常见的例子是确认两个密码匹配。你在 JavaScript 中比较它们,并用 setCustomValidity 将结果反馈到原生系统,它在控件上设置自定义错误消息(空字符串意味着"这是有效的"):

html
<input type="password" id="password" name="password" required>
<input type="password" id="confirm" name="confirm" required>

比较本身在 <script> 中运行:

js
const password = document.querySelector("#password")
const confirm = document.querySelector("#confirm")

confirm.addEventListener("input", () => {
  // 空字符串清除错误并标记字段为有效
  const message = confirm.value === password.value ? "" : "密码不匹配"
  confirm.setCustomValidity(message)
})

该字段现在参与原生验证,就像任何其他的一样,当密码不同时阻止提交并显示你的消息。需要服务器的检查,如用户名可用性,遵循相同的形状但在网络请求返回后设置消息。

JavaScript 通过 约束验证 API(浏览器在表单控件上公开的属性和方法集合,用于读取和设置有效性)到达原生验证。你最常使用的部分:

  • setCustomValidity(message) 在控件上设置自定义错误字符串。非空字符串将其标记为无效并成为其消息;空字符串清除自定义错误。
  • validity 是一个只读的 ValidityState 对象:每个可能的失败的一个布尔值(valueMissingtypeMismatchpatternMismatchrangeOverflowtooLongcustomError 等),加上 valid 用于总体结果。读取它来找出为什么字段失败,不仅仅是它失败了。
  • checkValidity() 返回真或假而不显示任何东西;reportValidity() 做相同的事情并显示原生气泡。

对于可访问的错误,颜色永远不是单独足够的:红色边框对屏幕阅读器或无法区分红色和绿色的人没有说什么。将错误文本与其字段关联,使辅助技术将它们一起读取。在输入上设置 aria-describedby 为错误元素的 id(这告诉屏幕阅读器"将这个文本作为字段的描述读取"),并在字段失败时设置 aria-invalid="true"(这宣布字段处于错误状态):

html
<input
  type="text"
  id="username"
  name="username"
  aria-describedby="username-error"
  aria-invalid="true"
  required>
<p id="username-error" role="alert">那个用户名已被占用。</p>

role="alert" 使屏幕阅读器在消息出现时立即宣布它。无障碍章节深入讨论了将控件与其描述相关联。

这留下了权衡。原生验证代码更少,与平台一致,在你的脚本加载之前就有效,但它的反馈很难设置样式和宣布。自定义验证是更多代码和更多责任,但给了你对措辞、时机和可访问性属性无法到达的完全控制。大多数生产表单两者都使用:原生属性作为基线,JavaScript 分层用于约束属性无法到达的检查和消息。

无论你选择哪一个,一条规则不能改变。客户端验证是一种便利,永不保证。本章中描述的所有内容都在访问者的浏览器中运行,任何人都可以将其关闭、编辑页面或直接向服务器发送请求而不触及表单。服务器必须重新验证它收到的每个值,就好像没有浏览器检查曾经发生过一样。将浏览器的检查视为快速、友好的第一遍,为用户省去往返,将服务器视为实际保护你数据的检查。

Juno仍需要 JavaScript 的时候 内置检查一次覆盖一个字段,所以任何比较字段的东西,比如"这两个密码是否匹配",或与服务器检查,比如"这个用户名是否免费",需要 JavaScript。这是正常的。HTML 免费处理日常情况,JavaScript 处理它无法自己看到的情况。
Juno仍需要 JavaScript 的时候 当检查跨越两个字段或需要服务器时,在 JavaScript 中计算它并用 setCustomValidity 手传结果:一个消息字符串使字段失败,一个空字符串清除它。该字段然后加入原生验证,就像任何其他的一样。密码匹配和用户名可用性是经典的两个。
Juno仍需要 JavaScript 的时候 约束验证 API 是你的钩子:setCustomValidity 设置消息,validity 读取为什么字段失败,checkValidityreportValidity 运行检查。对于可访问的错误,用 aria-describedbyaria-invalid 将文本与字段关联,永远不仅仅是颜色。还有那个不可选的:这里的每个检查都在浏览器中运行,所以服务器必须重新验证所有它。