大多数 JavaScript 表单验证库都很大,并且通常需要其他库,例如 jQuery。例如,MailChimp 的嵌入式表单包含一个 140kb 的验证文件(已压缩)。它包含了整个 jQuery 库、一个第三方表单验证插件以及一些自定义 MailChimp 代码。实际上,这种设置是激发这个关于现代表单验证的新系列的原因。如今我们有哪些新的表单验证工具?什么可能?还需要什么?
在本系列中,我将向您展示两种在前端验证表单的轻量级方法。两者都利用了更新的 Web API。我还将教您如何将这些 API 的浏览器支持推回 IE9(这将为您提供对全球 99.6% 的网络流量的覆盖范围)。
最后,我们将看看 MailChimp 的注册表单,并提供相同的体验,但代码量减少了 28 倍。
值得一提的是,前端表单验证可以绕过。您也应该始终在服务器上验证您的代码。
好了,让我们开始吧!
文章系列
- HTML 中的约束验证(您在这里!)
- JavaScript 中的约束验证 API
- 有效性状态 API 填充
- 验证 MailChimp 订阅表单
超级简单的方法:约束验证
通过语义输入类型(例如,<input type="email">
)和验证属性(例如,required
和 pattern
)的组合,浏览器可以原生验证表单输入,并在用户操作错误时提醒他们。
各种输入类型和属性的支持 在不同浏览器之间差异很大,但我将提供一些技巧和解决方法来最大限度地提高浏览器兼容性。
基本文本验证
假设您有一个文本字段,用户必须在提交表单之前填写。添加 required
属性,支持的浏览器将提醒未填写该字段的用户,并拒绝让他们提交表单。
<input type="text" required>

您是否需要响应为最小或最大数量的字符?使用 minlength
和 maxlength
来强制执行这些规则。此示例要求值在 3 到 12 个字符之间。
<input type="text" minlength="3" maxlength="12">

pattern
属性允许您对输入值运行正则表达式验证。例如,如果您要求密码至少包含 1 个大写字母、1 个小写字母和 1 个数字,浏览器可以为您验证这一点。
<input type="password" pattern="^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?!.*\s).*$" required>

如果在 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>

您甚至可以将其与 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]+">
如果数字应该在一定范围内,浏览器可以使用 min
和 max
属性验证这些范围。您还应该修改 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 Holst 和 Luke Wroblewski(分别)的用户研究发现,在用户离开字段时显示错误,并将该错误保持持久显示直到问题解决,提供了最佳和最快的用户体验。CSS 技巧:仅在当前未编辑时为无效选择器设置样式,使用 :not(:focus):invalid { }
。
不幸的是,默认情况下,没有一个浏览器完全按这种方式工作。
在本系列的下一篇文章中,我将向您展示如何使用原生约束验证 API 来使用一些轻量级的 JavaScript 将我们期望的 UX 集成到您的应用程序中。无需使用第三方库!
文章系列
- HTML 中的约束验证(您在这里!)
- JavaScript 中的约束验证 API
- 有效性状态 API 填充
- 验证 MailChimp 订阅表单
很棒的介绍!
您可能计划在下一篇文章中介绍,但您可以使用
checkValidity()
函数强制 Chrome 在失去焦点时验证字段(或强制任何浏览器在任何时间验证字段):https://mdn.org.cn/en-US/docs/Web/API/HTMLSelectElement/checkValidity编辑:看起来对它的支持比我想象的要复杂一些。HTMLInputElement 也支持它,但 HTMLFormElement 却有
reportValidity()
,原因不明。不确定其他表单字段元素。在下一篇文章中,我将讨论 Validity State,它与
checkValidity()
做同样的事情,但功能更多/更好。我们将围绕我们的原生约束验证属性构建一个 JavaScript 增强的验证器,而无需编写包含大量正则表达式模式的大型库。
并且,为了解决您关于支持的问题,在第三篇文章中,我们将创建一个轻量级 polyfill,将缺少的功能添加到所有浏览器中,并将支持推回到 IE9。
很高兴看到对前端开发中经常被忽视的角落进行了全面概述。我几乎总是必须在网站上实现表单,而且我的验证方法一直在变化。很高兴看到一个包含经过充分测试的方法的概述的中心位置。
我喜欢这篇文章!作为一名广泛使用表单验证和输入屏蔽(同时尝试控制 JS 负载)的人,很高兴看到对原生 HTML5 功能的全面说明。我对 这段内容有一个观察结果。
我想指出,MS Edge 也具有一个很棒的
input type="date"
原生日期选择器。我的 Mac 偏见在这里显现出来。刚更新了文章。感谢您指出这一点!
对 Firefox 的等待即将结束,至少:Nightly 上启用了日期/时间输入
感谢!我听说过这件事,但写作时找不到链接来确认。刚更新了文章,以反映这一点。
很棒的文章,但是,您是否认为非原生的表单验证用于更灵活的解决方案?例如,不可能拥有非英语错误消息。
完全同意!我认为约束验证属性本身是不够的,而且您提到的问题——非英语错误——是一个重要的问题,我没有提到(应该提到)。
明天在第 2 部分中,我将逐步介绍如何使用浏览器原生的 JavaScript API 创建一个超轻量级的自定义验证脚本,它使您可以灵活地自定义错误,更改错误的显示方式和时间等。
我仍然认为它是“原生的”,但它比纯 HTML 版本更强大,而且比使用完全非原生的插件获得的方案要小得多。
我一直觉得有趣的一件事是,如果用户使用多字节字符(例如表情符号),则可以绕过 minlength。minlength 和 maxlength 实际上指的是字节数,而不是字符数,因此“☹️”的长度为 2。
这也会给经常使用多字节字符的语言带来问题。
不错的文章。对 CSS 技巧的补充。
要仅在未聚焦且为空(但有占位符)时为无效选择器设置样式,您可以执行以下操作。
注意:IE 和 Edge 不支持
:placeholder-shown
。“[…] 28 倍 (2,800%) 代码更少。”
数学不是这样算的。如果其他代码是您代码的 28 倍,那么您的代码是 1/28,即其他代码的 3.6%。这意味着代码减少了 96.4%。
代码减少 2800% 意味着:如果其他代码库有 100 行,那么您的代码库有 -2700 行。
</smart ass mode off>
感谢 Dominic!我确实为如何计算百分比而苦苦挣扎,显然失败了。我只删除了百分比。
Chris,写得不错。
您将在下一篇文章中添加通过 JavaScript 实现 ARIA 支持吗?
在 第 2 部分中,我谈到了如何使用
aria-describedby
将错误消息与各自的字段关联起来。不过,我深入探讨的程度仅限于此。3 到 42 之间的数字模式是错误的:
[3-42]
只会匹配“2”、“3”或“4”。您需要使用类似
([3-9]|[1-3][0-9]|4[0-2])
的内容。感谢您!显然,正则表达式的黑暗艺术不是我的强项。我真的很感谢这一点。刚更新了帖子。
不错。看起来 TLD 模式只接受 2 到 3 个字符,但它们可以更长:根据 RFC 1034,TLD 长度可以达到 63 个八位字节。
保罗,你抓到了!我在写这个系列的时候注意到了这一点,我以为我已经把它改好了,但是漏掉了一两个地方。已经修复了!
对于日期字段,显示的格式取自用户的操作系统设置。不能保证它们将采用美国的 MM/DD/YYYY 格式。
例如,在我的电脑上,日期格式为 YYYY-MM-DD,它们在 Chrome 中的字段中就是这样显示的。
感谢您的澄清。我刚刚相应地更新了帖子。
那么如何进行 i18n 本地化呢?
看起来它们会自动本地化 通过用户的浏览器区域设置,这可能是或可能不是最准确的方法。
在第二部分,我提供了一个 JavaScript 解决方案,它允许您自定义消息的位置和文本,这可能是对您来说更好的选择。
我觉得我已经等这篇文章好几年了,所以谢谢你 Chris。
至于不显示自定义错误消息的限制,难道不能通过将错误消息插入 HTML 并使用以下方法显示/隐藏它来显示消息吗?
谢谢 Luca!
PPK 实际上在他关于 原生表单验证的系列文章 中深入研究了这种事情,并确定了一些潜在的挑战。
PPK 最终得出了与我对事物整体状态不同的结论
我不同意他的观点,但如果你想真正详细地了解所有东西在幕后是如何工作的,他的系列文章 值得一看。