【ステップ式フォーム】jQueryを使わないで実装する、コピペOKのステップ式フォーム【JavaScript】

ステップ式フォームを

Vanilla.js(純粋なJavaScript)で実装する機会があったので備忘録として記載

ステップ式フォームをこれから実装する人の参考になれば幸いです。

なぜjQueyをなぜ使わないのか?

ポイントイメージ

jQueryはInternet Explorer時代に一世を風靡したJavaScriptフレームワークで

純粋なJavaScriptを書くよりもスピード感を持って実装できる画期的なフレームワーク

Internet Explorerがサ終(サポート終了)になった今、

使うメリットがなくなり

スマホで反応しなかったり不具合が多数報告。

いつjQueryもサ終になるかわからないので、jQueryで実装することが返ってデメリットに感じている。

そこで、今回はVanilla.js(純粋なJavaScript)で実装することに

ステップ式フォーム

See the Pen Untitled by 松村祐弥 (@lveklkok-the-vuer) on CodePen.

入力しないと次に進めないように設定してます。

更に、入力値が誤っているとアラートが表示されるようになってます。

まだhtmlでコードを書いている段階なので

Contact Form 7で動作するか確認してません。

コンタクトフォーム7で動作しなかったので、コンタクトフォーム7を使用している方は下記の記事も参考にしてください。

HTML
<form action="">
            <div class="form-horizontal c-form-step">
              <div class="c-form-step__wrapper-box current" data-step="0">
                <div class="input-container name-container">
                  <div class="label-wrap">
                    <label for="name" class="label-text">お名前</label>
                  </div>
                  <div class="input-wrap">
                    <input type="text" name="" id="name" class="input-text" required data-target-button="0">
                  </div>
                </div>
                <div class="form-btnWrap">
                  <button type="button" class="next-btn" onclick="nextStep(this)">次に進む</button>
                </div>
              </div><!-- c-form-step__wrapper-box end -->
              <div class="c-form-step__wrapper-box" data-step="1">
                <div class="input-container post-container">
                  <div class="label-wrap">
                    <label for="post" class="label-text">郵便番号</label>
                  </div>
                  <div class="input-wrap">
                    <input type="text" name="" id="post" class="input-text" required data-target-button="1">
                  </div>
                </div>
                <div class="input-container address-container">
                  <div class="label-wrap">
                    <label for="address" class="label-text">住所</label>
                  </div>
                  <div class="input-wrap">
                    <input type="text" name="" id="address" class="input-text" required data-target-button="1">
                  </div>
                </div>
                <div class="form-btnWrap">
                  <button type="button" class="prev-btn" onclick="prevStep()">戻る</button>
                  <button type="button" class="next-btn" onclick="nextStep(this)">次に進む</button>
                </div>
              </div><!-- c-form-step__wrapper-box end -->
              <div class="c-form-step__wrapper-box" data-step="2">
                <div class="input-container tel-container">
                  <div class="label-wrap">
                    <label for="tel" class="label-text">電話番号</label>
                  </div>
                  <div class="input-wrap">
                    <input type="tel" name="" id="tel" class="input-text" required data-target-button="2">
                  </div>
                </div>
                <div class="form-btnWrap">
                  <button type="button" class="prev-btn" onclick="prevStep()">戻る</button>
                  <button type="button" class="next-btn" onclick="nextStep(this)">次に進む</button>
                </div>
              </div><!-- c-form-step__wrapper-box end -->
              <div class="c-form-step__wrapper-box" data-step="3">
                <div class="input-container email-container">
                  <div class="label-wrap">
                    <label for="email" class="label-text">メールアドレス</label>
                  </div>
                  <div class="input-wrap">
                    <input type="email" name="" id="email" class="input-text" required data-target-button="3">
                  </div>
                </div>
                <div class="form-btnWrap">
                  <button type="button" class="prev-btn" onclick="prevStep()">戻る</button>
                  <button type="button" class="next-btn" onclick="nextStep(this)">次に進む</button>
                </div>
              </div><!-- c-form-step__wrapper-box end -->
              <div class="c-form-step__wrapper-box" data-step="4">
                <div class="input-container content-container">
                  <div class="label-wrap">
                    <label for="content" class="label-text" required>お問い合わせ内容</label>
                  </div>
                  <div class="input-wrap">
                    <textarea name="" id="content" cols="30" rows="10" class="input-text" data-target-button="4"></textarea>
                  </div>
                </div>
                <div class="form-btnWrap">
                  <button type="button" class="prev-btn" onclick="prevStep()">戻る</button>
                  <button type="submit" class="next-btn">申し込む</button>
                </div>
              </div><!-- c-form-step__wrapper-box end -->
            </div>
          </form>

これが今回使用したhtmlです。

質問項目を増やしたり減らしたりしたい人は

.c-form-step__wrapper-boxを調整してください。

CSS
.form-btnWrap {
  display: flex;
  align-items: center;
  justify-content: center;
  column-gap: 40px;
  margin-top: 30px;
}

.next-btn {
  display: block;
  background: #c6d3e0;
  border: none;
  color: #fff;
  font-weight: bold;
  pointer-events: none;
  padding: 10px 30px;
  border-radius: 999px;
  transition: all 0.5s ease ;
}

.next-btn.active {
  pointer-events: auto;
  background: rgb(4,95,186);
  background: linear-gradient(0deg, rgba(4,95,186,1) 0%, rgba(98,247,230,1) 100%);
  transition: all 0.5s ease ;
}

.prev-btn {
  background: #7a7a7a;
  border: none;
  color: #fff;
  font-weight: bold;
  display: block;
  padding: 10px 30px;
  border-radius: 999px;
}


.input-container {
  padding: 10px 0px;
  display: flex;
  align-items: center;
}

.label-wrap {
  width: 30%;

}

.label-text {
  color: rgb(4,95,186);
  font-weight: bold;
}

.input-wrap {
  width: 70%;
  margin-left: 20px;
}

.input-text {
  border: 1px solid #c5c5c5;
  width: 100%;
  padding:10px;
  background: #efefef;
}

.form-submit {
  margin-top: 30px;
  text-align: center;
}

.submit {
  padding: 10px 20px;
  background: rgb(12,137,183);
  background: linear-gradient(0deg, rgba(12,137,183,1) 0%, rgba(12,183,183,1) 100%);
  font-weight: bold;
  color: #fff;
  font-size: 24px;
  transition: all 0.5s ease;
}

CSSです。

適宜変更してください。

JavaScript
const stepWrappers = document.querySelectorAll(".c-form-step__wrapper-box");
  let currentStep = 0;

  // 最初に表示するステップを設定
  showStep(currentStep);

  // 各input要素にイベントリスナーを追加
  document.querySelectorAll('.input-text').forEach(input => {
    input.addEventListener('input', function() {
      const targetButtonIndex = this.dataset.targetButton;
      const targetButton = stepWrappers[targetButtonIndex].querySelector('.next-btn');
      const isValid = validateInput(this); // 入力の検証
      targetButton.classList.toggle('active', isValid);
    });
  });

  function showStep(stepIndex) {
    if (stepIndex < 0 || stepIndex >= stepWrappers.length) {
      return;
    }

    stepWrappers.forEach((wrapper, index) => {
      wrapper.style.display = index === stepIndex ? "block" : "none";
    });

    currentStep = stepIndex;
  }

  function nextStep(button) {
    // 現在のステップの入力値を検証
    const currentInput = stepWrappers[currentStep].querySelector('.input-text');
    const isValid = validateInput(currentInput);

    if (!isValid) {
      // 入力エラーの場合、アラートを出力
      alert(getErrorMessage(currentInput.id));
      return; // 処理を中断
    }

    // ボタンの活性/非活性制御
    button.classList.toggle('active', isValid);

    // 有効な入力値の場合のみ、次のステップへ進む
    if (isValid) {
      showStep(currentStep + 1);
    }
  }

  function prevStep() {
    // 前のステップへ戻る
    showStep(currentStep - 1);
    // 現在のステップのボタンを非アクティブにする
    const currentButton = stepWrappers[currentStep].querySelector(".next-btn");
    if (currentButton) {
      currentButton.classList.remove("active");
    }
  }

  /**
   * 入力値の検証を行う関数
   * @param {HTMLInputElement} input 検証対象のinput要素
   * @returns {boolean} 検証結果(true: 有効、false: 無効)
   */
  function validateInput(input) {
    if (!input) {
      return false; // input要素がない場合は無効とする
    }
    const value = input.value.trim();
    const id = input.id;

   // 各入力項目ごとの検証ルール
  switch (id) {
    case 'name':
      return value !== ''; // 名前は空チェックのみ
    case 'post':
      return /^\d{7}$/.test(value); // 郵便番号は7桁の数字のみ
    case 'address':
      return value !== ''; // 住所は空チェックのみ
    case 'tel':
      return /^\d{10,11}$/.test(value); // 電話番号は10桁または11桁の数字のみ
    case 'email':
      return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value); // メールアドレスは形式チェック
    case 'content':
      return value !== ''; // お問い合わせ内容は空チェックのみ
    default:
      return true; // その他の項目はtrue
  }
}

  /**
   * 入力項目ごとのエラーメッセージを返す関数
   * @param {string} inputId 入力項目のID
   * @returns {string} エラーメッセージ
   */
  function getErrorMessage(inputId) {
    switch (inputId) {
      case 'name':
        return 'お名前を入力してください。';
      case 'post':
        return '郵便番号を正しく入力してください。';
      case 'address':
        return '住所を入力してください。';
      case 'tel':
        return '電話番号を正しく入力してください。';
      case 'email':
        return 'メールアドレスを正しく入力してください。';
      case 'content':
        return 'お問い合わせ内容を入力してください。';
      default:
        return '入力内容を確認してください。';
    }
  }

これが今回使用したJavaScriptです。

jQueryならこれの半分くらいで済みます。

JavaScriptの解説

複数ステップのフォームにおいて、各ステップの入力が完了してから次のステップへ進むように制御し、入力値の検証も同時に行います。

全体的な処理の流れ

  1. ステップのHTML要素を取得:const stepWrappers = document.querySelectorAll(".c-form-step__wrapper-box");
  2. 現在のステップを初期化:let currentStep = 0;
    • currentStep 変数は、現在表示されているステップのインデックスを示します。初期値は 0 で、最初のステップを表します。
  3. 最初のステップを表示:showStep(currentStep);
    • ページ読み込み時に showStep 関数を呼び出し、最初のステップ (currentStep は初期値 0) を表示します。
  4. 入力フィールドにイベントリスナーを設定:document.querySelectorAll('.input-text').forEach(input => { input.addEventListener('input', function() { // ... 省略 }); });
    • document.querySelectorAll(‘.input-text’) で、HTMLドキュメント内にある .input-text クラスを持つすべての入力フィールドを取得します。
    • 取得した各入力フィールドに input イベントリスナーを設定し、入力内容が変更されるたびに以下の処理を実行します。
      • const targetButtonIndex = this.dataset.targetButton; で、入力フィールドの data-target-button 属性の値を取得し、targetButtonIndex に格納します。この値は、対応する「次に進む」ボタンのインデックスを示します。
      • const targetButton = stepWrappers[targetButtonIndex].querySelector(‘.next-btn’); で、targetButtonIndex を使用して対応する「次に進む」ボタンを取得し、targetButton に格納します。
      • const isValid = validateInput(this); で、validateInput 関数を呼び出し、現在の入力フィールドの値を検証します。検証結果 (true または false) は isValid に格納されます。
      • targetButton.classList.toggle(‘active’, isValid); で、「次に進む」ボタンの active クラスを、検証結果 (isValid) に基づいて切り替えます。
  5. showStep 関数の詳細:function showStep(stepIndex) { if (stepIndex < 0 || stepIndex >= stepWrappers.length) { return; } stepWrappers.forEach((wrapper, index) => { wrapper.style.display = index === stepIndex ? "block" : "none"; }); currentStep = stepIndex; }
    • stepIndex で指定されたインデックスのステップを表示し、それ以外のステップを非表示にします。
      • stepIndex が有効な範囲内 (0 以上、ステップの数未満) であることを確認します。
      • stepWrappers をループ処理し、各ステップ (wrapper) について、現在のステップ (stepIndex) と一致する場合にのみ display: block を設定し、それ以外の場合は display: none を設定することで、表示を切り替えます。
      • 最後に currentStep を更新し、現在表示されているステップのインデックスを保持します。
  6. nextStep 関数の詳細:function nextStep(button) { // 現在のステップの入力値を検証 const currentInput = stepWrappers[currentStep].querySelector('.input-text'); const isValid = validateInput(currentInput); if (!isValid) { // 入力エラーの場合、アラートを出力 alert(getErrorMessage(currentInput.id)); return; // 処理を中断 } // ボタンの活性/非活性制御 button.classList.toggle('active', isValid); // 有効な入力値の場合のみ、次のステップへ進む if (isValid) { showStep(currentStep + 1); } }
    • 「次に進む」ボタンがクリックされた際に実行される関数です。
      • const currentInput = stepWrappers[currentStep].querySelector(‘.input-text’); で、現在のステップ (currentStep) にある入力フィールドを取得します。
      • const isValid = validateInput(currentInput); で、入力フィールドの値を検証します。
      • 入力値が無効 (!isValid) の場合は、エラーメッセージを表示し、処理を中断します。
      • 入力値が有効な場合は、「次に進む」ボタンの active クラスを制御し、次のステップ (currentStep + 1) を表示します。
  7. prevStep 関数の詳細:function prevStep() { showStep(currentStep - 1); // 現在のステップのボタンを非アクティブにする const currentButton = stepWrappers[currentStep].querySelector(".next-btn"); if (currentButton) { currentButton.classList.remove("active"); } }
    • 「戻る」ボタンがクリックされた際に実行される関数です。
      • 前のステップ (currentStep – 1) を表示します。
      • 現在のステップの「次に進む」ボタンを非アクティブにします。
  8. validateInput 関数の詳細:function validateInput(input) { // ... 省略 }
    • 入力値を検証する関数です。
      • 入力フィールドの値を受け取り、各入力項目ごとのルールに基づいて検証を行います。
      • 検証結果 (true または false) を返します。
  9. getErrorMessage 関数の詳細:function getErrorMessage(inputId) { // ... 省略 }
    • 入力項目に対応するエラーメッセージを返す関数です。
      • 入力項目のIDを受け取り、対応するエラーメッセージを返します。
  10. querySelectorAll(“.c-form-step__wrapper-box”) を使用して、HTMLドキュメント内にある .c-form-step__wrapper-box クラスを持つすべての要素を取得し、stepWrappers に格納します。

ポイント

  • 各ステップは .c-form-step__wrapper-box クラスを持つ要素で囲みます。
  • 入力フィールドには .input-text クラスを付け、data-target-button 属性で対応する「次に進む」ボタンのインデックスを指定します。
  • 「次に進む」ボタンには .next-btn クラスを、「戻る」ボタンには .prev-btn クラスを付けます。
  • validateInput 関数と getErrorMessage 関数は、フォームの仕様に合わせて適宜修正します。

コンタクトフォーム7で動作しなかったので、コンタクトフォーム7を使用している方は下記の記事も参考にしてください。