表单验证第一部分:HTML 中的约束验证

Avatar of Chris Ferdinandi
Chris Ferdinandi 发布

DigitalOcean 为您的旅程的每个阶段提供云产品。立即开始使用 200 美元的免费积分!

大多数 JavaScript 表单验证库都很大,并且通常需要其他库,例如 jQuery。例如,MailChimp 的嵌入式表单包含一个 140kb 的验证文件(已压缩)。它包含了整个 jQuery 库、一个第三方表单验证插件以及一些自定义 MailChimp 代码。实际上,这种设置是激发这个关于现代表单验证的新系列的原因。如今我们有哪些新的表单验证工具?什么可能?还需要什么?

在本系列中,我将向您展示两种在前端验证表单的轻量级方法。两者都利用了更新的 Web API。我还将教您如何将这些 API 的浏览器支持推回 IE9(这将为您提供对全球 99.6% 的网络流量的覆盖范围)。

最后,我们将看看 MailChimp 的注册表单,并提供相同的体验,但代码量减少了 28 倍。

值得一提的是,前端表单验证可以绕过。您也应该始终在服务器上验证您的代码。

好了,让我们开始吧!

文章系列

  1. HTML 中的约束验证(您在这里!)
  2. JavaScript 中的约束验证 API
  3. 有效性状态 API 填充
  4. 验证 MailChimp 订阅表单

超级简单的方法:约束验证

通过语义输入类型(例如,<input type="email">)和验证属性(例如,requiredpattern)的组合,浏览器可以原生验证表单输入,并在用户操作错误时提醒他们。

各种输入类型和属性的支持 在不同浏览器之间差异很大,但我将提供一些技巧和解决方法来最大限度地提高浏览器兼容性。

基本文本验证

假设您有一个文本字段,用户必须在提交表单之前填写。添加 required 属性,支持的浏览器将提醒未填写该字段的用户,并拒绝让他们提交表单。

<input type="text" required>
Chrome 中的必填文本输入。

您是否需要响应为最小或最大数量的字符?使用 minlengthmaxlength 来强制执行这些规则。此示例要求值在 3 到 12 个字符之间。

<input type="text" minlength="3" maxlength="12">
Firefox 中字符数量错误的消息。

pattern 属性允许您对输入值运行正则表达式验证。例如,如果您要求密码至少包含 1 个大写字母、1 个小写字母和 1 个数字,浏览器可以为您验证这一点。

<input type="password" pattern="^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?!.*\s).*$" required>
Safari 中的格式错误消息。

如果在 pattern 中提供 title 属性,则如果模式不匹配,title 值将包含在任何错误消息中。

<input type="password" pattern="^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?!.*\s).*$" title="Please include at least 1 uppercase character, 1 lowercase character, and 1 number." required>
Opera 中的格式错误消息,包含解释正则表达式的标题文本。

您甚至可以将其与 minlength 和(似乎与银行有关)maxlength 相结合,以强制执行最小或最大长度。

<input type="password" minlength="8" pattern="^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?!.*\s).*$" title="Please include at least 1 uppercase character, 1 lowercase character, and 1 number." required>

查看 CodePen 上 Chris Ferdinandi (@cferdinandi) 的示例 表单验证:基本文本

验证数字

number 输入类型只接受数字。浏览器将拒绝接受字母和其他字符,或者在用户使用它们时提醒用户。对 input[type="number"] 的浏览器支持各不相同,但您可以提供 pattern 作为后备方案。

<input type="number" pattern="[-+]?[0-9]">

默认情况下,number 输入类型只允许整数。

您可以使用 step 属性允许浮点数(带小数点的数字)。这告诉浏览器接受什么数字间隔。它可以是任何数字值(例如,0.1),或者如果您希望允许任何数字,则可以是 any

您还应该修改 pattern 以允许小数点。

<input type="number" step="any" pattern="[-+]?[0-9]*[.,]?[0-9]+">

如果数字应该在一定范围内,浏览器可以使用 minmax 属性验证这些范围。您还应该修改 pattern 以进行匹配。例如,如果数字必须在 3 到 42 之间,您将执行以下操作

<input type="number" min="3" max="42" pattern="[3-9]|[1-3][0-9]|4[0-2]">

查看 CodePen 上 Chris Ferdinandi (@cferdinandi) 的示例 表单验证:数字

验证电子邮件地址和 URL

email 输入类型将提醒用户提供的电子邮件地址是否无效。与 number 输入类型一样,您应该为不支持此输入类型的浏览器提供模式。

电子邮件验证正则表达式模式是一个备受争议的话题。我测试了大量的模式,专门寻找符合 RFC822 规范的模式。下面使用的模式,由 Richard Willis 提供,是我找到的最好的模式。

<input type="email" pattern="^([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22))*\x40([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d))*$">

email 输入类型的一个“陷阱”是它允许没有顶级域名(“example.com”部分)的电子邮件地址(例如,[email protected])。这是因为 RFC822(电子邮件地址的标准)允许本地主机电子邮件,这些电子邮件不需要顶级域名。

如果您想要求顶级域名(您很可能需要),您可以修改 pattern 以强制执行域名扩展,如下所示

<input type="email" title="The domain portion of the email address is invalid (the portion after the @)." pattern="^([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22))*\x40([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d))*(\.\w{2,})+$">

同样,url 输入类型将提醒用户提供的 value 是否不是有效的 URL。同样,您应该为不支持此输入类型的浏览器提供模式。下面包含的模式改编自 Diego Perini 的一个项目,是我遇到的最健壮的模式。

<input type="url" pattern="^(?:(?:https?|HTTPS?|ftp|FTP):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)(?:\.(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)*)(?::\d{2,})?(?:[\/?#]\S*)?$">

email 属性类似,url 不需要顶级域名。如果您不想允许本地主机 URL,您可以更新模式以检查顶级域名,如下所示。

<input type="url" title="The URL is a missing a TLD (for example, .com)." pattern="^(?:(?:https?|HTTPS?|ftp|FTP):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)(?:\.(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)*(?:\.(?:[a-zA-Z\u00a1-\uffff]{2,}))\.?)(?::\d{2,})?(?:[/?#]\S*)?$">

查看 CodePen 上 Chris Ferdinandi (@cferdinandi) 的示例 表单验证:电子邮件和 URL

验证日期

有一些非常棒的输入类型,不仅可以验证日期,还可以提供原生的日期选择器。不幸的是,Chrome、Edge 和 Mobile Safari 是唯一实现它的浏览器。(我已经等待多年让 Firefox 采用此功能!更新:此功能有望 在不久的将来添加到 Firefox 中。)其他浏览器只是将其显示为文本字段。

和以往一样,我们可以提供 pattern 来捕获不支持它的浏览器。
date 输入类型用于标准的日/月/年日期。

<input type="date" pattern="(?:19|20)[0-9]{2}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-9])|(?:(?!02)(?:0[1-9]|1[0-2])-(?:30))|(?:(?:0[13578]|1[02])-31))">

在支持的浏览器中,选择的日期将显示为:MM/DD/YYYY注意:在美国。这对于其他国家/地区的用户或修改了日期设置的用户来说可能会有所不同)。但是,value 实际上是这种格式:YYYY-MM-DD

您应该为不支持的浏览器的用户提供有关此格式的指导——类似“请使用 YYYY-MM-DD 格式”。但是,您不希望使用 Chrome 或 Mobile Safari 的人看到这一点,因为这不是他们看到的格式,这会导致混淆。

查看 CodePen 上 Chris Ferdinandi (@cferdinandi) 的示例 表单验证:日期

简单的功能测试

我们可以编写一个简单的功能测试来检查支持情况。我们将创建一个 input[type="date"] 元素,添加一个不是有效日期的值,然后看看浏览器是否对其进行了清理。然后,您可以对支持 date 输入类型的浏览器隐藏描述性文本。

<label for="date">Date <span class="description-date">YYYY-MM-DDD</span></label>
<input type="date" id="date" pattern="(?:19|20)[0-9]{2}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-9])|(?:(?!02)(?:0[1-9]|1[0-2])-(?:30))|(?:(?:0[13578]|1[02])-31))">

<script>
  var isDateSupported = function () {
      var input = document.createElement('input');
      var value = 'a';
      input.setAttribute('type', 'date');
      input.setAttribute('value', value);
      return (input.value !== value);
  };

  if (isDateSupported()) {
      document.documentElement.className += ' supports-date';
  }
</scipt>

<style>
  .supports-date .description-date {
      display: none;
  }
</style>

查看 CodePen 上 Chris Ferdinandi ( @cferdinandi ) 的 表单验证:带特征测试的日期

其他日期类型

time 输入类型允许访客选择时间,而 month 输入类型允许他们从月份/年份选择器中进行选择。同样,我们也将为不支持的浏览器包含一个模式。

<input type="time" pattern="(0[0-9]|1[0-9]|2[0-3])(:[0-5][0-9])">
<input type="month" pattern="(?:19|20)[0-9]{2}-(?:(?:0[1-9]|1[0-2]))">

time 输入以 12 小时 AM/PM 格式显示时间,但 value 是 24 小时军用时间。month 输入在支持的浏览器中显示为 May 2017,但值是 YYYY-MM 格式。

就像 input[type="date"] 一样,您应该提供一个在支持的浏览器中隐藏的模式描述。

查看 CodePen 上 Chris Ferdinandi ( @cferdinandi ) 的 表单验证:以编程方式添加 novalidate

这看起来非常容易。有什么问题吗?

虽然约束验证 API 很简单且轻量级,但它确实有一些缺点。

您可以使用 :invalid 伪选择器为包含错误的字段设置样式,但无法为错误消息本身设置样式。

行为在不同的浏览器之间也不一致。Chrome 不会显示任何错误,直到您尝试提交表单。Firefox 在字段失去焦点时会显示红色边框,但仅在悬停时显示错误消息(而 WebKit 浏览器会保持错误持久显示)。

来自 Christian HolstLuke Wroblewski(分别)的用户研究发现,在用户离开字段时显示错误,并将该错误保持持久显示直到问题解决,提供了最佳和最快的用户体验。CSS 技巧:仅在当前未编辑时为无效选择器设置样式,使用 :not(:focus):invalid { }

不幸的是,默认情况下,没有一个浏览器完全按这种方式工作。

在本系列的下一篇文章中,我将向您展示如何使用原生约束验证 API 来使用一些轻量级的 JavaScript 将我们期望的 UX 集成到您的应用程序中。无需使用第三方库!

文章系列

  1. HTML 中的约束验证(您在这里!)
  2. JavaScript 中的约束验证 API
  3. 有效性状态 API 填充
  4. 验证 MailChimp 订阅表单