客户端表单验证

在将用户输入的表单数据提交到服务器之前,确保所有必需的表单控件都已填写并格式正确非常重要。这种客户端表单验证有助于确保输入的数据符合各种表单控件中设定的要求。

本文将引导您了解客户端表单验证的基本概念和示例。

预备知识 计算机基础知识,对HTMLCSSJavaScript 的合理理解。
目标 了解什么是客户端表单验证,为什么它很重要,以及如何应用各种技术来实现它。

客户端验证是初始检查,也是良好用户体验的重要组成部分;通过在客户端捕获无效数据,用户可以立即修复。如果数据到达服务器然后被拒绝,则由于往返服务器再返回客户端告知用户修复其数据,会导致明显的延迟。

但是,客户端验证不应被视为详尽的安全措施!您的应用程序应始终在服务器端以及客户端对任何表单提交的数据执行验证,包括安全检查,因为客户端验证太容易绕过,恶意用户仍然可以轻松地向您的服务器发送不良数据。

注意:阅读网站安全以了解可能发生的情况;实现服务器端验证在本文档的范围之外,但您应该牢记这一点。

什么是表单验证?

访问任何带有注册表的热门网站,您会注意到当您未按预期格式输入数据时,它们会提供反馈。您会收到以下消息:

  • “此字段为必填项”(您不能将此字段留空)。
  • “请以 xxx-xxxx 格式输入您的电话号码”(需要特定的数据格式才能被视为有效)。
  • “请输入有效的电子邮件地址”(您输入的数据格式不正确)。
  • “您的密码长度需介于 8 到 30 个字符之间,并包含一个大写字母、一个符号和一个数字。”(您的数据需要非常特定的数据格式)。

这称为表单验证。当您输入数据时,浏览器(和 Web 服务器)会检查数据是否格式正确并在应用程序设置的约束范围内。在浏览器中完成的验证称为客户端验证,而在服务器上完成的验证称为服务器端验证。在本章中,我们专注于客户端验证。

如果信息格式正确,应用程序允许数据提交到服务器并(通常)保存到数据库中;如果信息格式不正确,它会向用户显示一条错误消息,解释需要更正的内容,并让他们再次尝试。

我们希望尽可能简化 Web 表单的填写。那么我们为什么要坚持验证表单呢?主要有三个原因:

  • 我们希望以正确的格式获取正确的数据。如果用户数据以错误的格式存储、不正确或完全省略,我们的应用程序将无法正常工作。

  • 我们希望保护用户数据。强制用户输入安全密码可以更轻松地保护他们的帐户信息。

  • 我们希望保护自己。恶意用户可以通过多种方式滥用未受保护的表单来损害应用程序。请参阅网站安全

    警告:切勿信任从客户端传递到服务器的数据。即使您的表单正确验证并阻止了客户端上格式错误​​的输入,恶意用户仍然可以更改网络请求。

不同类型的客户端验证

您将在 Web 上遇到两种不同类型的客户端验证:

  • HTML 表单验证 HTML 表单属性可以定义哪些表单控件是必需的,以及用户输入的数据必须采用哪种格式才能有效。
  • JavaScript 表单验证 JavaScript 通常用于增强或自定义 HTML 表单验证。

客户端验证只需很少甚至无需 JavaScript 即可完成。HTML 验证比 JavaScript 快,但比 JavaScript 验证的可定制性差。通常建议使用健壮的 HTML 功能开始您的表单,然后根据需要使用 JavaScript 增强用户体验。

使用内置表单验证

表单控件最重要的功能之一是无需依赖 JavaScript 即可验证大多数用户数据。这是通过在表单元素上使用验证属性来完成的。我们在课程早期已经看到其中许多,但回顾一下:

  • required:指定表单字段是否需要在提交表单之前填写。
  • minlengthmaxlength:指定文本数据(字符串)的最小和最大长度。
  • minmaxstep:指定数字输入类型的最小值和最大值,以及从最小值开始的增量或步长。
  • type:指定数据是数字、电子邮件地址还是其他特定预设类型。
  • pattern:指定一个正则表达式,该表达式定义了输入数据需要遵循的模式。

如果表单字段中输入的数据遵循应用于该字段的所有属性指定的所有规则,则认为它是有效的。否则,认为它是无效的。

当元素有效时,以下情况属实:

  • 该元素匹配 :valid CSS 伪类,该伪类允许您对有效元素应用特定样式。如果用户与控件进行了交互,该控件也将匹配 :user-valid,并且根据输入类型和属性,可能匹配其他 UI 伪类,例如 :in-range
  • 如果用户尝试发送数据,只要没有其他阻止表单提交的因素(例如 JavaScript),浏览器就会提交表单。

当元素无效时,以下情况属实:

  • 该元素匹配 :invalid CSS 伪类。如果用户与控件进行了交互,它也匹配 :user-invalid CSS 伪类。其他 UI 伪类也可能匹配,例如 :out-of-range,具体取决于错误。这些允许您对无效元素应用特定样式。
  • 如果用户尝试发送数据,浏览器将阻止表单提交并显示错误消息。错误消息将因错误类型而异。约束验证 API 在下面描述。

内置表单验证示例

在本节中,我们将测试上面讨论的一些属性。

基本入门文件

让我们从一个基本示例开始:一个允许您选择香蕉或樱桃的输入。此示例涉及一个文本 <input>,带有相关的 <label> 和一个提交 <button>

html
<!doctype html>
<html lang="en-US">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>Favorite fruit start</title>
    <style>
      input:invalid {
        border: 2px dashed red;
      }

      input:valid {
        border: 2px solid black;
      }
    </style>
  </head>

  <body>
    <form>
      <label for="choose">Would you prefer a banana or a cherry?</label>
      <input id="choose" name="i_like" />
      <button>Submit</button>
    </form>
  </body>
</html>

首先,将上一个 HTML 列表复制到新的 index.html 文件中。将其保存在硬盘上的新目录中。

required 属性

一个常见的 HTML 验证功能是 required 属性。将此属性添加到输入以使元素成为必填项。设置此属性后,元素匹配 :required UI 伪类,如果输入为空,表单将不会提交,并在提交时显示错误消息。当为空时,输入也将被视为无效,匹配 :invalid UI 伪类。

如果同一名称组中的任何单选按钮具有 required 属性,则该组中的一个单选按钮必须被选中才能使该组有效;被选中的单选按钮不必是设置了该属性的按钮。

注意:仅要求用户输入您需要的数据:例如,真的有必要知道某人的性别或称谓吗?

如下所示,将 required 属性添加到您的输入中。

html
<form>
  <label for="choose">Would you prefer a banana or cherry? *</label>
  <input id="choose" name="i-like" required />
  <button>Submit</button>
</form>

注意:一种常见的做法是在必填表单控件的标签后放置星号(或其他标记),以便它们在可视用户面前突出。向用户指出何时需要填写表单字段不仅是良好的用户体验,而且是 WCAG 无障碍指南所要求的。

我们包含基于元素是否为必需、有效和无效而应用的 CSS 样式:

css
input:invalid {
  border: 2px dashed red;
}

input:invalid:required {
  background-image: linear-gradient(to right, pink, lightgreen);
}

input:valid {
  border: 2px solid black;
}

此 CSS 会导致输入在无效时具有红色虚线边框,在有效时具有更细微的黑色实线边框。当输入为必需项无效时,我们还添加了背景渐变。在下面的示例中尝试新的行为:

尝试在没有值的情况下提交表单。请注意无效输入如何获得焦点,并出现默认错误消息(“请填写此字段”)。表单也被阻止发送(尽管请注意,即使输入了值,我们也会阻止表单提交,以避免由于 MDN 处理嵌入式表单的方式而导致的错误)。

根据正则表达式验证

另一个有用的验证功能是 pattern 属性,它期望一个 正则表达式 作为其值。正则表达式 (regexp) 是一种可用于匹配文本字符串中字符组合的模式,因此正则表达式非常适合表单验证,并在 JavaScript 中有各种其他用途。

正则表达式非常复杂,我们不打算在本文中详尽地教授它们。下面是一些示例,让您对它们的工作原理有一个基本的了解。

  • a — 匹配一个字符,该字符是 a(不是 b,不是 aa 等)。
  • abc — 匹配 a,后跟 b,后跟 c
  • ab?c — 匹配 a,可选后跟单个 b,后跟 c。(acabc
  • ab*c — 匹配 a,可选后跟任意数量的 b,后跟 c。(ac, abc, abbbbbc 等)。
  • a|b — 匹配一个字符,该字符是 ab
  • abc|xyz — 精确匹配 abc 或精确匹配 xyz(但不匹配 abcxyzay 等)。

我们这里没有涵盖更多可能性。有关完整列表和许多示例,请参阅我们的正则表达式文档。

我们来实现一个示例。更新您的 HTML 以添加一个 pattern 属性,如下所示:

html
<form>
  <label for="choose">Would you prefer a banana or a cherry? *</label>
  <input id="choose" name="i-like" required pattern="[Bb]anana|[Cc]herry" />
  <button>Submit</button>
</form>

这给我们带来了以下更新——试试看:

您也可以按 Play 按钮在 MDN Playground 中打开示例并编辑源代码。

在此示例中,<input> 元素接受四种可能的值之一:字符串“banana”、“Banana”、“cherry”或“Cherry”。正则表达式区分大小写,但我们通过在方括号内嵌套一个额外的“Aa”模式使其支持大写和小写版本。

此时,尝试更改 pattern 属性中的值,使其等于您之前看到的一些示例,并查看它如何影响您可以输入以使输入值有效的值。尝试编写一些您自己的,看看效果如何。尽可能使其与水果相关,以便您的示例有意义!

如果 <input> 的非空值与正则表达式的模式不匹配,则 input 将匹配 :invalid 伪类。如果为空且元素不是必需的,则不视为无效。

某些 <input> 元素类型不需要 pattern 属性即可根据正则表达式进行验证。例如,指定 email 类型会根据格式正确的电子邮件地址模式或匹配逗号分隔的电子邮件地址列表的模式验证输入值,如果它具有 multiple 属性。

注意: <textarea> 元素不支持 pattern 属性。

限制条目的长度

您可以使用 minlengthmaxlength 属性来限制所有由 <input><textarea> 创建的文本字段的字符长度。如果字段具有值,并且该值的字符数少于 minlength 值或多于 maxlength 值,则该字段无效。

浏览器通常不允许用户在文本字段中输入比预期更长的值。比仅仅使用 maxlength 更好的用户体验是还以可访问的方式提供字符计数反馈,并让用户将内容编辑到适当的大小。一个例子是社交媒体发帖时的字符限制。JavaScript,包括使用 maxlength 的解决方案,可以用来提供这个。

注意:如果值是通过编程方式设置的,则不会报告长度约束。它们仅针对用户提供的输入报告。

限制条目的值

对于数字字段,包括 <input type="number"> 和各种日期输入类型,可以使用 minmax 属性来提供有效值的范围。如果字段包含超出此范围的值,则它将无效。

我们来看另一个例子。创建基本入门文件文件的副本(将其保存在与 index2.html 相同的目录中)。

现在删除 <body> 元素的内容,并将其替换为以下内容:

html
<form>
  <div>
    <label for="choose">Would you prefer a banana or a cherry? *</label>
    <input
      type="text"
      id="choose"
      name="i-like"
      required
      minlength="6"
      maxlength="6" />
  </div>
  <div>
    <label for="number">How many would you like?</label>
    <input type="number" id="number" name="amount" value="1" min="1" max="10" />
  </div>
  <div>
    <button>Submit</button>
  </div>
</form>
  • 在这里您会看到我们已经给 text 字段设置了 minlengthmaxlength 为六,这与香蕉和樱桃的长度相同。
  • 我们还为 number 字段设置了 min 为 1,max 为 10。超出此范围输入的数字将显示为无效;用户将无法使用增量/减量箭头将值移到此范围之外。如果用户手动输入超出此范围的数字,则数据无效。该数字不是必需的,因此删除该值将导致一个有效值。

这是实时运行的示例:

您也可以按 Play 按钮在 MDN Playground 中打开示例并编辑源代码。

数字输入类型,例如 numberrangedate,也可以使用 step 属性。此属性指定当使用输入控件(例如上下数字按钮或滑动范围滑块)时,值将增加或减少的增量。我们的示例中省略了 step 属性,因此值默认为 1。这意味着浮点数(例如 3.2)也将显示为无效。

完整示例

这是一个完整的示例,展示了 HTML 内置验证功能的使用。首先是一些 HTML:

html
<form>
  <p>Please complete all required (*) fields.</p>
  <fieldset>
    <legend>Do you have a driver's license? *</legend>
    <input type="radio" required name="driver" id="r1" value="yes" />
    <label for="r1">Yes</label>
    <input type="radio" required name="driver" id="r2" value="no" />
    <label for="r2">No</label>
  </fieldset>
  <p>
    <label for="n1">How old are you?</label>
    <input type="number" min="12" max="120" step="1" id="n1" name="age" />
  </p>
  <p>
    <label for="t1">What's your favorite fruit? *</label>
    <input
      type="text"
      id="t1"
      name="fruit"
      list="l1"
      required
      pattern="[Bb]anana|[Cc]herry|[Aa]pple|[Ss]trawberry|[Ll]emon|[Oo]range" />
    <datalist id="l1">
      <option>Banana</option>
      <option>Cherry</option>
      <option>Apple</option>
      <option>Strawberry</option>
      <option>Lemon</option>
      <option>Orange</option>
    </datalist>
  </p>
  <p>
    <label for="t2">What's your email address?</label>
    <input type="email" id="t2" name="email" />
  </p>
  <p>
    <label for="t3">Leave a short message</label>
    <textarea id="t3" name="msg" maxlength="140" rows="5"></textarea>
  </p>
  <p>
    <button>Submit</button>
  </p>
</form>

现在是一些用于设置 HTML 样式的 CSS:

css
form {
  font: 1em sans-serif;
  max-width: 320px;
}

p > label {
  display: block;
}

input[type="text"],
input[type="email"],
input[type="number"],
textarea,
fieldset {
  width: 100%;
  border: 1px solid #333333;
  box-sizing: border-box;
}

input:invalid {
  box-shadow: 0 0 5px 1px red;
}

input:focus:invalid {
  box-shadow: none;
}

渲染结果如下:

您也可以按 Play 按钮在 MDN Playground 中打开示例并编辑源代码。

有关可用于限制输入值及其支持的输入类型的属性的完整列表,请参阅验证相关属性

使用 JavaScript 验证表单

如果您想更改原生错误消息的文本,则需要 JavaScript。在本节中,我们将探讨实现此目的的不同方法。

约束验证 API

约束验证 API 由以下表单元素 DOM 接口上可用的一组方法和属性组成:

约束验证 API 在上述元素上提供了以下属性。

  • validationMessage:返回一个本地化消息,描述控件未满足的验证约束(如果有)。如果控件不是约束验证的候选对象(willValidatefalse)或元素的值满足其约束(有效),则此属性将返回一个空字符串。

  • validity:返回一个 ValidityState 对象,其中包含描述元素有效性状态的几个属性。您可以在 ValidityState 参考页面中找到所有可用属性的完整详细信息;下面列出了一些更常见的属性:

    • patternMismatch:如果值与指定的 pattern 不匹配,则返回 true,如果匹配则返回 false。如果为 true,则元素匹配 :invalid CSS 伪类。
    • tooLong:如果值长于 maxlength 属性指定的最大长度,则返回 true,如果短于或等于最大长度,则返回 false。如果为 true,则元素匹配 :invalid CSS 伪类。
    • tooShort:如果值短于 minlength 属性指定的最小长度,则返回 true,如果大于或等于最小长度,则返回 false。如果为 true,则元素匹配 :invalid CSS 伪类。
    • rangeOverflow:如果值大于 max 属性指定的最大值,则返回 true,如果小于或等于最大值,则返回 false。如果为 true,则元素匹配 :invalid:out-of-range CSS 伪类。
    • rangeUnderflow:如果值小于 min 属性指定的最小值,则返回 true,如果大于或等于最小值,则返回 false。如果为 true,则元素匹配 :invalid:out-of-range CSS 伪类。
    • typeMismatch:如果值不符合所需语法(当 typeemailurl 时),则返回 true,如果语法正确则返回 false。如果为 true,则元素匹配 :invalid CSS 伪类。
    • valid:如果元素满足所有验证约束,因此被认为是有效的,则返回 true,否则返回 false。如果为 true,则元素匹配 :valid CSS 伪类;否则匹配 :invalid CSS 伪类。
    • valueMissing:如果元素具有 required 属性但没有值,则返回 true,否则返回 false。如果为 true,则元素匹配 :invalid CSS 伪类。
  • willValidate:如果元素在提交表单时将被验证,则返回 true;否则返回 false

约束验证 API 还允许在上述元素和 form 元素上使用以下方法。

  • checkValidity():如果元素的值没有有效性问题,则返回 true;否则返回 false。如果元素无效,此方法还会触发元素上的 invalid 事件
  • reportValidity():使用事件报告无效字段。此方法与 onSubmit 事件处理程序中的 preventDefault() 结合使用时很有用。
  • setCustomValidity(message):向元素添加自定义错误消息;如果您设置了自定义错误消息,则该元素被视为无效,并显示指定的错误。这允许您使用 JavaScript 代码建立一个不同于标准 HTML 验证约束提供的验证失败。报告问题时会向用户显示该消息。

实现自定义错误消息

正如您在前面的 HTML 验证约束示例中看到的,每次用户尝试提交无效表单时,浏览器都会显示一条错误消息。此消息的显示方式取决于浏览器。

这些自动化消息有两个缺点:

  • 没有标准方法可以使用 CSS 更改它们的外观和感觉。
  • 它们依赖于浏览器区域设置,这意味着您可能有一个以一种语言显示的页面,但错误消息却以另一种语言显示,如以下 Firefox 截图所示。

Example of an error message with Firefox in French on an English page

自定义这些错误消息是约束验证 API 最常见的用例之一。让我们看一个如何实现此功能的示例。

我们将从一些 HTML 开始。如果您愿意,可以将其放在基本入门文件的另一个副本中:

html
<form>
  <label for="mail">
    I would like you to provide me with an email address:
  </label>
  <input type="email" id="mail" name="mail" />
  <button>Submit</button>
</form>

将以下 JavaScript 添加到页面:

js
const email = document.getElementById("mail");

email.addEventListener("input", (event) => {
  if (email.validity.typeMismatch) {
    email.setCustomValidity("I am expecting an email address!");
  } else {
    email.setCustomValidity("");
  }
});

在这里,我们存储对电子邮件输入的引用,然后向其添加一个事件监听器,该监听器在输入中的值每次更改时运行包含的代码。

在包含的代码中,我们检查电子邮件输入的 validity.typeMismatch 属性是否返回 true,这意味着包含的值与格式正确的电子邮件地址模式不匹配。如果是这样,我们调用 setCustomValidity() 方法并带有一个自定义消息。这会将输入渲染为无效,因此当您尝试提交表单时,提交失败并显示自定义错误消息。

如果 validity.typeMismatch 属性返回 false,我们调用 setCustomValidity() 方法并带有一个空字符串。这会将输入渲染为有效,因此表单将提交。在验证期间,如果任何表单控件具有非空字符串的 customError,则表单提交将被阻止。

您可以在下面尝试(按 Play 按钮在 MDN Playground 中运行示例并编辑源代码):

扩展内置表单验证

前面的示例演示了如何为特定类型的错误 (validity.typeMismatch) 添加自定义消息。也可以使用所有内置表单验证,然后使用 setCustomValidity() 进行添加。

这里我们演示了如何扩展内置的 <input type="email"> 验证,使其只接受带有 @example.com 域的地址。我们从下面的 HTML <form> 开始。

html
<form>
  <label for="mail">Email address (@example.com only):</label>
  <input type="email" id="mail" />
  <button>Submit</button>
</form>

验证代码如下所示。在任何新输入的情况下,代码首先通过调用 setCustomValidity("") 重置自定义验证消息。然后它使用 email.validity.valid 检查输入的地址是否无效,如果是,则从事件处理程序返回。这确保了在输入文本不是有效电子邮件地址时运行所有正常的内置验证检查。

一旦电子邮件地址有效,代码会添加一个自定义约束,如果地址不以 @example.com 结尾,则调用 setCustomValidity() 并显示错误消息。

js
const email = document.getElementById("mail");

email.addEventListener("input", (event) => {
  // Validate with the built-in constraints
  email.setCustomValidity("");
  if (!email.validity.valid) {
    return;
  }

  // Extend with a custom constraints
  if (!email.value.endsWith("@example.com")) {
    email.setCustomValidity("Please enter an email address of @example.com");
  }
});

尝试提交一个无效的电子邮件地址、一个有效的但不是以 @example.com 结尾的电子邮件地址,以及一个以 @example.com 结尾的电子邮件地址。

一个更详细的例子

现在我们已经看到了一个非常基本的例子,让我们看看如何使用这个 API 来构建一些稍微复杂一点的自定义验证。

首先是 HTML。再次强调,您可以随意与我们一起构建它:

html
<form novalidate>
  <p>
    <label for="mail">
      <span>Please enter an email address *:</span>
      <input type="email" id="mail" name="mail" required minlength="8" />
      <span class="error" aria-live="polite"></span>
    </label>
  </p>
  <button>Submit</button>
</form>

此表单使用 novalidate 属性关闭浏览器的自动验证。在表单上设置 novalidate 属性会阻止表单显示其自己的错误消息气泡,并允许我们以我们自己选择的某种方式在 DOM 中显示自定义错误消息。但是,这不会禁用对约束验证 API 的支持,也不会禁用 CSS 伪类(例如 :valid 等)的应用。这意味着即使浏览器不会在发送数据之前自动检查表单的有效性,您仍然可以自己进行检查并相应地设置表单的样式。

我们要验证的输入是 <input type="email">,它是 required 的,并且 minlength 为 8 个字符。让我们使用我们自己的代码检查这些,并为每个显示一个自定义错误消息。

我们的目标是在 <span> 元素内显示错误消息。该 <span> 上设置了 aria-live 属性,以确保我们的自定义错误消息会呈现给所有人,包括屏幕阅读器用户也能读出。

现在,让我们来看一些基本的 CSS,以稍微改善表单的外观,并在输入数据无效时提供一些视觉反馈:

css
body {
  font: 1em sans-serif;
  width: 200px;
  padding: 0;
  margin: 0 auto;
}

p * {
  display: block;
}

input[type="email"] {
  appearance: none;

  width: 100%;
  border: 1px solid #333333;
  margin: 0;

  font-family: inherit;
  font-size: 90%;

  box-sizing: border-box;
}

/* invalid fields */
input:invalid {
  border-color: #990000;
  background-color: #ffdddd;
}

input:focus:invalid {
  outline: none;
}

/* error message styles */
.error {
  width: 100%;
  padding: 0;

  font-size: 80%;
  color: white;
  background-color: #990000;
  border-radius: 0 0 5px 5px;

  box-sizing: border-box;
}

.error.active {
  padding: 0.3em;
}

现在让我们看看实现自定义错误验证的 JavaScript。选择 DOM 节点有很多方法;这里我们获取表单本身和电子邮件输入框,以及我们将放置错误消息的 span 元素。

使用事件处理程序,我们在用户每次输入内容时检查表单字段是否有效。如果存在错误,我们会显示它。如果没有错误,我们会删除任何错误消息。

js
const form = document.querySelector("form");
const email = document.getElementById("mail");
const emailError = document.querySelector("#mail + span.error");

email.addEventListener("input", (event) => {
  if (email.validity.valid) {
    emailError.textContent = ""; // Remove the message content
    emailError.className = "error"; // Removes the `active` class
  } else {
    // If there is still an error, show the correct error
    showError();
  }
});

form.addEventListener("submit", (event) => {
  // if the email field is invalid
  if (!email.validity.valid) {
    // display an appropriate error message
    showError();
    // prevent form submission
    event.preventDefault();
  }
});

function showError() {
  if (email.validity.valueMissing) {
    // If empty
    emailError.textContent = "You need to enter an email address.";
  } else if (email.validity.typeMismatch) {
    // If it's not an email address,
    emailError.textContent = "Entered value needs to be an email address.";
  } else if (email.validity.tooShort) {
    // If the value is too short,
    emailError.textContent = `Email should be at least ${email.minLength} characters; you entered ${email.value.length}.`;
  }
  // Add the `active` class
  emailError.className = "error active";
}

每次我们更改输入的值时,我们都会检查它是否包含有效数据。如果包含,则删除任何正在显示的错误消息。如果数据无效,我们运行 showError() 显示相应的错误。

每次我们尝试提交表单时,我们都会再次检查数据是否有效。如果是,我们允许表单提交。如果不是,我们运行 showError() 显示适当的错误,并使用 preventDefault() 阻止表单提交。

showError() 函数使用输入 validity 对象的各种属性来确定错误是什么,然后相应地显示错误消息。

这是实时结果(按下 Play 按钮在 MDN Playground 中运行示例并编辑源代码):

约束验证 API 为您提供了处理表单验证的强大工具,让您可以超越仅使用 HTML 和 CSS 所能实现的对用户界面的巨大控制。

无需内置 API 即可验证表单

在某些情况下,例如自定义控件,您将无法或不愿使用约束验证 API。您仍然可以使用 JavaScript 验证表单,但您只需编写自己的验证逻辑。

要验证表单,请问自己几个问题:

我应该执行哪种验证?

您需要确定如何验证数据:字符串操作、类型转换、正则表达式等。这取决于您。

如果表单未通过验证,我该怎么办?

这显然是一个 UI 问题。您必须决定表单将如何表现。表单是否仍然发送数据?您是否应该突出显示错误的字段?您是否应该显示错误消息?

我如何帮助用户更正无效数据?

为了减少用户的挫败感,提供尽可能多的有用信息来指导他们纠正输入非常重要。您应该提供预先建议,让他们知道预期内容,以及清晰的错误消息。如果您想深入研究表单验证 UI 要求,这里有一些有用的文章值得阅读:

一个不使用约束验证 API 的例子

为了说明这一点,下面是上一个示例的简化版本,没有约束验证 API。

HTML 几乎相同;我们只是删除了 HTML 验证功能。

html
<form>
  <p>
    <label for="mail">
      <span>Please enter an email address:</span>
    </label>
    <input type="text" id="mail" name="mail" />
    <span id="error" aria-live="polite"></span>
  </p>
  <button>Submit</button>
</form>

同样,CSS 不需要做太大更改;我们只是将 :invalid CSS 伪类转换为真正的类,并避免使用属性选择器。

css
body {
  font: 1em sans-serif;
  width: 200px;
  padding: 0;
  margin: 0 auto;
}

form {
  max-width: 200px;
}

p * {
  display: block;
}

input {
  appearance: none;
  width: 100%;
  border: 1px solid #333333;
  margin: 0;

  font-family: inherit;
  font-size: 90%;

  box-sizing: border-box;
}

/* invalid fields */
input.invalid {
  border: 2px solid #990000;
  background-color: #ffdddd;
}

input:focus.invalid {
  outline: none;
  /* make sure keyboard-only users see a change when focusing */
  border-style: dashed;
}

/* error messages */
#error {
  width: 100%;
  font-size: 80%;
  color: white;
  background-color: #990000;
  border-radius: 0 0 5px 5px;
  box-sizing: border-box;
}

.active {
  padding: 0.3rem;
}

最大的变化在于 JavaScript 代码,它需要做更多繁重的工作。

js
const form = document.querySelector("form");
const email = document.getElementById("mail");
const error = document.getElementById("error");

// Regular expression for email validation as per HTML specification
const emailRegExp = /^[\w.!#$%&'*+/=?^`{|}~-]+@[a-z\d-]+(?:\.[a-z\d-]+)*$/i;

// Check if the email is valid
const isValidEmail = () => {
  const validity = email.value.length !== 0 && emailRegExp.test(email.value);
  return validity;
};

// Update email input class based on validity
const setEmailClass = (isValid) => {
  email.className = isValid ? "valid" : "invalid";
};

// Update error message and visibility
const updateError = (isValid) => {
  if (isValid) {
    error.textContent = "";
    error.removeAttribute("class");
  } else {
    error.textContent = "I expect an email, darling!";
    error.setAttribute("class", "active");
  }
};

// Handle input event to update email validity
const handleInput = () => {
  const validity = isValidEmail();
  setEmailClass(validity);
  updateError(validity);
};

// Handle form submission to show error if email is invalid
const handleSubmit = (event) => {
  event.preventDefault();

  const validity = isValidEmail();
  setEmailClass(validity);
  updateError(validity);
};

// Now we can rebuild our validation constraint
// Because we do not rely on CSS pseudo-class, we have to
// explicitly set the valid/invalid class on our email field
const validity = isValidEmail();
setEmailClass(validity);
// This defines what happens when the user types in the field
email.addEventListener("input", handleInput);
// This defines what happens when the user tries to submit the data
form.addEventListener("submit", handleSubmit);

结果如下所示

正如您所看到的,自行构建一个验证系统并不难。困难的部分是如何使其足够通用,以便在跨平台和您可能创建的任何表单上使用。有许多可用的库来执行表单验证,例如 Validate.js

总结

如果您想自定义样式和错误消息,客户端表单验证有时需要 JavaScript,但它总是需要您仔细考虑用户。请始终记住帮助用户纠正他们提供的数据。为此,请务必:

  • 显示明确的错误消息。
  • 对输入格式持宽容态度。
  • 准确指出错误发生的位置,尤其是在大型表单上。

一旦您检查确认表单已正确填写,就可以提交表单了。接下来我们将介绍发送表单数据