送信時の遅延または二重送信
HTMLフォームで「送信」ボタンをクリックすると、「ありがとう」ページにリダイレクトされるまでの短い間、不安を感じた経験があるかと思います。その時間はほんの数秒ですが、「フォームが機能していないのでは?」「クリックは正常に受け付けられたのか?」「インターネット接続が切れた?」といった疑問が生じることがあります。パニックになったユーザーが送信ボタンを再度クリックしてしまい、重複送信が発生することもあります。
この遅延はユーザー体験を損なうだけでなく、2回目のクリックによってFormspreeフォームへのリクエストが重複し(使用量クォータにカウントされます)、送信リストにも重複レコードが作成されてしまいます(後で削除が必要になります)。本ガイドでは、この問題が発生する理由と、短いJSスニペットを使って解決する方法を説明します。
なぜこのような問題が起きるのか?
Formspreeフォームで「送信」ボタンをクリックした後に発生する遅延は、Formspree自体の問題ではなく、Webフォームの仕組みによるものです。処理の流れを簡単に説明すると:ユーザーが「送信」ボタンをクリックすると、ブラウザはフォームデータをバックエンドにHTTPリクエストとして送信します。このリクエストはインターネットを通じてバックエンドに到達しますが、その途中でネットワーク接続の遅延が発生することがあります。
リクエストがサーバーに到達すると、処理が行われます。これには情報の検証、データベースへの保存、確認メールの送信などが含まれます。この処理にも時間がかかりますが、通常はネットワーク転送よりも短い時間です。処理が完了すると、バックエンドは確認レスポンスを返し、ユーザーを「ありがとう」ページにリダイレクトします。
リクエストがサーバーとの間を往復する時間が、ユーザー側での「送信」ボタンクリック後の遅延を引き起こします。さらに、モバイルデータネットワークのような低速な接続では、リクエストの遅延がさらに大きくなります。
遅延の様子は以下のとおりです:

どのように解決できるか?
HTTPリクエストの処理時間そのものを短縮することはできませんが、ユーザーの待機体験を改善することは可能です。React(またはNext/Gatsby)ベースのプロジェクトを開発している場合は、Formspree ReactライブラリのuseFormフックが返すstateオブジェクトを使用して、フォームが送信中かどうかを確認し、送信ボタンを無効化できます。ただし、バニラJSの場合は、このチェックを自分で実装する必要があります。
そのためには、Webページでフォームが送信されるたびに発火するフォーム送信イベントを監視する必要があります。このイベントを受け取ったら、送信ボタンを無効化したり、フォームデータがバックグラウンドで処理中であることをユーザーに伝えるUI要素(スピナーなど)を表示したりします。
注意:「送信」ボタンのクリックイベントを使って同様のロジックを実装することもできますが、フォームの
submitイベントの方が、送信ボタンのクリック、フィールド編集中のEnterキー押下、form.requestSubmit()メソッドによるプログラムからの送信など、あらゆる送信方法をより確実に捕捉できます。
送信時にボタンを無効化する
バックグラウンド処理をユーザーに伝える最もシンプルな方法は、送信ボタンを無効化し、オプションでボタンにホバーした際に待機カーソルを表示することです。見た目は以下のようになります:

これを実装するには、CSSでボタンの無効状態に対してグレーアウトした背景とwaitカーソルを設定します。次に、HTMLフォームのsubmitイベントを監視し、submitイベントが発生したときに送信ボタンのdisabled属性をtrueに設定します。
HTMLファイルの例は以下のとおりです:
<html>
<head>
<style>
/* Other styles omitted... */
/* 1. Set the background and cursor for the disabled state of the button */
#submitButton:disabled {
cursor: wait;
background: #dfdfdf;
}
</style>
</head>
<body>
<form id="contact-form" name="simple-contact-form" accept-charset="utf-8" action="https://formspree.io/f/{form_id}"
method="post">
<label for="full-name">Full Name</label>
<input type="text" name="name" id="full-name" placeholder="First and Last" required="">
<label for="email-address">Email Address</label>
<input type="email" name="_replyto" id="email-address" placeholder="email@domain.tld" required="">
<label for="message">Message</label>
<textarea rows="5" name="message" id="message"
placeholder="Your message here"
required=""></textarea>
<input id="submitButton" type="submit" value="Submit">
</form>
<!-- 2. Add the following script to listen to the submit event -->
<script>
const form = document.getElementById('contact-form');
const submitButton = document.getElementById('submitButton');
form.addEventListener('submit', function (event) {
// Sets the submit button's disabled attribute as true
submitButton.disabled = true;
});
</script>
</body>
</html>
この方法はシンプルで、プラットフォームネイティブのカーソルローダーを利用できます。たとえば、Windowsユーザーには以下のように表示されます:

モバイルデバイスではカーソルは表示されませんが、送信ボタンはグレーアウトされるため、ユーザーはタップが受け付けられたことを確認でき、再度タップすることを防げます:

送信時にボタンを別のUI要素に置き換える
別の方法として、フォーム送信時に送信ボタンを完全に非表示にして、スピナーなどのUI要素を代わりに表示することもできます:

このフォームのコードも同様にシンプルです:
<html>
<head>
<style>
/* 1. Add the following styles */
#loader-container {
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
#loader {
border: 4px solid #e5122e;
border-bottom: 4px solid #fff;
border-radius: 50%;
width: 16px;
height: 16px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<form id="contact-form" name="simple-contact-form" accept-charset="utf-8" action="<https://formspree.io/f/{form_id}>"
method="post">
<fieldset id="fs-frm-inputs">
<div id="inputs">
<label for="full-name">Full Name</label>
<input type="text" name="name" id="full-name" placeholder="First and Last" required="">
<label for="email-address">Email Address</label>
<input type="email" name="_replyto" id="email-address" placeholder="email@domain.tld" required="">
<label for="message">Message</label>
<textarea rows="5" name="message" id="message"
placeholder="Your message here"
required=""></textarea>
<input type="hidden" name="_subject" id="email-subject" value="Contact Form Submission">
</div>
</fieldset>
<input id="submitButton" type="submit" value="Submit">
<!-- 2. Add the following div tags -->
<div id="loader-container" >
<div id="loader" hidden="hidden"></div>
</div>
</form>
<!-- 3. Add the following script to listen to the submit event -->
<script>
const form = document.getElementById('contact-form');
const submitButton = document.getElementById('submitButton');
const loader = document.getElementById('loader')
form.addEventListener('submit', function (event) {
// Removes the hidden attribute from the loader element
loader.hidden = ""
// Sets the submit button to be hidden
submitButton.hidden = "hidden";
});
</script>
</body>
</html>
送信ボタンの直下に2つの<div>(入れ子構造)を追加し、ローダーをコンテナ内に中央配置するCSSと、ローダーのアニメーション用キーフレームを設定します。最後に、フォーム送信イベントを監視して送信ボタンを非表示にしつつローダーを表示するJSスクリプトを追加します。
この方法では、ローダー要素のUIデザインをより柔軟に制御できます。また、プラットフォームを問わず一貫したデザインのローダーを表示することが可能です。
フォーム送信イベントリスナーを追加するスクリプトは、イベントリスナーを設定するためにHTMLのDOMにアクセスする必要があります。そのため、DOMの読み込みが完了した後にのみコードが実行されるよう注意してください。インラインスクリプトを使用する場合は、上記の例のように
</body>タグの直前に追加してください。外部スクリプトを使用する場合は、<script>タグのdefer属性を使用してHTMLのDOM解析後まで実行を遅延させるか、スクリプト内でフォーム送信イベントリスナーを追加する前にDOMContentLoadedイベントを監視するようにしてください。
フォームで快適なユーザー体験を提供する
以上のように、送信ボタンクリック後のフィードバックの遅延や重複送信の問題は、フォーム送信のバックグラウンド処理をユーザーに伝えるUI要素を実装することで簡単に解決できます。本ガイドが仕組みの理解と、すぐに使えるコードスニペットの提供に役立てば幸いです。