在本文中,我们将研究 HTML表单字段和 HTML5 提供的验证选项。我们还将研究如何通过使用 CSS和 JavaScript来增强这些功能。
什么是约束验证【Constraint Validation】?
每个表单域都有一个目的。并为此经常管辖约束【Constraint】上或理事什么应该和不应该被输入到每个表单域的规则 。例如,一个email
字段需要一个有效的电子邮件地址;一个password
字段可能需要某些类型的字符,并且有最少数量的必需字符;并且文本字段可能对可以输入的字符数有限制。
现代浏览器能够检查用户是否遵守了这些约束【Constraint】,并可以在违反这些规则时向他们发出警告。这称为约束验证【Constraint Validation】。
客户端与服务器端验证
在语言早期编写的大多数 JavaScript代码处理客户端表单验证。即使在今天,开发人员仍花费大量时间编写函数来检查字段值。这在现代浏览器中仍然必要吗?可能不是。在大多数情况下,这实际上取决于您要尝试做什么。
但首先,这里有一个重要的警告信息:
客户端验证是一项很好的功能,它可以在应用程序浪费时间和带宽将数据发送到服务器之前防止常见的数据输入错误。它不能替代服务器端验证!
始终清理服务器端的数据。并非每个请求都来自浏览器。即使这样做,也不能保证浏览器验证数据。任何知道如何打开浏览器开发工具的人也可以绕过您精心制作的 HTML 和 JavaScript。
HTML5 输入字段 {#html5inputfields}
HTML 提供:
-
<textarea>
用于多行文本框 -
<select>
用于选项的下拉列表 -
<button>
用于......按钮
但你<input>
最常使用的是:
<input type="text" name="username" />
该type
属性设置控件类型,并且有很多选项可供选择:
| type
| 描述 |
|------------------|------------------------------------|
| button
| 一个没有默认行为的按钮 |
| checkbox
| 一个复选框 |
| color
| 颜色选择器 |
| date
| 年、月、日的日期选择器 |
| datetime-local
| 日期和时间选择器 |
| email
| 电子邮件输入字段 |
| file
| 文件选择器 |
| hidden
| 一个隐藏的领域 |
| image
| 显示由src
属性定义的图像的按钮 |
| month
| 月份和年份选择器 |
| number
| 数字输入字段 |
| password
| 带有模糊文本的密码输入字段 |
| radio
| 一个单选按钮 |
| range
| 滑块控件 |
| reset
| 将所有表单输入重置为其默认值的按钮(但请避免使用它,因为它很少有用) |
| search
| 搜索输入字段 |
| submit
| 一个表单提交按钮 |
| tel
| 电话号码输入字段 |
| text
| 文本输入字段 |
| time
| 没有时区的时间选择器 |
| url
| URL 输入字段 |
| week
| 周数和年份选择器 |
text
如果您省略该type
属性或它不支持某个选项,则浏览器会回退到。现代浏览器对所有类型都有很好的支持,但旧浏览器仍会显示文本输入字段。
其他有用的<input>
属性包括:
| 属性 | 描述 |
|----------------|-----------------------------|
| accept
| 文件上传类型 |
| alt
| 图像类型的替代文本 |
| autocomplete
| 字段自动完成提示 |
| autofocus
| 页面加载时的焦点字段 |
| capture
| 媒体捕捉输入法 |
| checked
| 复选框/收音机被选中 |
| disabled
| 禁用控件(它不会被验证或提交其值) |
| form
| 与使用此 ID 的表单关联 |
| formaction
| 提交和图像按钮上提交的 URL |
| inputmode
| 数据类型提示 |
| list
| 自动完成选项的ID<datalist>
|
| max
| 最大值 |
| maxlength
| 最大字符串长度 |
| min
| 最小值 |
| minlength
| 最小字符串长度 |
| name
| 提交给服务器的控件名称 |
| pattern
| 正则表达式模式,例如[A-Z]+
一个或多个大写字符 |
| placeholder
| 字段值为空时的占位符文本 |
| readonly
| 该字段不可编辑,但仍将被验证并提交 |
| required
| 该字段是必需的 |
| size
| 控件的大小(通常在 CSS 中被覆盖) |
| spellcheck
| 设置true
或false
拼写检查 |
| src
| 图片网址 |
| step
| 数字和范围的增量值 |
| type
| 字段类型(见上文) |
| value
| 初始值 |
HTML 输出字段 {#htmloutputfields}
除了输入类型,HTML5 还提供只读输出:
-
output
: 计算或用户操作的文本结果 -
progress
: 带有value
和max
属性的进度条 -
meter
:它可以根据对设定的值绿色,琥珀色和红色之间改变规模value
,min
,max
,low
,high
,和optimum
属性。
输入标签 {#inputlabels}
字段应该有一个关联的<label>
,您可以将其包裹在元素周围:
<label>your name <input type="text" name="name" /><label>
或者id
使用for
属性将字段链接到标签:
<label for="nameid">your name</label><input type="text" id="nameid" name="name" />
标签对于可访问性很重要。您可能遇到过使用 aplaceholder
来节省屏幕空间的表单:
<input type="text" name="name" value="" placeholder="your name" />
一旦用户输入内容,占位符文本就会消失------即使是一个空格。最好显示标签而不是强迫用户记住该字段想要什么!
输入行为 {#inputbehaviors}
字段类型和约束【Constraint】属性会改变浏览器的输入行为。例如,number
输入显示移动设备上的数字键盘。该字段可能会显示一个微调器,键盘上/下光标按下将增加和减少值。
大多数字段类型是显而易见的,但也有例外。例如,信用卡是数字,但增量/减量微调器没用,输入 16 位数字时很容易向上或向下按。最好使用标准text
类型,但将inputmode
属性设置为numeric
,这会显示合适的键盘。设置autocomplete="cc-number"
还建议任何预先配置或以前输入的卡号。
使用正确的字段type
并autocorrect
提供在 JavaScript 中难以实现的好处。例如,一些移动浏览器可以:
-
通过使用相机扫描卡来导入信用卡详细信息
-
导入短信发送的一次性代码
自动验证 {#automaticvalidation}
该浏览器可以确保与由定义的约束【Constraint】的输入值附着type
,min
,max
,step
,minlength
,maxlength
,pattern
,和required
属性。例如:
<input type="number" min="1" max="100" required />
尝试提交空值会阻止表单提交并在 Chrome 中显示以下消息:
微调器不允许 1 到 100 范围之外的值。如果您键入的字符串不是数字,则会出现类似的验证消息。所有这些都没有一行 JavaScript。
您可以通过以下方式停止浏览器验证:
-
novalidate
给<form>
元素添加一个属性 -
向
formnovalidate
提交按钮或图像添加属性
创建自定义 JavaScript 输入 {#creatingcustomjavascriptinputs}
如果您正在编写一个新的基于 JavaScript 的日期输入组件,请停止并远离您的键盘!
编写自定义输入控件很困难。您必须考虑鼠标、键盘、触摸、语音、可访问性、屏幕尺寸以及 JavaScript 失败时会发生什么。您也在创造不同的用户体验。也许你的控制比桌面、iOS 和 Android 上的标准日期选择器要好,但不熟悉的 UI 会让一些用户感到困惑。
开发人员选择创建基于 JavaScript 的输入有三个主要原因。
1. 标准控件难以设计风格 {#1standardcontrolsaredifficulttostyle}
CSS 样式是有限的,通常需要技巧,例如用标签::before
和::after
伪元素覆盖输入。情况正在改善,但质疑任何将形式置于功能之上的设计。
2.<input>
旧浏览器不支持现代类型 {#2moderninputtypesarenotsupportedinoldbrowsers}
本质上,您正在为 Internet Explorer 编码。IE 用户不会获得日期选择器,但仍可以按YYYY-MM-DD
格式输入日期。如果您的客户坚持,则仅在 IE 中加载 polyfill。没有必要给现代浏览器增加负担。
3. 您需要一种以前从未实现过的新输入类型 {#3yourequireanewinputtypewhichhasneverbeenimplementedbefore}
这些情况很少见,但总是从适当的 HTML5 字段开始。它们很快,甚至在脚本加载之前它们就可以工作。您可以根据需要逐步增强字段。例如,少量的 JavaScript 可以确保日历事件的结束日期发生在开始日期之后。
总之:避免重新发明 HTML 控件!
CSS 验证样式 {#cssvalidationstyling}
您可以将以下伪类应用于输入字段以根据当前状态对其进行样式设置:
| 选择器 | 描述 |
|------------------|-------------------------------|
| :focus
| 重点领域 |
| :focus-within
| 一个元素包含一个具有焦点的字段(是的,它是一个父选择器!) |
| :focus-visible
| 由于键盘导航,元素具有焦点,因此需要焦点环或更明显的样式 |
| :required
| 具有required
属性的字段 |
| :optional
| 没有required
属性的字段 |
| :valid
| 已通过验证的字段 |
| :invalid
| 未通过验证的字段 |
| :user-valid
| 在用户与其交互后通过验证的字段(仅限 Firefox) |
| :user-invalid
| 用户与其交互后未通过验证的字段(仅限 Firefox) |
| :in-range
| 该值在 anumber
或range
输入的范围内 |
| :out-of-range
| 该值超出了 anumber
或range
输入的范围 |
| :disabled
| 具有disabled
属性的字段 |
| :enabled
| 没有disabled
属性的字段 |
| :read-only
| 具有read-only
属性的字段 |
| :read-write:
| 没有read-only
属性的字段 |
| :checked
| 选中的复选框或单选按钮 |
| :indeterminate
| 不确定的复选框或单选状态,例如取消选中所有单选按钮时 |
| :default
| 默认提交按钮或图像 |
您可以placeholder
使用::placeholder
伪元素设置输入文本的样式:
/* blue placeholder on email fields */input[type="email"]::placeholder {
color: blue;}
上面的选择器具有相同的特性,因此顺序可能很重要。考虑这个例子:
input:invalid { color: red; }input:enabled { color: black; }
无效输入具有红色文本,但它仅适用于具有disabled
属性的输入------因此所有启用的输入都是黑色的。
浏览器在页面加载时应用验证样式。例如,在下面的代码中,每个无效字段都有一个红色边框:
:invalid {
border-color: #900;}
用户在与表单交互之前会遇到一组令人生畏的红色框。在第一次提交后或更改值时显示验证错误将提供更好的体验。这就是 JavaScript 介入的地方......
JavaScript 和约束验证【Constraint Validation】 API {#javascriptandtheconstraintvalidationapi}
该约束验证【Constraint Validation】API提供了可增强标准的HTML现场检查表单自定义选项。你可以:
-
停止验证,直到用户与字段交互或提交表单
-
使用自定义样式显示错误消息
-
提供仅在 HTML 中无法实现的自定义验证。当您需要比较两个输入时,这通常是必要的------例如,当您输入电子邮件地址或电话号码时,检查"新"和"确认"密码字段是否具有相同的值,或确保一个日期接一个日期。
表单验证 {#formvalidation}
在使用 API 之前,您的代码应该通过将表单的noValidate
属性设置为true
(与添加novalidate
属性相同)来禁用默认验证和错误消息:
const myform = document.getElementById('myform');myform.noValidate = true;
然后你可以添加事件处理程序------比如当表单提交时:
myform.addEventListener('submit', validateForm);
处理程序可以使用checkValidity()
orreportValidity()
方法检查整个表单是否有效,true
当表单的所有输入都有效时返回。(不同之处在于checkValidity()
检查是否有任何输入受约束验证【Constraint Validation】。)
Mozilla 文档解释说:
invalid
每个无效字段也会触发一个事件。这不会冒泡:必须将处理程序添加到使用它的每个控件中。
// validate form on submissionfunction validateForm(e) {
const form = e.target;
if (form.checkValidity()) {
// form is valid - make further checks
}
else {
// form is invalid - cancel submit
e.preventDefault();
}};
有效的表单现在可能会导致进一步的验证检查。同样,无效表单可能会突出显示无效字段。
现场验证 {#fieldvalidation}
各个字段具有以下约束验证【Constraint Validation】属性:
-
willValidate
:true
如果元素是约束验证【Constraint Validation】的候选元素,则返回。 -
validationMessage
: 验证消息。如果该字段有效,这将是一个空字符串。 -
valitity
:一个ValidityState 对象。当字段有效时,它有一个valid
属性集true
。如果是false
,则以下一项或多项属性将是true
:| 有效性状态 | 描述 | |--------------------|----------------------| |
.badInput
| 浏览器无法理解输入 | |.customError
| 已设置自定义有效性消息 | |.patternMismatch
| 该值与指定的pattern
属性不匹配 | |.rangeOverflow
| 值大于max
属性 | |.rangeUnderflow
| 值小于min
属性 | |.stepMismatch
| 该值不符合step
属性规则 | |.tooLong
| 字符串长度大于maxlength
属性 | |.tooShort
| 字符串长度小于minlength
属性 | |.typeMismatch
| 该值不是有效的电子邮件或 URL | |.valueMissing
| 一个required
值为空 |
各个字段具有以下约束验证【Constraint Validation】方法:
-
setCustomValidity(message)
: 为无效字段设置错误消息。当该字段有效时必须传递一个空字符串,否则该字段将永远无效。 -
checkValidity()
:true
当输入有效时返回。该valitity.valid
属性执行相同的操作,但checkValidity()
还会invalid
在该字段上触发一个可能有用的事件。
该validateForm()
处理函数可以遍历各个领域,并应用invalid
类,它的父元素在必要时:
function validateForm(e) {
const form = e.target;
if (form.checkValidity()) {
// form is valid - make further checks
}
else {
// form is invalid - cancel submit
e.preventDefault();
// apply invalid class
Array.from(form.elements).forEach(i => {
if (i.checkValidity()) {
// field is valid - remove class
i.parentElement.classList.remove('invalid');
}
else {
// field is invalid - add class
i.parentElement.classList.add('invalid');
}
});
}};
假设您的 HTML 定义了一个电子邮件字段:
<div>
<label for="email">email</label>
<input type="email" id="email" name="email" required />
<p class="help">Please enter a valid email address</p></div>
当电子邮件未指定或无效时,脚本将invalid
类应用到<div>
。CSS 可以在表单提交时显示或隐藏验证消息:
.help { display: none; }.invalid .help { display: block; }.invalid label, .invalid input, .invalid .help {
color: red;
border-color: red;}
创建自定义表单验证器 {#creatingacustomformvalidator}
以下演示显示了一个示例联系表单,它需要用户名和电子邮件地址、电话号码或两者:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Web前端表单开发:HTML5表单和Constraint Validation完整指南 | Web前端之家www.jiangweishan.com</title>
<style>
* {
font-family: sans-serif;
font-size: 1em;
}
div {
width: 100%;
max-width: 15em;
margin: 1em auto;
}
label, input {
display: block;
width: 100%;
}
input {
padding: 0.5em;
border: 1px solid #666;
border-radius: 4px;
}
button {
display: block;
margin: 0 auto;
}
.help {
display: none;
font-size: 0.8em;
text-align: center;
margin: 0.2em 0 1em 0;
color: #c00;
}
.invalid .help {
display: block;
}
.invalid label, .invalid input {
color: #c00;
border-color: #c00;
}
</style>
</head>
<body>
<form id="contact" action="./" method="post">
<div>
<label for="username">注册Web前端之家账号</label>
<input type="text" autocomplete="username" id="username" name="username" required />
<p class="help">Please enter your username.</p>
</div>
<div>
<label for="email">注册Web前端之家邮箱</label>
<input type="email" autocomplete="email" id="email" name="email" />
<p class="help">Please enter your email or telephone.</p>
</div>
<div>
<label for="tel">手机号码</label>
<input type="tel" autocomplete="tel" id="tel" name="tel" />
<p class="help">Please enter your email or telephone.</p>
</div>
<div>
<button type="submit">submit</button>
</div>
</form>
<script>
// custom form validation
class FormValidate {
constructor(form, field) {
// active form
this.form = form;
this.form.noValidate = true;
// custom validation functions
this.custom = [];
// validate fields on focus change?
this.validate = !!field;
// field focusout event
this.form.addEventListener('focusout', e => this.changeHandler(e) );
// form submit event
this.form.addEventListener('submit', e => this.submitHandler(e) );
}
// add a custom validation function
// it's passed the field and must return true (valid) or false (invalid)
addCustom(field, vfunc) {
// get index
let c = field.CustomValidator;
if (typeof c === 'undefined') {
c = this.custom.length;
field.CustomValidator = c;
}
// store function reference
this.custom[c] = (this.custom[c] || []).concat(vfunc);
}
// validate a field when focus changes
changeHandler(e) {
const t = e.target;
if (this.validate && t && t.checkValidity) this.validateField(t);
}
// validate all fields on submit
submitHandler(e) {
// validate all fields
let first, invCount = 0;
Array.from(this.form.elements).forEach(f => {
if (!this.validateField(f)) {
// find first visible invalid
if (f.offsetHeight) first = first || (f.focus && f);
invCount++;
}
});
// at least one field is invalid
if (invCount) {
// stop submission
e.stopImmediatePropagation();
e.preventDefault();
// enable field focusout validation
this.validate = true;
// focus first invalid field
if (first) {
first.parentElement.scrollIntoView(true);
setTimeout(() => first.focus(), 800);
}
}
// form is valid - submit
else if (this.submit) this.submit(e);
}
// validate a field
validateField(field) {
const
parent = field.parentElement,
c = field.CustomValidator,
inv = 'invalid';
field.setCustomValidity('');
// default validation
let valid = field.checkValidity();
// custom validation
if (valid && typeof c !== 'undefined') {
valid = !this.custom[c].some(fn => !fn(field));
}
if (valid) {
// field is valid
parent.classList.remove(inv);
return true;
}
else {
// field is not valid
field.setCustomValidity(inv);
parent.classList.add(inv);
return false;
}
}
}
// ___________________________________________________________________
// validate contact form
const contactForm = new FormValidate(document.getElementById('contact'), false);
// custom validation - email and/or telephone
const
email = document.getElementById('email'),
tel = document.getElementById('tel');
contactForm.addCustom(email, f => f.value || tel.value);
contactForm.addCustom(tel, f => f.value || email.value);
// custom submit
contactForm.submit = e => {
e.preventDefault();
alert('Form is valid!\n(open the console)');
const fd = new FormData(e.target);
for (const [name, value] of fd.entries()) {
console.log(name + ': ' + value);
}
}
</script>
</body>
</html>
它是使用名为 的通用表单验证类实现的FormValidate
。实例化对象时传递表单元素。可以设置可选的第二个参数:
-
true
在用户与其交互时验证每个字段 -
false
(默认)在第一次提交后验证所有字段(在此之后进行字段级验证)
// validate contact formconst contactForm = new FormValidate(document.getElementById('contact'), false);
一个.addCustom(field, func)
方法定义了自定义验证函数。以下代码确保email
或tel
字段有效(都没有required
属性):
// custom validation - email and/or telephoneconst
email = document.getElementById('email'),
tel = document.getElementById('tel');
contactForm.addCustom(email, f => f.value || tel.value);
contactForm.addCustom(tel, f => f.value || email.value);
一个FormValidate
对象监视以下两个:
-
focusout
事件,然后检查单个字段 -
表单
submit
事件,然后检查每个字段
两者都调用该.validateField(field)
方法,该方法检查字段是否通过标准约束验证【Constraint Validation】。当它这样做时,分配给该字段的任何自定义验证功能将依次执行。必须全部返回true
才能使该字段有效。
无效字段具有invalid
应用于该字段的父元素的类,该类使用 CSS 显示红色帮助消息。
最后,submit
当整个表单有效时,对象调用自定义函数:
// custom submitcontactForm.submit = e => {
e.preventDefault();
alert('Form is valid!\n(open the console)');
const fd = new FormData(e.target);
for (const [name, value] of fd.entries()) {
console.log(name + ': ' + value);
}}
或者,您可以使用标准addEventListener
来处理表单submit
事件,因为FormValidate
当表单无效时可以防止进一步的处理程序运行。
形式技巧 {#formfinesse}
表单是所有 Web 应用程序的基础,开发人员花费大量时间处理用户输入。约束验证【Constraint Validation】得到很好的支持:浏览器可以处理大多数检查并显示适当的输入选项。
建议:
-
尽可能使用标准的 HTML 输入类型。集
min
,max
,step
,minlength
,maxlength
,pattern
,required
,inputmode
,和autocomplete
属性适当。 -
如有必要,使用一点 JavaScript 来启用自定义验证和消息。
-
对于更复杂的字段,逐步增强标准输入。
最后:忘记 Internet Explorer!
除非您的客户主要是 IE 用户,否则没有必要实现您自己的回退验证功能。所有 HTML5 输入字段都可以在 IE 中使用,但可能需要更多的用户努力。(例如,当您输入无效的电子邮件地址时,IE 不会检测到。)您仍然需要验证服务器上的数据,因此请考虑将其用作 IE 错误检查的基础。