【JavaScript】リアルタイムバリデーション(入力中に即時チェック)

JavaScript

はじめに

前回記事のボタンを押してからバリデーション実施では、大量のエラーがどっと出ると一つ一つ直していくのは大変です。リアルタイムバリデーションなら、入力中にすぐ「何を直せばよいか」を示すことができます。
今回は、リアルタイムバリデーションの実装方法をLogとして記載していきたいと思います。

基礎から体系的に学びたい方にはこちらの本がおすすめです 👉
1冊ですべて身につくJavaScript入門講座

 

 

リアルタイムバリデーションとは

リアルタイムバリデーションとは、ユーザーが入力している最中(キー入力・貼り付け・フォーカス移動など)に即時でチェックを走らせ、エラーやヒントを表示する手法です。

 

メリット

  • 即時フィードバックが可能:送信前に間違いに気づくことができる
  • 修正コストの軽減:どの条件に引っかかったかがすぐわかり修正できる

 

デメリット

  • 過剰なアラートは逆効果:入力途中の「未完成」を即エラー扱いしない工夫が必要となる(デバウンスや初回はblurで判定などの工夫)
  • パフォーマンス低下:高頻度の検証は負荷に注意(正規表現や外部API照会はデバウンス/スロットリング)

 

リアルタイムバリデーションの実装方法

htmlとJavaScriptを使用して、簡単な実装例を試してみます。

 

html

<!doctype html>
<html lang="ja">

<head>
    <meta charset="utf-8" />
    <title>リアルタイムバリデーション</title>
    <link rel="stylesheet" href="css/realvalid.css" />
    <script src="js/realvalid.js" defer></script>
</head>

<body>
    <form id="signupForm" novalidate>
        <div class="field">
            <label for="username">ユーザー名(3文字以上)</label>
            <input id="username" name="username" type="text" required />
            <p id="usernameError" class="error" aria-live="polite"></p>
        </div>

        <div class="field">
            <label for="email">メールアドレス</label>
            <input id="email" name="email" type="email" required />
            <p id="emailError" class="error" aria-live="polite"></p>
        </div>

        <div class="field">
            <label for="age">年齢(0〜120)</label>
            <input id="age" name="age" type="number" inputmode="numeric" min="0" max="120" required />
            <p id="ageError" class="error" aria-live="polite"></p>
        </div>

        <div class="field checkbox">
            <label>
                <input id="tos" name="tos" type="checkbox" required />
                利用規約に同意する
            </label>
            <p id="tosError" class="error" aria-live="polite"></p>
        </div>

        <button type="submit" id="submitBtn">登録する</button>
        <p id="formStatus" class="status" aria-live="polite"></p>
    </form>
</body>
</html>

 

css

body {
    font-family: system-ui, sans-serif;
    line-height: 1.6;
    padding: 24px;
}

.field {
    margin-bottom: 16px;
}

label {
    display: block;
    font-weight: 600;
    margin-bottom: 6px;
}

input[type="text"],
input[type="email"],
input[type="number"] {
    width: 320px;
    max-width: 100%;
    padding: 10px;
    border: 1px solid #bbb;
    border-radius: 6px;
    outline: none;
}

input:focus {
    border-color: #4b8cf7;
    box-shadow: 0 0 0 3px rgba(75, 140, 247, .2);
}

.error {
    color: #c83532;
    font-size: 12px;
    min-height: 1.2em;
    margin: 6px 0 0;
}

.is-invalid {
    border-color: #c83532 !important;
    background: #fff7f7;
}

.checkbox label {
    display: inline-flex;
    gap: 8px;
    align-items: center;
    font-weight: 400;
}

button {
    padding: 10px 16px;
    border: none;
    border-radius: 6px;
    background: #0ea5e9;
    color: #fff;
    cursor: pointer;
}

button:disabled {
    opacity: .6;
    cursor: not-allowed;
}

.status {
    margin-top: 12px;
}

 

JavaScript

// --- デバウンス ---
const debounce = (fn, delay = 200) => {
    let t; return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), delay); };
};

const fields = {
    username: { el: document.getElementById("username"), err: document.getElementById("usernameError") },
    email: { el: document.getElementById("email"), err: document.getElementById("emailError") },
    age: { el: document.getElementById("age"), err: document.getElementById("ageError") },
    tos: { el: document.getElementById("tos"), err: document.getElementById("tosError") },
};

// --- ルール ---
const emailPattern = /^[^\s@]+@[^\s@]+\.[a-z]{2,}$/i;

// --- 検証関数 ---
function validateUsername(value) {
    if (!value.trim()) return "ユーザー名は必須です";
    if (value.trim().length < 3) return "ユーザー名は3文字以上で入力してください";
    return "";
}

function validateEmail(value) {
    if (!value.trim()) return "メールアドレスは必須です";
    if (!emailPattern.test(value.trim())) return "メールアドレスの形式が正しくありません";
    return "";
}

function validateAge(value) {
    if (value === "" || value === null) return "年齢は必須です";
    const n = Number(value);
    if (!Number.isFinite(n)) return "年齢は数値で入力してください";
    if (n < 0 || n > 120) return "年齢は0〜120の範囲で入力してください";
    return "";
}

function validateTos(checked) {
    return checked ? "" : "利用規約に同意してください";
}

// --- 表示更新 ---
function renderFieldState(field, message) {
    field.err.textContent = message || "";
    field.el.classList.toggle("is-invalid", Boolean(message));
}

// --- 単体検証 ---
function validateField(name) {
    switch (name) {
        case "username":
            return renderFieldState(fields.username, validateUsername(fields.username.el.value));
        case "email":
            return renderFieldState(fields.email, validateEmail(fields.email.el.value));
        case "age":
            return renderFieldState(fields.age, validateAge(fields.age.el.value));
        case "tos":
            return renderFieldState(fields.tos, validateTos(fields.tos.el.checked));
    }
}

// --- イベント登録 ---
const onInput = debounce((e) => validateField(e.target.id), 180);

["username", "email", "age"].forEach(id => {
    fields[id].el.addEventListener("input", onInput);
    fields[id].el.addEventListener("blur", (e) => validateField(e.target.id));
});
fields.tos.el.addEventListener("change", () => validateField("tos"));

 

まとめ

リアルタイムバリデーションは、エラーをその場で直すことができるためユーザビリティの向上につながります。
本記事のテンプレート(HTML/CSS/JS)をベースに、検証ルール・メッセージ・見た目をプロジェクト要件に合わせて調整してみてください。

 

最後に

JavaScriptの環境構築は、この記事を参照してみてください。
【JavaScript】VSCodeでJavaScriptを使用するための環境構築を実施する – SEもりのLog JavaScript

以上、ログになります。
これからも継続していきましょう!!

JavaScriptフロントサイド関連
おすすめIT本
良いコード/悪いコードで学ぶ設計入門

「ITエンジニア本大賞2023」技術書部門で大賞を受賞した本です。
・コードの可読性
・普段意識したほうが良いこと
・リファクタリング考え方
等、普段のコードを設計する際に意識することが書かれています。
コードのあるべき姿に迷ったら一度読んでみると良い本です。

仕組みと使い方がわかる Docker&Kubernetesのきほんのきほん

Dockerって何?となったときに私が最初に読んだ本です。
Dockerがどんな仕組みで動いているのか、コマンドでは何を命令しているのかを理解できるように、イラストを多用して説明しています。

1冊ですべて身につくJavaScript入門講座

「ITエンジニア本大賞2024」技術書部門で大賞を受賞した本です。
私が次に読もうと思っている本なのでおすすめとして挙げておきたいと思います。

コメント

タイトルとURLをコピーしました