客戶端表單驗證

在將使用者輸入的表單資料提交到伺服器之前,確保所有必需的表單控制元件都已填寫並格式正確非常重要。這種客戶端表單驗證有助於確保輸入的資料符合各種表單控制元件中設定的要求。

本文將引導您瞭解客戶端表單驗證的基本概念和示例。

預備知識 計算機基礎知識,對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,但它總是需要您仔細考慮使用者。請始終記住幫助使用者糾正他們提供的資料。為此,請務必:

  • 顯示明確的錯誤訊息。
  • 對輸入格式持寬容態度。
  • 準確指出錯誤發生的位置,尤其是在大型表單上。

一旦您檢查確認表單已正確填寫,就可以提交表單了。接下來我們將介紹傳送表單資料