表单校验是前端交互里最基础也最容易暴露体验短板的环节。浏览器自带的验证提示虽然能省去不少逻辑,但那个默认气泡在视觉上常常和项目设计语言冲突,而且不同浏览器样式不一,很难统一。想要兼顾原生校验的便利与界面的可控,就需要用一些技巧把提示信息接管过来自己渲染。

一、理解原生验证与默认提示的局限
HTML5为表单控件提供了约束校验API,比如required、type="email"、minlength等属性。当用户输入不符合规则时,浏览器会阻止提交并弹出一个默认提示气泡。这个气泡由浏览器内核绘制,开发者无法通过CSS直接修改它的字体、背景或圆角。
除了样式不可控,默认提示的出现位置也飘忽不定,有时会遮挡输入框,在移动端尤其影响操作。另外,提示内容通常是浏览器内置文案,例如“请填写此字段”,无法针对业务场景给出更明确的指引。因此,许多团队选择关闭默认提示,改用自定义方案。
1.1 关闭默认气泡的基础方法
最简单的方式是在form元素上监听submit事件并调用preventDefault,同时利用novalidate属性让浏览器不自动弹窗。这样我们就能完全接管校验流程:
<form id="myForm" novalidate> <input type="text" name="username" required minlength="3" /> <span class="error-msg"></span> <button type="submit">提交</button> </form>
加上novalidate后,浏览器不再自动显示气泡,但控件的validity对象依然可用,我们可以通过JavaScript读取校验状态,再决定如何展示提示。
二、用invalid事件捕获校验失败
每个表单控件在校验失败时会触发invalid事件。通过这个事件,我们可以插入自定义提示,并阻止默认行为。相比在submit里统一遍历,invalid能更及时地定位到具体出错的字段。
const form = document.getElementById('myForm');
form.addEventListener('invalid', function (e) {
e.preventDefault();
const input = e.target;
const msgBox = input.nextElementSibling;
if (input.validity.valueMissing) {
msgBox.textContent = '此项为必填,请补充内容';
} else if (input.validity.tooShort) {
msgBox.textContent = '长度不能少于' + input.minLength + '个字符';
}
input.classList.add('input-error');
}, true);
注意这里使用了事件捕获阶段(第三个参数为true),因为invalid不会冒泡,只有在捕获阶段才能由form统一监听。上述代码把提示写进了输入框后面的span,并给输入框加上红色边框类,视觉上立刻友好很多。
2.1 动态清除提示
用户重新输入时,应该把错误提示消掉。可以监听input事件,当值变为有效就移除样式和文字:
form.addEventListener('input', function (e) {
const input = e.target;
if (input.checkValidity()) {
input.classList.remove('input-error');
const msgBox = input.nextElementSibling;
if (msgBox) msgBox.textContent = '';
}
});
这种随输随校的方式比提交后才报错更温和,也更符合当下主流表单的交互预期。
三、结合CSS美化提示样式
光有文字还不够,需要用CSS把提示做成和页面融为一体的组件。下面给出一组基础样式,包含输入框报错态与提示文字的排版:
.input-error {
border-color: #e74c3c;
box-shadow: 0 0 0 2px rgba(231, 76, 60, 0.15);
}
.error-msg {
display: block;
margin-top: 4px;
font-size: 12px;
color: #e74c3c;
line-height: 1.4;
}
通过伪元素还能做出小箭头指向输入框的效果,让提示看起来像气泡但不依赖浏览器原生UI。比如给.error-msg加上border和position定位即可。
3.1 使用:invalid伪类做轻量提示
如果不想写太多JS,也可以纯用CSS的:invalid伪类在输入过程中给边框变色。不过:invalid在页面加载时就会对空字段生效,需要配合:placeholder-shown避开初始状态:
input:not(:placeholder-shown):invalid {
border-color: #e74c3c;
}
input:not(:placeholder-shown):valid {
border-color: #2ecc71;
}
这种方式零脚本,但只能做样式变化,无法自定义文字内容,适合要求不高的场景。
四、用setCustomValidity输出业务化文案
当校验规则比较复杂,比如密码需要同时包含字母和数字,原生属性不够用时,可以调用setCustomValidity方法写入自定义错误描述,再借助前面的invalid监听显示出来。
const pwd = document.querySelector('input[name="password"]');
pwd.addEventListener('input', function () {
if (!/[a-zA-Z]/.test(pwd.value) || !/d/.test(pwd.value)) {
pwd.setCustomValidity('密码须同时包含字母和数字');
} else {
pwd.setCustomValidity('');
}
});
setCustomValidity设了非空字符串后,控件的validity.customError会变为true,checkValidity返回false,从而顺利进入我们的提示逻辑。清空字符串则恢复有效状态。
4.1 与后端校验的衔接
前端美化提示只是第一道关卡,最终仍要以服务端校验为准。当接口返回字段级错误时,可以复用同一套span提示结构,把后端msg填进去,保持视觉统一,避免用户面对两种风格的报错。
| 方案 | 优点 | 缺点 |
|---|---|---|
| 原生气泡 | 零代码、兼容好 | 样式不可控、文案固定 |
| invalid事件+自定义DOM | 文案灵活、样式自由 | 需写少量JS |
| 纯CSS :invalid | 无脚本、轻量 | 不能改提示文字 |
综合来看,用invalid事件配合setCustomValidity是平衡成本与体验的首选,既保留了HTML校验的可靠性,又让提示信息真正成为界面的一部分。