前回のこの記事をJavaScriptにしました。
https://qiita.com/WebDesignGaju/items/7a6f3a68c3a0d690c7ed
主な変換ポイント
1. jQuery → Vanilla JavaScript の基本変換
-
$(document).ready()→document.addEventListener('DOMContentLoaded') -
$('selector')→document.querySelector()/document.querySelectorAll() -
.on('event', function)→.addEventListener('event', function) -
.addClass()/.removeClass()→.classList.add()/.classList.remove()
2. DOM操作の変換
-
.after()→parentNode.insertBefore(element, nextSibling) -
.siblings()→parentNode.querySelector()+ 条件 -
.remove()→parentNode.removeChild()or.remove() -
.text()→.textContent -
.val()→.value
3. イベント処理の改善
- 要素の存在チェック(
if (element))を追加 -
forEachでイベントリスナーを一括登録 - メモリリーク防止のため適切なイベント管理
4. エラーハンドリングの強化
-
nullチェックを追加 - 要素が存在しない場合の処理
- 安全なDOM操作
5. パフォーマンス最適化
- 不要なDOM検索を削減
- イベント委譲の活用
- 効率的な要素操作
document.addEventListener('DOMContentLoaded', function() {
// ===== 基本的なバリデーション =====
// 1. 必須項目チェック
const nameField = document.querySelector('input[name="your-name"]');
if (nameField) {
nameField.addEventListener('blur', function() {
if (this.value.trim() === '') {
this.classList.add('wpcf7-not-valid');
showError(this, '名前を入力してください');
} else {
this.classList.remove('wpcf7-not-valid');
hideError(this);
}
});
}
// 2. メールアドレス形式チェック
const emailField = document.querySelector('input[name="your-email"]');
if (emailField) {
emailField.addEventListener('blur', function() {
const email = this.value.trim();
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
this.classList.add('wpcf7-not-valid');
showError(this, '正しいメールアドレスを入力してください');
} else {
this.classList.remove('wpcf7-not-valid');
hideError(this);
}
});
}
// 3. 文字数制限チェック
if (nameField) {
nameField.addEventListener('input', function() {
const value = this.value;
const minLength = 2;
const maxLength = 50;
if (value.length < minLength) {
showError(this, `名前は${minLength}文字以上で入力してください`);
} else if (value.length > maxLength) {
showError(this, `名前は${maxLength}文字以下で入力してください`);
} else {
hideError(this);
}
});
}
// 4. 電話番号形式チェック
const telField = document.querySelector('input[name="your-tel"]');
if (telField) {
telField.addEventListener('input', function() {
const tel = this.value.replace(/[^\d-]/g, '');
const telRegex = /^0\d{1,4}-\d{1,4}-\d{4}$/;
if (tel && !telRegex.test(tel)) {
showError(this, '正しい電話番号を入力してください(例: 03-1234-5678)');
} else {
hideError(this);
}
});
}
// 5. 郵便番号チェック
const zipField = document.querySelector('input[name="your-zip"]');
if (zipField) {
zipField.addEventListener('input', function() {
const zip = this.value.replace(/[^\d-]/g, '');
const zipRegex = /^\d{3}-\d{4}$/;
if (zip && !zipRegex.test(zip)) {
showError(this, '郵便番号は123-4567の形式で入力してください');
} else {
hideError(this);
}
});
}
// ===== リアルタイムバリデーション =====
// 6. 入力時即座チェック
if (nameField) {
nameField.addEventListener('input', function() {
const value = this.value.trim();
if (value.length === 0) {
clearValidation(this);
} else if (value.length < 2) {
showError(this, '名前は2文字以上で入力してください');
} else if (!/^[ぁ-んァ-ヶー一-龠\s]+$/.test(value)) {
showError(this, '日本語で入力してください');
} else {
showSuccess(this, '正しく入力されています');
}
});
}
// 7. メールアドレスリアルタイム
if (emailField) {
emailField.addEventListener('input', function() {
const email = this.value.trim();
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (email.length === 0) {
clearValidation(this);
} else if (!emailRegex.test(email)) {
showError(this, '正しいメールアドレス形式ではありません');
} else {
showSuccess(this, '正しいメールアドレスです');
}
});
}
// 8. パスワード強度チェック
const passwordField = document.querySelector('input[name="your-password"]');
if (passwordField) {
passwordField.addEventListener('input', function() {
const password = this.value;
const strength = calculatePasswordStrength(password);
if (password.length === 0) {
clearValidation(this);
} else if (strength < 3) {
showError(this, 'パスワードが弱すぎます');
} else if (strength < 5) {
showWarning(this, 'パスワードの強度は普通です');
} else {
showSuccess(this, '強固なパスワードです');
}
});
}
// ===== もうちょっと踏み込んだバリデーション =====
// 9. 文字数カウント表示
const messageField = document.querySelector('textarea[name="your-message"]');
if (messageField) {
messageField.addEventListener('input', function() {
const value = this.value;
const maxLength = 500;
const currentLength = value.length;
// カウンター表示
let counter = this.nextElementSibling;
if (!counter || !counter.classList.contains('char-counter')) {
counter = document.createElement('div');
counter.className = 'char-counter';
this.parentNode.insertBefore(counter, this.nextSibling);
}
counter.textContent = `${currentLength}/${maxLength}`;
// 制限チェック
if (currentLength > maxLength) {
this.classList.add('wpcf7-not-valid');
showError(this, `文字数が上限を超えています(${currentLength}/${maxLength})`);
} else {
this.classList.remove('wpcf7-not-valid');
hideError(this);
}
});
}
// 10. 数値範囲チェック
const ageField = document.querySelector('input[name="your-age"]');
if (ageField) {
ageField.addEventListener('input', function() {
const value = parseInt(this.value);
const min = 18;
const max = 100;
if (isNaN(value)) {
showError(this, '数値を入力してください');
} else if (value < min || value > max) {
showError(this, `年齢は${min}歳から${max}歳の範囲で入力してください`);
} else {
showSuccess(this, '正しい年齢です');
}
});
}
// 11. 日付バリデーション
const dateField = document.querySelector('input[name="your-date"]');
if (dateField) {
dateField.addEventListener('change', function() {
const selectedDate = new Date(this.value);
const today = new Date();
const minDate = new Date();
minDate.setFullYear(today.getFullYear() - 1);
if (selectedDate < minDate) {
showError(this, '1年以内の日付を選択してください');
} else if (selectedDate > today) {
showError(this, '今日以前の日付を選択してください');
} else {
showSuccess(this, '正しい日付です');
}
});
}
// 12. ファイルバリデーション
const fileField = document.querySelector('input[name="your-file"]');
if (fileField) {
fileField.addEventListener('change', function() {
const file = this.files[0];
const maxSize = 5 * 1024 * 1024; // 5MB
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
if (!file) {
clearValidation(this);
return;
}
if (file.size > maxSize) {
showError(this, 'ファイルサイズは5MB以下にしてください');
} else if (!allowedTypes.includes(file.type)) {
showError(this, '許可されていないファイル形式です');
} else {
showSuccess(this, 'ファイルが正常に選択されました');
}
});
}
// ===== Contact Form 7 統合 =====
// 13. CF7送信前バリデーション
document.addEventListener('wpcf7beforesubmit', function(event) {
const form = event.target;
let isValid = true;
// カスタムバリデーション
const nameField = form.querySelector('input[name="your-name"]');
if (nameField && nameField.value.trim().length < 2) {
nameField.classList.add('wpcf7-not-valid');
isValid = false;
}
const emailField = form.querySelector('input[name="your-email"]');
if (emailField && !validateEmail(emailField.value)) {
emailField.classList.add('wpcf7-not-valid');
isValid = false;
}
if (!isValid) {
event.preventDefault();
}
}, false);
// 14. CF7送信後処理
document.addEventListener('wpcf7mailsent', function(event) {
// 送信成功時の処理
const form = event.target;
// フォームリセット
form.reset();
// 成功メッセージ表示
showSuccessMessage('メールが正常に送信されました!');
// Google Analytics イベント送信
if (typeof gtag !== 'undefined') {
gtag('event', 'form_submit', {
'event_category': 'contact',
'event_label': 'contact_form_7'
});
}
}, false);
// 15. CF7エラーハンドリング
document.addEventListener('wpcf7invalid', function(event) {
const form = event.target;
const invalidFields = form.querySelectorAll('.wpcf7-not-valid');
// 最初のエラーフィールドにフォーカス
if (invalidFields.length > 0) {
invalidFields[0].focus();
invalidFields[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
}
// カスタムエラーメッセージ
invalidFields.forEach(field => {
addCustomErrorMessage(field);
});
}, false);
// 16. リアルタイムエラー表示クリア
const formFields = document.querySelectorAll('.wpcf7-form input, .wpcf7-form textarea, .wpcf7-form select');
formFields.forEach(field => {
['input', 'change'].forEach(eventType => {
field.addEventListener(eventType, function() {
this.classList.remove('wpcf7-not-valid');
const errorTip = this.parentNode.querySelector('.wpcf7-not-valid-tip');
if (errorTip) {
errorTip.remove();
}
});
});
});
// ===== 複合バリデーション =====
// 17. 複数フィールド相互チェック
const emailConfirmField = document.querySelector('input[name="your-email-confirm"]');
if (emailField && emailConfirmField) {
[emailField, emailConfirmField].forEach(field => {
field.addEventListener('input', function() {
const email = emailField.value;
const emailConfirm = emailConfirmField.value;
if (email && emailConfirm && email !== emailConfirm) {
showError(emailConfirmField, 'メールアドレスが一致しません');
} else {
hideError(emailConfirmField);
}
});
});
}
// 18. 条件付きバリデーション
const inquiryTypeField = document.querySelector('select[name="your-inquiry-type"]');
const companyField = document.querySelector('input[name="your-company"]');
if (inquiryTypeField && companyField) {
inquiryTypeField.addEventListener('change', function() {
const inquiryType = this.value;
if (inquiryType === 'business') {
companyField.setAttribute('required', true);
const formGroup = companyField.closest('.form-group');
if (formGroup) {
formGroup.classList.add('required');
}
} else {
companyField.removeAttribute('required');
const formGroup = companyField.closest('.form-group');
if (formGroup) {
formGroup.classList.remove('required');
}
}
});
}
// 19. 動的フィールド追加時のバリデーション
const addFieldBtn = document.querySelector('.add-field-btn');
if (addFieldBtn) {
addFieldBtn.addEventListener('click', function() {
const newField = document.createElement('input');
newField.type = 'text';
newField.name = 'dynamic-field[]';
newField.className = 'dynamic-field';
// 新しく追加されたフィールドにバリデーションを適用
newField.addEventListener('input', function() {
if (this.value.trim() === '') {
showError(this, 'この項目は必須です');
} else {
hideError(this);
}
});
const container = document.querySelector('.dynamic-fields-container');
if (container) {
container.appendChild(newField);
}
});
}
// 20. 全フィールド一括バリデーション
const forms = document.querySelectorAll('.wpcf7-form');
forms.forEach(form => {
form.addEventListener('submit', function(e) {
let isValid = true;
// すべての必須フィールドをチェック
const requiredFields = form.querySelectorAll('[required]');
requiredFields.forEach(field => {
if (field.value.trim() === '') {
field.classList.add('wpcf7-not-valid');
showError(field, 'この項目は必須です');
isValid = false;
}
});
// メールアドレスフィールドをチェック
const emailFields = form.querySelectorAll('input[type="email"]');
emailFields.forEach(field => {
if (field.value && !validateEmail(field.value)) {
field.classList.add('wpcf7-not-valid');
showError(field, '正しいメールアドレスを入力してください');
isValid = false;
}
});
if (!isValid) {
e.preventDefault();
}
});
});
});
// ===== ヘルパー関数 =====
// エラーメッセージ表示
function showError(element, message) {
element.classList.add('wpcf7-not-valid');
// 既存のエラーメッセージを削除
const existingError = element.parentNode.querySelector('.error-message');
if (existingError) {
existingError.remove();
}
// 新しいエラーメッセージを作成
const errorSpan = document.createElement('span');
errorSpan.className = 'error-message';
errorSpan.style.color = 'red';
errorSpan.style.fontSize = '12px';
errorSpan.textContent = message;
element.parentNode.insertBefore(errorSpan, element.nextSibling);
}
// 成功メッセージ表示
function showSuccess(element, message) {
element.classList.remove('wpcf7-not-valid');
element.classList.add('valid');
// 既存の成功メッセージを削除
const existingSuccess = element.parentNode.querySelector('.success-message');
if (existingSuccess) {
existingSuccess.remove();
}
// 新しい成功メッセージを作成
const successSpan = document.createElement('span');
successSpan.className = 'success-message';
successSpan.style.color = 'green';
successSpan.style.fontSize = '12px';
successSpan.textContent = message;
element.parentNode.insertBefore(successSpan, element.nextSibling);
}
// 警告メッセージ表示
function showWarning(element, message) {
element.classList.remove('wpcf7-not-valid');
element.classList.add('warning');
// 既存の警告メッセージを削除
const existingWarning = element.parentNode.querySelector('.warning-message');
if (existingWarning) {
existingWarning.remove();
}
// 新しい警告メッセージを作成
const warningSpan = document.createElement('span');
warningSpan.className = 'warning-message';
warningSpan.style.color = 'orange';
warningSpan.style.fontSize = '12px';
warningSpan.textContent = message;
element.parentNode.insertBefore(warningSpan, element.nextSibling);
}
// エラーメッセージ非表示
function hideError(element) {
element.classList.remove('wpcf7-not-valid');
const errorMessage = element.parentNode.querySelector('.error-message');
if (errorMessage) {
errorMessage.remove();
}
}
// バリデーション状態クリア
function clearValidation(element) {
element.classList.remove('wpcf7-not-valid', 'valid', 'warning');
const messages = element.parentNode.querySelectorAll('.error-message, .success-message, .warning-message');
messages.forEach(msg => msg.remove());
}
// メールアドレス検証
function validateEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
// パスワード強度計算
function calculatePasswordStrength(password) {
let strength = 0;
if (password.length >= 8) strength++;
if (password.length >= 12) strength++;
if (/[a-z]/.test(password)) strength++;
if (/[A-Z]/.test(password)) strength++;
if (/[0-9]/.test(password)) strength++;
if (/[^A-Za-z0-9]/.test(password)) strength++;
return strength;
}
// 全角・半角変換
function toHalfWidth(str) {
return str.replace(/[A-Za-z0-9]/g, function(s) {
return String.fromCharCode(s.charCodeAt(0) - 0xFEE0);
});
}
// ひらがな・カタカナ変換
function toKatakana(str) {
return str.replace(/[ぁ-ゖ]/g, function(match) {
const chr = match.charCodeAt(0) + 0x60;
return String.fromCharCode(chr);
});
}
// 数値のみ入力制限
function numbersOnly(element) {
element.addEventListener('input', function() {
this.value = this.value.replace(/[^0-9]/g, '');
});
}
// 日本語のみ入力制限
function japaneseOnly(element) {
element.addEventListener('input', function() {
const value = this.value;
const japaneseRegex = /^[ぁ-んァ-ヶー一-龠\s]*$/;
if (!japaneseRegex.test(value)) {
this.value = value.replace(/[^ぁ-んァ-ヶー一-龠\s]/g, '');
}
});
}
// 英数字のみ入力制限
function alphanumericOnly(element) {
element.addEventListener('input', function() {
this.value = this.value.replace(/[^a-zA-Z0-9]/g, '');
});
}
// フォーム送信前の最終チェック
function finalValidation(form) {
let isValid = true;
const errors = [];
// 必須項目チェック
const requiredFields = form.querySelectorAll('[required]');
requiredFields.forEach(field => {
if (field.value.trim() === '') {
errors.push(field.getAttribute('name') + 'は必須項目です');
isValid = false;
}
});
// メールアドレスチェック
const emailFields = form.querySelectorAll('input[type="email"]');
emailFields.forEach(field => {
if (field.value && !validateEmail(field.value)) {
errors.push('正しいメールアドレスを入力してください');
isValid = false;
}
});
if (!isValid) {
alert('以下のエラーを修正してください:\n' + errors.join('\n'));
}
return isValid;
}
// 成功メッセージ表示
function showSuccessMessage(message) {
const successDiv = document.createElement('div');
successDiv.className = 'success-notification';
successDiv.textContent = message;
successDiv.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #4CAF50;
color: white;
padding: 15px;
border-radius: 5px;
z-index: 9999;
opacity: 1;
transition: opacity 0.3s ease;
`;
document.body.appendChild(successDiv);
setTimeout(() => {
successDiv.style.opacity = '0';
setTimeout(() => {
if (successDiv.parentNode) {
successDiv.parentNode.removeChild(successDiv);
}
}, 300);
}, 3000);
}
// カスタムエラーメッセージ追加
function addCustomErrorMessage(field) {
const fieldName = field.getAttribute('name');
let customMessage = '';
switch(fieldName) {
case 'your-name':
customMessage = 'お名前を正しく入力してください';
break;
case 'your-email':
customMessage = 'メールアドレスの形式が正しくありません';
break;
case 'your-tel':
customMessage = '電話番号を正しく入力してください';
break;
default:
customMessage = 'この項目を正しく入力してください';
}
// 既存のエラーメッセージを削除
const existingCustomError = field.parentNode.querySelector('.custom-error');
if (existingCustomError) {
existingCustomError.remove();
}
// カスタムエラーメッセージを追加
const customErrorSpan = document.createElement('span');
customErrorSpan.className = 'custom-error';
customErrorSpan.style.cssText = 'color: red; font-size: 12px; display: block;';
customErrorSpan.textContent = customMessage;
field.parentNode.insertBefore(customErrorSpan, field.nextSibling);
}