この章では、フォーム操作とバリデーションを学びます。
Web制作案件では、お問い合わせフォーム、資料請求フォーム、採用応募フォーム、予約フォームなど、フォームまわりの実装がよく出てきます。
フォームは見た目だけでなく、入力しやすさ、エラーの伝え方、二重送信防止、アクセシビリティにも関わります。JavaScriptで何でも解決しようとするのではなく、HTMLの標準機能と組み合わせて考えることが大切です。
フォーム部品の基本
フォームでは、HTMLの部品を正しく使うことがとても大切です。
JavaScriptを書く前に、まずHTMLの意味を整えます。
<form class="contact-form js-contact-form" novalidate>
<div class="form-field">
<label for="name">お名前</label>
<input id="name" class="js-name-input" type="text" name="name" required />
</div>
<div class="form-field">
<label for="email">メールアドレス</label>
<input id="email" class="js-email-input" type="email" name="email" required />
</div>
<button class="js-submit-button" type="submit">送信する</button>
</form>labelと入力欄を対応させると、ラベルをクリックした時に入力欄へフォーカスできます。スクリーンリーダーにも項目名が伝わりやすくなります。
valueで入力値を取得する
テキスト入力、メールアドレス、電話番号、テキストエリアなどの値は、.valueで取得できます。
<input class="js-name-input" type="text" />const nameInput = document.querySelector(".js-name-input");
if (nameInput) {
console.log(nameInput.value);
}入力されるたびに値を取得したい場合は、inputイベントを使います。
const nameInput = document.querySelector(".js-name-input");
if (nameInput) {
nameInput.addEventListener("input", () => {
console.log(nameInput.value);
});
}文字数カウント、リアルタイムエラー表示、送信ボタンの有効化などで使います。
checkedでチェック状態を取得する
チェックボックスやラジオボタンでは、.checkedでチェック状態を取得します。
<label>
<input class="js-agree-checkbox" type="checkbox" />
利用規約に同意する
</label>const checkbox = document.querySelector(".js-agree-checkbox");
if (checkbox) {
checkbox.addEventListener("change", () => {
console.log(checkbox.checked);
});
}checkedは真偽値です。チェックされていればtrue、されていなければfalseになります。
送信ボタンの有効化でよく使います。
const checkbox = document.querySelector(".js-agree-checkbox");
const submitButton = document.querySelector(".js-submit-button");
if (checkbox && submitButton) {
checkbox.addEventListener("change", () => {
submitButton.disabled = !checkbox.checked;
});
}filesでファイルを取得する
ファイル入力では、.filesで選択されたファイルを取得します。
<input class="js-file-input" type="file" />const fileInput = document.querySelector(".js-file-input");
if (fileInput) {
fileInput.addEventListener("change", () => {
const file = fileInput.files[0];
if (file) {
console.log(file.name);
}
});
}ファイル名の表示、ファイルサイズの確認、画像プレビューなどで使います。
FormData
FormDataを使うと、フォーム内の値をまとめて取得できます。
<form class="js-contact-form">
<input type="text" name="name" />
<input type="email" name="email" />
<button type="submit">送信する</button>
</form>const form = document.querySelector(".js-contact-form");
if (form) {
form.addEventListener("submit", (event) => {
event.preventDefault();
const formData = new FormData(form);
console.log(formData.get("name"));
console.log(formData.get("email"));
});
}この教材ではAPI送信を本格的には扱いませんが、入力値をまとめて扱う考え方として知っておくと便利です。
HTML標準のバリデーション
HTMLには、入力チェックのための属性があります。
| 属性 | 役割 |
|---|---|
required | 必須項目にする |
type="email" | メール形式をチェックする |
minlength | 最小文字数を指定する |
maxlength | 最大文字数を指定する |
pattern | 正規表現で形式を指定する |
<input type="email" name="email" required />JavaScriptで全部を自作する前に、HTML標準でできることを使いましょう。
checkValidity
checkValidityは、フォームや入力欄がHTML標準の条件を満たしているか確認するメソッドです。
const emailInput = document.querySelector(".js-email-input");
if (emailInput) {
const isValid = emailInput.checkValidity();
console.log(isValid);
}フォーム全体に対しても使えます。
const form = document.querySelector(".js-contact-form");
if (form) {
form.addEventListener("submit", (event) => {
if (!form.checkValidity()) {
event.preventDefault();
console.log("入力内容に問題があります。");
}
});
}checkValidityは、問題なければtrue、問題があればfalseを返します。
validity
validityを見ると、どの種類のエラーかを確認できます。
const emailInput = document.querySelector(".js-email-input");
if (emailInput) {
console.log(emailInput.validity.valueMissing);
console.log(emailInput.validity.typeMismatch);
}よく使うプロパティは次の通りです。
| プロパティ | 意味 |
|---|---|
valueMissing | 必須なのに空 |
typeMismatch | type="email"などの形式に合わない |
tooShort | minlengthより短い |
tooLong | maxlengthより長い |
patternMismatch | patternに合わない |
valid | すべての条件を満たしている |
エラーメッセージを出し分けたい時に使います。
validationMessage
validationMessageには、ブラウザ標準のエラーメッセージが入ります。
const emailInput = document.querySelector(".js-email-input");
if (emailInput && !emailInput.checkValidity()) {
console.log(emailInput.validationMessage);
}ただし、ブラウザや言語設定によって文言が変わります。
案件のデザインや文体に合わせたい場合は、自分でメッセージを作ることが多いです。
function getEmailErrorMessage(input) {
if (input.validity.valueMissing) {
return "メールアドレスを入力してください。";
}
if (input.validity.typeMismatch) {
return "メールアドレスの形式で入力してください。";
}
return "";
}setCustomValidity
setCustomValidityを使うと、独自のエラー状態を設定できます。
const passwordInput = document.querySelector(".js-password-input");
const confirmInput = document.querySelector(".js-confirm-input");
if (passwordInput && confirmInput) {
confirmInput.addEventListener("input", () => {
if (passwordInput.value !== confirmInput.value) {
confirmInput.setCustomValidity("パスワードが一致しません。");
} else {
confirmInput.setCustomValidity("");
}
});
}空文字を渡すと、カスタムエラーを解除できます。
エラー表示の基本
実務では、ブラウザ標準の吹き出しではなく、デザインに合わせたエラー表示を作ることが多いです。
<div class="form-field">
<label for="email">メールアドレス</label>
<input
id="email"
class="js-email-input"
type="email"
name="email"
required
aria-describedby="email-error"
/>
<p class="form-error js-email-error" id="email-error" hidden></p>
</div>const emailInput = document.querySelector(".js-email-input");
const emailError = document.querySelector(".js-email-error");
function showEmailError(message) {
emailInput.setAttribute("aria-invalid", "true");
emailError.hidden = false;
emailError.textContent = message;
}
function clearEmailError() {
emailInput.setAttribute("aria-invalid", "false");
emailError.hidden = true;
emailError.textContent = "";
}aria-invalidは、入力欄にエラーがあるかを伝える属性です。
aria-describedbyでエラーメッセージのIDを指定しておくと、入力欄とエラー文の関係を伝えやすくなります。
submit時バリデーション
送信ボタンを押した時に入力チェックをする基本形です。
const form = document.querySelector(".js-contact-form");
const emailInput = document.querySelector(".js-email-input");
const emailError = document.querySelector(".js-email-error");
if (form && emailInput && emailError) {
form.addEventListener("submit", (event) => {
if (emailInput.checkValidity()) {
return;
}
event.preventDefault();
if (emailInput.validity.valueMissing) {
showEmailError("メールアドレスを入力してください。");
} else if (emailInput.validity.typeMismatch) {
showEmailError("メールアドレスの形式で入力してください。");
}
});
}送信時バリデーションは、ユーザーが「送信する」を押したタイミングでエラーを表示します。
最初からエラーを出しすぎないので、比較的やさしい操作感になります。
リアルタイムバリデーション
入力中にエラーを消したり、条件を満たしたらボタンを有効化したりする場合は、inputイベントを使います。
emailInput.addEventListener("input", () => {
if (emailInput.checkValidity()) {
clearEmailError();
}
});ただし、入力中に毎回強いエラーを出すと、ユーザーにとってうるさく感じることがあります。
エラー位置へスクロールする
フォームが長い場合、送信時に最初のエラー位置へスクロールすると親切です。
const firstInvalidInput = form.querySelector('[aria-invalid="true"]');
if (firstInvalidInput) {
firstInvalidInput.scrollIntoView({
behavior: "smooth",
block: "center",
});
firstInvalidInput.focus();
}固定ヘッダーがある場合は、CSSでscroll-margin-topを指定しておくと調整しやすいです。
.form-field input {
scroll-margin-top: 100px;
}確認画面風UI
静的なWeb制作案件では、本格的な確認画面ではなく、入力内容を同じページ内に表示する「確認画面風UI」を求められることがあります。
<input class="js-name-input" type="text" name="name" />
<p>確認: <span class="js-name-preview"></span></p>const nameInput = document.querySelector(".js-name-input");
const namePreview = document.querySelector(".js-name-preview");
if (nameInput && namePreview) {
nameInput.addEventListener("input", () => {
namePreview.textContent = nameInput.value;
});
}この時も、ユーザー入力を表示するだけならtextContentを使います。
innerHTMLにそのまま入れないようにしましょう。
二重送信防止
フォーム送信時に、送信ボタンを何度も押されると、二重送信につながることがあります。
フロント側では、送信時にボタンを無効化する基本形をよく使います。
const form = document.querySelector(".js-contact-form");
const submitButton = document.querySelector(".js-submit-button");
if (form && submitButton) {
form.addEventListener("submit", (event) => {
if (!form.checkValidity()) {
return;
}
submitButton.disabled = true;
submitButton.textContent = "送信中...";
});
}小さな実装例: お問い合わせフォーム
ここまでの内容を使って、名前とメールアドレスをチェックする小さな例を作ります。
<form class="contact-form js-contact-form" novalidate>
<div class="form-field">
<label for="name">お名前</label>
<input id="name" class="js-name-input" type="text" name="name" required aria-describedby="name-error" />
<p id="name-error" class="form-error js-name-error" hidden></p>
</div>
<div class="form-field">
<label for="email">メールアドレス</label>
<input id="email" class="js-email-input" type="email" name="email" required aria-describedby="email-error" />
<p id="email-error" class="form-error js-email-error" hidden></p>
</div>
<button class="js-submit-button" type="submit">送信する</button>
</form>const form = document.querySelector(".js-contact-form");
const nameInput = document.querySelector(".js-name-input");
const emailInput = document.querySelector(".js-email-input");
const nameError = document.querySelector(".js-name-error");
const emailError = document.querySelector(".js-email-error");
const submitButton = document.querySelector(".js-submit-button");
function setError(input, errorElement, message) {
input.setAttribute("aria-invalid", "true");
errorElement.hidden = false;
errorElement.textContent = message;
}
function clearError(input, errorElement) {
input.setAttribute("aria-invalid", "false");
errorElement.hidden = true;
errorElement.textContent = "";
}
function validateName() {
if (nameInput.value === "") {
setError(nameInput, nameError, "お名前を入力してください。");
return false;
}
clearError(nameInput, nameError);
return true;
}
function validateEmail() {
if (emailInput.validity.valueMissing) {
setError(emailInput, emailError, "メールアドレスを入力してください。");
return false;
}
if (emailInput.validity.typeMismatch) {
setError(emailInput, emailError, "メールアドレスの形式で入力してください。");
return false;
}
clearError(emailInput, emailError);
return true;
}
if (form && nameInput && emailInput && nameError && emailError && submitButton) {
form.addEventListener("submit", (event) => {
const isNameValid = validateName();
const isEmailValid = validateEmail();
if (!isNameValid || !isEmailValid) {
event.preventDefault();
return;
}
submitButton.disabled = true;
submitButton.textContent = "送信中...";
});
nameInput.addEventListener("input", validateName);
emailInput.addEventListener("input", validateEmail);
}この例では、次の処理を入れています。
- 入力値を取得する
- 必須チェックをする
- メール形式をチェックする
- エラー表示を更新する
aria-invalidを更新する- 送信中にボタンを無効化する
よくある失敗
フォーム実装でよくある失敗を整理しておきます。
labelと入力欄が対応していない- エラー文を出しているが、
aria-invalidやaria-describedbyが更新されていない preventDefaultを常に実行してしまい、正常時も送信できないsetCustomValidityの解除を忘れる- 入力中にエラーを出しすぎて使いにくい
- フロント側チェックだけで安全だと思ってしまう
- 二重送信防止でボタンを無効化したまま、エラー時に戻していない
- ユーザー入力を
innerHTMLに入れてしまう
フォームはユーザーが直接触る部分です。
エラーがあることを伝えるだけでなく、どう直せばよいかがわかる文言にしましょう。
この章のまとめ
この章では、フォーム操作とバリデーションの基本を学びました。
- 入力値は
.value、チェック状態は.checked、ファイルは.filesで取得する - HTML標準の
required、type="email"、minlengthなどを活用する checkValidityで入力欄やフォーム全体が有効か確認できるvalidityを見ると、エラーの種類を判定できる- エラー表示では
aria-invalidやaria-describedbyも意識する preventDefaultは、送信を止めたい時だけ使う- 二重送信防止はフロント側だけでなく、サーバー側の対応も重要
次の章では、スクロールに応じた表示アニメーションを実装するために、Intersection Observerを学びます。