自定义验证消息
HTML5 表单验证提供了默认的浏览器提示消息,但这些消息通常以英文显示且样式不可控。通过 JavaScript 的 setCustomValidity()、invalid 事件和 reportValidity() 方法,可以实现自定义的中文验证消息和更友好的提示体验。
前置知识
阅读本节前,建议先了解:autofocus / tabindex
基础概念
Constraint Validation API
setCustomValidity() 是浏览器提供的自定义验证消息 API,它允许为控件设置自定义的错误提示文本。主要方法:
| 方法/属性 | 说明 |
|---|---|
setCustomValidity(message) | 设置自定义验证消息。空字符串表示验证通过 |
validity.customError | 是否存在自定义验证错误 |
validationMessage | 当前的验证消息文本 |
reportValidity() | 触发浏览器显示验证消息 |
checkValidity() | 检查验证状态但不显示消息 |
invalid 事件
当控件验证失败时触发 invalid 事件,可以在该事件中自定义消息:
javascript
input.addEventListener('invalid', function () {
if (this.validity.valueMissing) {
this.setCustomValidity('此字段不能为空');
}
});语法
setCustomValidity 基本用法
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>自定义验证消息</title>
</head>
<body>
<h2>自定义验证</h2>
<form id="custom-form">
<div>
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required
minlength="3" maxlength="20">
</div>
<div>
<label for="age">年龄:</label>
<input type="number" id="age" name="age" min="1" max="120">
</div>
<button type="submit">提交</button>
</form>
<script>
// 用户名自定义消息
const usernameInput = document.getElementById('username');
usernameInput.addEventListener('invalid', function () {
if (this.validity.valueMissing) {
this.setCustomValidity('请输入用户名');
} else if (this.validity.tooShort) {
this.setCustomValidity('用户名至少需要 3 个字符');
} else if (this.validity.tooLong) {
this.setCustomValidity('用户名不能超过 20 个字符');
} else {
this.setCustomValidity(''); // 清除自定义消息
}
});
// 输入时清除自定义消息
usernameInput.addEventListener('input', function () {
this.setCustomValidity('');
});
</script>
</body>
</html>reportValidity 和 checkValidity
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>reportValidity 和 checkValidity</title>
</head>
<body>
<h2>验证方法对比</h2>
<form id="demo-form">
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required>
<br><br>
<button type="button" id="check-btn">checkValidity()</button>
<button type="button" id="report-btn">reportValidity()</button>
<button type="submit">提交</button>
</form>
<p id="result"></p>
<script>
const form = document.getElementById('demo-form');
const result = document.getElementById('result');
// checkValidity:检查但不显示浏览器提示
document.getElementById('check-btn').addEventListener('click', function () {
const valid = form.checkValidity();
result.textContent = `checkValidity 返回:${valid}(不显示浏览器提示)`;
});
// reportValidity:检查并显示浏览器提示
document.getElementById('report-btn').addEventListener('click', function () {
const valid = form.reportValidity();
result.textContent = `reportValidity 返回:${valid}(显示浏览器提示)`;
});
// 表单提交前拦截
form.addEventListener('submit', function (e) {
e.preventDefault();
if (form.checkValidity()) {
result.textContent = '表单验证通过,可以提交';
} else {
form.reportValidity();
}
});
</script>
</body>
</html>详细说明
自定义消息匹配验证类型
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>多类型自定义消息</title>
<style>
body { font-family: sans-serif; max-width: 600px; margin: 40px auto; }
.form-group { margin-bottom: 16px; }
label { display: block; font-weight: 600; margin-bottom: 4px; }
input, select {
width: 100%;
padding: 8px 12px;
border: 2px solid #ddd;
border-radius: 6px;
box-sizing: border-box;
}
button { padding: 10px 24px; background: #4a90d9; color: #fff; border: none; border-radius: 6px; cursor: pointer; }
</style>
</head>
<body>
<h2>自定义验证消息</h2>
<form id="form">
<div class="form-group">
<label for="name">姓名:</label>
<input type="text" id="name" name="name" required minlength="2">
</div>
<div class="form-group">
<label for="phone">手机号:</label>
<input type="tel" id="phone" name="phone" pattern="1[3-9]\d{9}" required>
</div>
<div class="form-group">
<label for="age">年龄:</label>
<input type="number" id="age" name="age" min="1" max="120">
</div>
<button type="submit">提交</button>
</form>
<script>
// 为每个输入框设置自定义消息
function setupValidation(inputId, rules) {
const input = document.getElementById(inputId);
input.addEventListener('invalid', function () {
// 清除之前的消息
this.setCustomValidity('');
// 逐条检查验证规则
for (const rule of rules) {
if (this.validity[rule.check]) {
this.setCustomValidity(rule.message);
break;
}
}
});
input.addEventListener('input', function () {
this.setCustomValidity('');
});
}
setupValidation('name', [
{ check: 'valueMissing', message: '姓名不能为空' },
{ check: 'tooShort', message: '姓名至少需要 2 个字符' }
]);
setupValidation('phone', [
{ check: 'valueMissing', message: '手机号不能为空' },
{ check: 'patternMismatch', message: '请输入有效的 11 位手机号' }
]);
setupValidation('age', [
{ check: 'rangeUnderflow', message: '年龄不能小于 1' },
{ check: 'rangeOverflow', message: '年龄不能大于 120' },
{ check: 'typeMismatch', message: '请输入有效的数字' }
]);
</script>
</body>
</html>自定义气泡消息 vs 内联消息
浏览器内置的验证气泡(tooltip)在不同浏览器中样式不一致。许多开发者选择使用内联错误消息替代:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>内联验证消息</title>
<style>
body { font-family: sans-serif; max-width: 600px; margin: 40px auto; }
.form-group { margin-bottom: 16px; }
label { display: block; font-weight: 600; margin-bottom: 4px; }
input {
width: 100%;
padding: 8px 12px;
border: 2px solid #ddd;
border-radius: 6px;
box-sizing: border-box;
}
.error {
color: #e74c3c;
font-size: 14px;
margin-top: 4px;
display: none;
}
input:not(:focus):invalid ~ .error {
display: block;
}
button {
padding: 10px 24px;
background: #4a90d9;
color: #fff;
border: none;
border-radius: 6px;
cursor: pointer;
}
</style>
</head>
<body>
<h2>内联验证消息</h2>
<!-- 使用 novalidate 禁用浏览器默认验证 -->
<form id="form" novalidate>
<div class="form-group">
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required>
<p class="error" id="email-error">请输入有效的邮箱地址</p>
</div>
<div class="form-group">
<label for="password">密码:</label>
<input type="password" id="password" name="password" required minlength="8">
<p class="error" id="password-error">密码至少需要 8 位</p>
</div>
<button type="submit">提交</button>
</form>
<script>
// 使用 CSS :invalid 伪类 + 相邻选择器实现内联消息
// 无需 JavaScript,纯 CSS 方案
</script>
</body>
</html>validationMessage 属性
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>validationMessage</title>
</head>
<body>
<h2>获取验证消息</h2>
<form>
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required>
<br><br>
<button type="button" onclick="showMessage()">查看验证消息</button>
</form>
<p id="msg"></p>
<script>
function showMessage() {
const input = document.getElementById('email');
const valid = input.checkValidity();
document.getElementById('msg').textContent =
`验证通过:${valid},消息:${input.validationMessage}`;
}
</script>
</body>
</html>实战示例
统一验证系统
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>统一验证系统</title>
<style>
body { font-family: sans-serif; max-width: 600px; margin: 40px auto; }
.form-group { margin-bottom: 16px; position: relative; }
label { display: block; font-weight: 600; margin-bottom: 4px; }
input { width: 100%; padding: 8px 12px; border: 2px solid #ddd; border-radius: 6px; box-sizing: border-box; }
.tip { font-size: 13px; color: #666; margin-top: 2px; }
.error { font-size: 14px; color: #e74c3c; margin-top: 4px; display: none; }
button { padding: 10px 24px; background: #4a90d9; color: #fff; border: none; border-radius: 6px; cursor: pointer; }
</style>
</head>
<body>
<h2>注册</h2>
<form id="form" novalidate>
<div class="form-group">
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required minlength="3" maxlength="20"
data-error-required="用户名不能为空" data-error-minlength="用户名至少3个字符">
<p class="error" id="username-error"></p>
</div>
<div class="form-group">
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required
data-error-required="邮箱不能为空" data-error-type="请输入有效的邮箱">
<p class="error" id="email-error"></p>
</div>
<button type="submit">注册</button>
</form>
<script>
// 统一验证逻辑
document.getElementById('form').addEventListener('submit', function (e) {
e.preventDefault();
let valid = true;
this.querySelectorAll('input').forEach(input => {
const errorEl = document.getElementById(input.id + '-error');
input.setCustomValidity('');
if (!input.checkValidity()) {
valid = false;
// 先尝试 data-* 自定义消息
const customMsg = input.dataset['errorRequired'] ||
input.dataset['errorMinlength'] ||
input.dataset['errorType'] || '';
if (customMsg) {
input.setCustomValidity(customMsg);
}
errorEl.textContent = input.validationMessage;
errorEl.style.display = 'block';
} else {
errorEl.style.display = 'none';
}
});
if (valid) {
alert('验证通过!');
}
});
// 输入时清除错误
document.querySelectorAll('#form input').forEach(input => {
input.addEventListener('input', function () {
this.setCustomValidity('');
document.getElementById(this.id + '-error').style.display = 'none';
});
});
</script>
</body>
</html>注意事项
setCustomValidity 与 invalid 事件
setCustomValidity('')清除自定义消息,恢复默认验证行为- 必须在
invalid事件中设置自定义消息,否则默认消息已显示 input事件中清除自定义消息,以便用户修正后重新验证
novalidate 禁用所有浏览器验证
<form novalidate> 禁用浏览器的默认验证,需要完全用 JavaScript 实现验证逻辑。
validationMessage 的语言
浏览器默认的 validationMessage 可能是英文的。使用 setCustomValidity() 可以设置为中文。
最佳实践
- 中文消息:通过
setCustomValidity()替代浏览器默认的英文提示 - 输入时清除:在
input事件中调用setCustomValidity('') - 统一验证框架:使用
data-*属性配置验证规则和消息 - 内联消息优先:自定义内联消息比浏览器 tooltip 体验更好
- 配合 CSS 伪类:
:valid/:invalid提供实时视觉反馈 - 服务端验证并行:客户端验证增强体验,服务端验证保障安全
下一节