HTML 表单验证

表单验证确保用户提交的数据符合预期格式。HTML 提供了浏览器内置验证,无需 JavaScript。对于复杂场景,也可以使用 Constraint Validation API。

本章后半部分涉及 JavaScript 代码(Constraint Validation API、自定义验证逻辑)。如果你尚未学习 JavaScript,可以仅阅读”浏览器内置验证”和”CSS 伪类”两节,后续需深入时再回来查阅。

浏览器内置验证

通过属性组合即可实现基础验证:

html
<form>
  <input type="text" name="username" required minlength="3" maxlength="20">
  <input type="email" name="email" required>
  <input type="number" name="age" min="1" max="150">
  <input type="password" name="password" required pattern=".{8,}">
  <button type="submit">提交</button>
</form>

浏览器会阻止提交并在无效字段旁显示提示(提示文字因浏览器而异)。

自定义验证信息

使用 Constraint Validation API 可以定制错误消息和验证逻辑:

javascript
const emailInput = document.querySelector('input[name="email"]');

emailInput.addEventListener('input', () => {
  if (emailInput.validity.typeMismatch) {
    emailInput.setCustomValidity('请输入有效的邮箱地址');
  } else if (emailInput.validity.valueMissing) {
    emailInput.setCustomValidity('邮箱不能为空');
  } else {
    emailInput.setCustomValidity(''); // 清除自定义错误
  }
});

ValidityState 属性

每个表单控件都有一个 validity 对象,包含以下布尔属性:

属性含义
valueMissing必填字段为空
typeMismatch值与 type 不匹配(如 email 不含 @)
patternMismatch值与 pattern 正则不匹配
tooLong / tooShort文本过长/过短
rangeUnderflow / rangeOverflow数字超出 min/max
stepMismatch数字不符合 step
badInput浏览器无法解析输入
valid所有约束都满足
javascript
const form = document.querySelector('form');
form.addEventListener('submit', (e) => {
  if (!form.checkValidity()) {
    e.preventDefault();
    // 遍历所有控件,显示自定义错误
    form.querySelectorAll(':invalid').forEach(el => {
      console.log(el.name, el.validationMessage);
    });
  }
});

实时验证(表单提交前)

javascript
const inputs = document.querySelectorAll('input');

inputs.forEach(input => {
  input.addEventListener('blur', () => {
    // 失去焦点时验证
    validateField(input);
  });

  input.addEventListener('input', () => {
    // 如果之前有错误,实时清除
    if (input.validity.valid) {
      clearError(input);
    }
  });
});

function validateField(field) {
  const errorSpan = document.getElementById(`${field.name}-error`);
  if (!field.validity.valid) {
    field.classList.add('invalid');
    errorSpan.textContent = field.validationMessage;
  } else {
    field.classList.remove('invalid');
    errorSpan.textContent = '';
  }
}

reportValidity()checkValidity()

javascript
// checkValidity() — 仅返回 true/false,不触发 UI 提示
if (input.checkValidity()) { /* 有效 */ }

// reportValidity() — 返回 true/false,并触发浏览器的 UI 提示
if (!input.reportValidity()) { /* 无效,已显示错误 */ }

CSS 伪类

浏览器为验证状态提供了 CSS 伪类,可直接控制样式:

css
input:valid    { border-color: green; }
input:invalid  { border-color: red; }
input:required { border-left-color: var(--color-accent); }
input:optional { border-style: dashed; }
input:in-range   { background: #f0fff0; }
input:out-of-range { background: #fff0f0; }

:invalid 伪类在页面加载时就生效,意味着用户还没开始填写,必填的空字段就已经被标记为无效。最好在用户交互后才触发——结合 JavaScript 或使用 :user-invalid(更新的伪类,在用户尝试提交后才触发)。

完整的验证示例

html
<form id="signup" novalidate>
  <div>
    <label for="name">姓名</label>
    <input type="text" id="name" name="name" required minlength="2">
    <span class="error" id="name-error"></span>
  </div>
  <div>
    <label for="email">邮箱</label>
    <input type="email" id="email" name="email" required>
    <span class="error" id="email-error"></span>
  </div>
  <div>
    <label for="password">密码</label>
    <input type="password" id="password" name="password"
           required minlength="8"
           pattern="^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$">
    <span class="error" id="password-error"></span>
    <small>至少 8 位,包含字母和数字</small>
  </div>
  <button type="submit">注册</button>
</form>

novalidate 禁用浏览器原生提示,由 JavaScript 接管所有验证提示,保持跨浏览器一致的 UI。