お問い合わせフォームの離脱率が高い、入力エラーで問い合わせが減っているという悩みを解決。実際にコンバージョン率を2倍に改善したJavaScriptスニペットを公開します。
こんな悩みありませんか?
「お問い合わせフォームの離脱率が高い」「入力エラーが多くて問い合わせが完了しない」「スマホでの入力がしづらい」
このような悩みを抱えているWeb担当者の方は多いのではないでしょうか。実際に弊社のクライアントでも、フォーム改善前は問い合わせ完了率が30%程度という企業が少なくありませんでした。
しかし、適切なJavaScriptスニペットを実装することで、問い合わせ完了率を60%以上まで改善した事例を複数持っています。今回は、実際に効果があったフォーム改善テクニックをご紹介します。
リアルタイムバリデーションで入力ミスを防ぐ
課題:送信ボタンを押してからエラーに気づく
ある製造業のクライアントでは、問い合わせフォームで「送信ボタンを押してから初めてエラーが表示される」という状況でした。ユーザーは一度にすべてのエラーを見て、修正する気力を失ってしまいます。
解決策:リアルタイムバリデーション
入力中にリアルタイムでバリデーションを行うスニペットを実装しました:
class FormValidator {
constructor(formElement) {
this.form = formElement;
this.init();
}
init() {
const inputs = this.form.querySelectorAll('input, textarea');
inputs.forEach(input => {
input.addEventListener('blur', (e) => this.validateField(e.target));
input.addEventListener('input', (e) => this.clearError(e.target));
});
}
validateField(field) {
const value = field.value.trim();
const fieldType = field.type;
let isValid = true;
let message = '';
// 必須チェック
if (field.hasAttribute('required') && !value) {
isValid = false;
message = 'この項目は必須です';
}
// メールバリデーション
else if (fieldType === 'email' && value) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
isValid = false;
message = '正しいメールアドレスを入力してください';
}
}
// 電話番号バリデーション
else if (field.name === 'tel' && value) {
const telRegex = /^[0-9\-\+\(\)\s]+$/;
if (!telRegex.test(value)) {
isValid = false;
message = '正しい電話番号を入力してください';
}
}
this.showValidationResult(field, isValid, message);
}
showValidationResult(field, isValid, message) {
const errorElement = field.nextElementSibling;
if (!isValid) {
field.classList.add('error');
if (errorElement && errorElement.classList.contains('error-message')) {
errorElement.textContent = message;
} else {
const errorDiv = document.createElement('div');
errorDiv.className = 'error-message';
errorDiv.textContent = message;
field.parentNode.insertBefore(errorDiv, field.nextSibling);
}
} else {
field.classList.remove('error');
field.classList.add('valid');
}
}
clearError(field) {
field.classList.remove('error');
const errorElement = field.nextElementSibling;
if (errorElement && errorElement.classList.contains('error-message')) {
errorElement.remove();
}
}
}
// 使用方法
document.addEventListener('DOMContentLoaded', () => {
const contactForm = document.querySelector('#contact-form');
new FormValidator(contactForm);
});
結果:問い合わせ完了率が47%向上
リアルタイムバリデーション実装後、問い合わせ完了率が30%から44%に改善しました。ユーザーが入力中に問題を解決できるため、最後の送信でつまずくことが激減したのです。
入力補助機能でユーザビリティを向上
郵便番号から住所自動入力
特にBtoBの問い合わせフォームでは住所入力が必要なケースが多く、入力の手間が離脱要因になっていました。
class AddressHelper {
constructor() {
this.apiUrl = 'https://zipcloud.ibsnet.co.jp/api/search';
this.init();
}
init() {
const zipInput = document.querySelector('input[name="zipcode"]');
if (zipInput) {
zipInput.addEventListener('input', (e) => this.handleZipInput(e));
}
}
handleZipInput(event) {
const zip = event.target.value.replace(/\D/g, '');
if (zip.length === 7) {
this.searchAddress(zip);
}
}
async searchAddress(zipcode) {
try {
const response = await fetch(`${this.apiUrl}?zipcode=${zipcode}`);
const data = await response.json();
if (data.results && data.results.length > 0) {
const result = data.results[0];
const prefecture = document.querySelector('input[name="prefecture"]');
const city = document.querySelector('input[name="city"]');
const town = document.querySelector('input[name="town"]');
if (prefecture) prefecture.value = result.address1;
if (city) city.value = result.address2;
if (town) town.value = result.address3;
// 入力補完の視覚的フィードバック
this.showCompletionAnimation([prefecture, city, town]);
}
} catch (error) {
console.error('住所検索エラー:', error);
}
}
showCompletionAnimation(fields) {
fields.forEach(field => {
if (field && field.value) {
field.classList.add('auto-filled');
setTimeout(() => field.classList.remove('auto-filled'), 1000);
}
});
}
}
new AddressHelper();
送信前確認とローディング状態の実装
よくある失敗:二重送信と送信状態の不明確さ
多くのフォームで見かける問題が「送信ボタンを何度も押してしまう」「送信中なのかわからない」という状況です。これは技術的には簡単に解決できますが、意外と実装されていないサイトが多いのが現状です。
class FormSubmissionHandler {
constructor(form) {
this.form = form;
this.submitButton = form.querySelector('button[type="submit"]');
this.originalButtonText = this.submitButton.textContent;
this.init();
}
init() {
this.form.addEventListener('submit', (e) => this.handleSubmit(e));
}
async handleSubmit(event) {
event.preventDefault();
// バリデーションチェック
if (!this.validateForm()) {
return;
}
// 確認ダイアログ
if (!this.showConfirmation()) {
return;
}
// 送信状態に変更
this.setLoadingState(true);
try {
const formData = new FormData(this.form);
const response = await fetch(this.form.action, {
method: 'POST',
body: formData
});
if (response.ok) {
this.showSuccess();
} else {
throw new Error('送信に失敗しました');
}
} catch (error) {
this.showError(error.message);
} finally {
this.setLoadingState(false);
}
}
validateForm() {
const requiredFields = this.form.querySelectorAll('[required]');
let isValid = true;
requiredFields.forEach(field => {
if (!field.value.trim()) {
field.classList.add('error');
isValid = false;
}
});
return isValid;
}
showConfirmation() {
const formData = new FormData(this.form);
const confirmText = `以下の内容で送信してよろしいですか?\n\n` +
`お名前: ${formData.get('name') || ''}\n` +
`メール: ${formData.get('email') || ''}\n` +
`件名: ${formData.get('subject') || ''}`;
return confirm(confirmText);
}
setLoadingState(isLoading) {
if (isLoading) {
this.submitButton.disabled = true;
this.submitButton.innerHTML = '<span class="spinner"></span>送信中...';
} else {
this.submitButton.disabled = false;
this.submitButton.textContent = this.originalButtonText;
}
}
showSuccess() {
// フォームを隠して成功メッセージを表示
this.form.innerHTML = `
<div class="success-message">
<h3>お問い合わせありがとうございました</h3>
<p>担当者より2営業日以内にご連絡いたします。</p>
</div>
`;
}
showError(message) {
const errorDiv = document.createElement('div');
errorDiv.className = 'form-error';
errorDiv.textContent = `エラー: ${message}`;
this.form.insertBefore(errorDiv, this.form.firstChild);
}
}
// 使用方法
document.addEventListener('DOMContentLoaded', () => {
const forms = document.querySelectorAll('form[data-ajax="true"]');
forms.forEach(form => new FormSubmissionHandler(form));
});
実装時の注意点
アクセシビリティを忘れずに
JavaScriptでフォームを改善する際、アクセシビリティを損なってしまうケースがあります。以下の点は必ず確認しましょう:
- エラーメッセージは
aria-describedbyで関連付ける - キーボード操作でも同様に動作する
- スクリーンリーダーでも内容が伝わる
パフォーマンスへの配慮
住所検索APIなど外部サービスを使用する場合は、適切なデバウンス処理を実装し、無駄なAPIコールを避けましょう。
まず始めるべきこと
今回紹介したスニペットの中で、最も効果が高いのはリアルタイムバリデーションです。まずはこの機能から実装を始めることをお勧めします。
実装が難しいと感じる場合や、より高度な改善をご希望の場合は、ぜひ弊社にご相談ください。横浜で20年以上Web制作に携わってきた経験を活かし、御社のフォームを最適化いたします。