onchangeイベントとは?JavaScriptでフォーム変更を検知する基礎

JavaScript onchangeイベントの基本的な使い方から実践的な活用方法まで詳しく解説。フォーム要素の値変更検知、動的フォーム作成、リアルタイム処理の実装方法を初心者向けに分かりやすく説明します。

Learning Next 運営
60 分で読めます

onchangeイベントとは?JavaScriptでフォーム変更を検知する基礎

みなさん、Webページでフォームに入力したとき、自動的に何かが変わったりする体験をしたことありませんか?

「プルダウンメニューを変更したら、下の項目が自動で更新された」 「チェックボックスをオンにしたら、追加の入力欄が表示された」 「テキストを入力したら、リアルタイムで計算結果が変わった」

こんな便利な機能に遭遇することがあるかもしれませんね。

JavaScriptのonchangeイベントは、フォーム要素の値が変更されたときに自動的に処理を実行するための強力な仕組みです。 この記事では、onchangeイベントについて基本的な使い方から実践的な活用方法まで詳しく解説します。

フォーム変更の検知、動的なWebページの作成、リアルタイム処理の実装方法を、実際のコード例を交えて初心者向けに分かりやすく説明していきます。

onchangeイベントって何だろう?

簡単に言うとどんなもの?

onchangeイベントは、フォーム要素の値が変更されたときに発生するイベントです。

簡単に言うと、ユーザーが入力フィールドやプルダウンメニューの値を変更したときに「値が変わったよ!」と教えてくれる機能なんです。 この変化を検知して、自動的に何かの処理を実行できるようになります。

// onchangeイベントの基本的な仕組み
element.addEventListener('change', function(event) {
console.log('値が変更されました:', event.target.value);
});

このコードで、要素の値が変わったときに自動的に処理が実行されます。

いつ発生するの?

onchangeイベントが発生するタイミングを理解することが大切です。

要素の種類によって、発生するタイミングが少し違います。

<!-- テキスト入力:入力後にフォーカスが外れたとき -->
<input type="text" onchange="textChanged()">
<!-- プルダウン:選択肢を変更したとき -->
<select onchange="selectChanged()">
<option value="option1">選択肢1</option>
<option value="option2">選択肢2</option>
</select>
<!-- チェックボックス:チェック状態を変更したとき -->
<input type="checkbox" onchange="checkboxChanged()">
<!-- ラジオボタン:選択状態を変更したとき -->
<input type="radio" name="radio" onchange="radioChanged()">

それぞれの特徴を覚えておくと、適切に使い分けできます。

どんな場面で役立つの?

onchangeイベントは、様々な場面で活用できます。

実際の使用例を見てみましょう。

// 使用例1: フォーム入力のリアルタイム検証
function validateEmail(input) {
let email = input.value;
let isValid = email.includes('@') && email.includes('.');
if (isValid) {
input.style.borderColor = 'green';
showMessage('正しいメールアドレスです', 'success');
} else {
input.style.borderColor = 'red';
showMessage('正しいメールアドレスを入力してください', 'error');
}
}
// 使用例2: 動的な価格計算
function calculateTotal() {
let price = document.getElementById('price').value;
let quantity = document.getElementById('quantity').value;
let total = price * quantity;
document.getElementById('total').textContent = `合計: ${total}`;
}

フォームの使いやすさが格段に向上します。

基本的な使い方をマスターしよう

HTMLで直接指定する方法

最もシンプルな方法は、HTML要素に直接onchange属性を書くことです。

<!DOCTYPE html>
<html>
<head>
<title>onchangeの基本例</title>
</head>
<body>
<h2>基本的なonchangeの使い方</h2>
<!-- テキスト入力の例 -->
<label for="username">ユーザー名:</label>
<input type="text" id="username" onchange="handleNameChange(this)">
<p id="nameMessage">ユーザー名を入力してください</p>
<!-- プルダウンの例 -->
<label for="color">好きな色:</label>
<select id="color" onchange="handleColorChange(this)">
<option value="">選択してください</option>
<option value="red"></option>
<option value="blue"></option>
<option value="green"></option>
</select>
<p id="colorMessage">色を選択してください</p>
</body>
</html>

このHTMLに対応するJavaScriptも見てみましょう。

// ユーザー名変更時の処理
function handleNameChange(element) {
let username = element.value;
let message = document.getElementById('nameMessage');
if (username.trim() === '') {
message.textContent = 'ユーザー名を入力してください';
message.style.color = 'gray';
} else if (username.length < 3) {
message.textContent = '3文字以上で入力してください';
message.style.color = 'red';
} else {
message.textContent = `こんにちは、${username}さん!`;
message.style.color = 'green';
}
}
// 色変更時の処理
function handleColorChange(element) {
let color = element.value;
let message = document.getElementById('colorMessage');
if (color === '') {
message.textContent = '色を選択してください';
message.style.color = 'gray';
document.body.style.backgroundColor = 'white';
} else {
message.textContent = `${getColorName(color)}が選択されました`;
message.style.color = color;
document.body.style.backgroundColor = getColorCode(color);
}
}
// 色名を取得する補助関数
function getColorName(value) {
const colorNames = {
'red': '赤',
'blue': '青',
'green': '緑'
};
return colorNames[value] || value;
}
// 色コードを取得する補助関数
function getColorCode(value) {
const colorCodes = {
'red': '#ffebee',
'blue': '#e3f2fd',
'green': '#e8f5e8'
};
return colorCodes[value] || '#ffffff';
}

このように、直接HTMLに書く方法は理解しやすいです。

JavaScriptでイベントリスナーを登録する方法

より柔軟で推奨される方法は、JavaScriptでイベントリスナーを登録することです。

// DOMが読み込まれてから実行
document.addEventListener('DOMContentLoaded', function() {
// 要素を取得
let nameInput = document.getElementById('username');
let emailInput = document.getElementById('email');
let categorySelect = document.getElementById('category');
let agreeCheckbox = document.getElementById('agree');
// それぞれにonchangeイベントを設定
nameInput.addEventListener('change', function(event) {
console.log('名前が変更されました:', event.target.value);
validateName(event.target.value);
});
emailInput.addEventListener('change', function(event) {
console.log('メールが変更されました:', event.target.value);
validateEmail(event.target.value);
});
categorySelect.addEventListener('change', function(event) {
console.log('カテゴリが変更されました:', event.target.value);
updateSubOptions(event.target.value);
});
agreeCheckbox.addEventListener('change', function(event) {
console.log('同意状態が変更されました:', event.target.checked);
toggleSubmitButton(event.target.checked);
});
});

各処理の詳細も実装してみましょう。

// 名前の検証
function validateName(name) {
let errorElement = document.getElementById('nameError');
if (name.length < 2) {
showError(errorElement, '名前は2文字以上で入力してください');
return false;
} else if (name.length > 20) {
showError(errorElement, '名前は20文字以内で入力してください');
return false;
} else {
hideError(errorElement);
return true;
}
}
// メールアドレスの検証
function validateEmail(email) {
let errorElement = document.getElementById('emailError');
let emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailPattern.test(email)) {
showError(errorElement, '正しいメールアドレスを入力してください');
return false;
} else {
hideError(errorElement);
return true;
}
}
// サブオプションの更新
function updateSubOptions(category) {
let subSelect = document.getElementById('subcategory');
// 既存の選択肢をクリア
subSelect.innerHTML = '<option value="">選択してください</option>';
// カテゴリに応じてオプションを追加
let options = getSubOptions(category);
options.forEach(option => {
let optionElement = document.createElement('option');
optionElement.value = option.value;
optionElement.textContent = option.text;
subSelect.appendChild(optionElement);
});
}
// サブオプションのデータを取得
function getSubOptions(category) {
const optionsMap = {
'programming': [
{ value: 'web', text: 'Web開発' },
{ value: 'mobile', text: 'モバイル開発' },
{ value: 'ai', text: 'AI・機械学習' }
],
'design': [
{ value: 'ui', text: 'UI/UX' },
{ value: 'graphic', text: 'グラフィック' },
{ value: 'web-design', text: 'Webデザイン' }
],
'business': [
{ value: 'marketing', text: 'マーケティング' },
{ value: 'sales', text: '営業' },
{ value: 'management', text: '経営' }
]
};
return optionsMap[category] || [];
}
// 送信ボタンの有効/無効切り替え
function toggleSubmitButton(isChecked) {
let submitButton = document.getElementById('submitButton');
submitButton.disabled = !isChecked;
if (isChecked) {
submitButton.textContent = '送信する';
submitButton.style.backgroundColor = '#007bff';
submitButton.style.cursor = 'pointer';
} else {
submitButton.textContent = '利用規約に同意してください';
submitButton.style.backgroundColor = '#6c757d';
submitButton.style.cursor = 'not-allowed';
}
}
// エラー表示の補助関数
function showError(element, message) {
element.textContent = message;
element.style.display = 'block';
element.style.color = 'red';
}
function hideError(element) {
element.style.display = 'none';
}

この方法なら、より柔軟で保守しやすいコードが書けます。

動的フォームを作ってみよう

条件に応じてフィールドを表示する

ユーザーの選択に応じて、フォームの内容を動的に変更する機能を作ってみましょう。

<div class="dynamic-form">
<h3>お問い合わせフォーム</h3>
<label for="inquiryType">お問い合わせ種類:</label>
<select id="inquiryType">
<option value="">選択してください</option>
<option value="product">製品について</option>
<option value="support">サポート</option>
<option value="sales">営業相談</option>
<option value="other">その他</option>
</select>
<div id="dynamicSection"></div>
<div id="submitSection" style="display: none;">
<button type="submit" id="submitBtn">送信する</button>
</div>
</div>

対応するJavaScriptを実装してみます。

document.addEventListener('DOMContentLoaded', function() {
let inquirySelect = document.getElementById('inquiryType');
let dynamicSection = document.getElementById('dynamicSection');
let submitSection = document.getElementById('submitSection');
inquirySelect.addEventListener('change', function(event) {
let selectedType = event.target.value;
// 既存の動的フィールドをクリア
dynamicSection.innerHTML = '';
if (selectedType === '') {
submitSection.style.display = 'none';
return;
}
// 共通フィールドを追加
addCommonFields(dynamicSection);
// 種類に応じて専用フィールドを追加
addSpecificFields(dynamicSection, selectedType);
// 送信セクションを表示
submitSection.style.display = 'block';
// 新しく追加されたフィールドにイベントを設定
attachValidation();
});
});
// 共通フィールドを追加
function addCommonFields(container) {
let commonHTML = `
<div class="field-group">
<label for="customerName">お名前 *</label>
<input type="text" id="customerName" required>
<div class="error-message" id="customerNameError"></div>
</div>
<div class="field-group">
<label for="customerEmail">メールアドレス *</label>
<input type="email" id="customerEmail" required>
<div class="error-message" id="customerEmailError"></div>
</div>
`;
container.innerHTML += commonHTML;
}
// 種類別の専用フィールドを追加
function addSpecificFields(container, type) {
let specificHTML = '';
switch (type) {
case 'product':
specificHTML = `
<div class="field-group">
<label for="productName">製品名</label>
<select id="productName">
<option value="">選択してください</option>
<option value="basic">ベーシックプラン</option>
<option value="premium">プレミアムプラン</option>
<option value="enterprise">エンタープライズプラン</option>
</select>
</div>
<div class="field-group">
<label for="productQuestion">ご質問内容</label>
<textarea id="productQuestion" rows="4"
placeholder="製品についてのご質問をお書きください"></textarea>
</div>
`;
break;
case 'support':
specificHTML = `
<div class="field-group">
<label for="supportType">サポート種類</label>
<select id="supportType">
<option value="">選択してください</option>
<option value="technical">技術的な問題</option>
<option value="billing">請求に関する問題</option>
<option value="account">アカウントの問題</option>
</select>
</div>
<div class="field-group">
<label for="urgency">緊急度</label>
<select id="urgency">
<option value="low">低</option>
<option value="medium">中</option>
<option value="high">高</option>
<option value="critical">緊急</option>
</select>
</div>
<div class="field-group">
<label for="issueDescription">問題の詳細</label>
<textarea id="issueDescription" rows="5"
placeholder="問題の詳細をお書きください"></textarea>
</div>
`;
break;
case 'sales':
specificHTML = `
<div class="field-group">
<label for="companyName">会社名</label>
<input type="text" id="companyName">
</div>
<div class="field-group">
<label for="budget">ご予算</label>
<select id="budget">
<option value="">選択してください</option>
<option value="under_100k">10万円未満</option>
<option value="100k_500k">10万円〜50万円</option>
<option value="500k_1m">50万円〜100万円</option>
<option value="over_1m">100万円以上</option>
</select>
</div>
<div class="field-group">
<label for="timeline">導入予定時期</label>
<select id="timeline">
<option value="">選択してください</option>
<option value="immediate">すぐに</option>
<option value="1month">1ヶ月以内</option>
<option value="3months">3ヶ月以内</option>
<option value="6months">6ヶ月以内</option>
<option value="undecided">未定</option>
</select>
</div>
`;
break;
case 'other':
specificHTML = `
<div class="field-group">
<label for="subject">件名</label>
<input type="text" id="subject" placeholder="お問い合わせの件名">
</div>
<div class="field-group">
<label for="message">メッセージ</label>
<textarea id="message" rows="6"
placeholder="お問い合わせ内容をお書きください"></textarea>
</div>
`;
break;
}
container.innerHTML += specificHTML;
}
// 検証機能を追加
function attachValidation() {
// 必須フィールドの検証
let requiredFields = document.querySelectorAll('input[required]');
requiredFields.forEach(field => {
field.addEventListener('change', function(event) {
validateField(event.target);
checkFormCompletion();
});
field.addEventListener('blur', function(event) {
validateField(event.target);
});
});
// メールアドレスの特別検証
let emailField = document.getElementById('customerEmail');
if (emailField) {
emailField.addEventListener('change', function(event) {
validateEmailField(event.target);
});
}
}
// フィールドの検証
function validateField(field) {
let errorElement = document.getElementById(field.id + 'Error');
if (field.hasAttribute('required') && field.value.trim() === '') {
showFieldError(errorElement, 'この項目は必須です');
return false;
} else if (field.type === 'email' && !isValidEmail(field.value)) {
showFieldError(errorElement, '正しいメールアドレスを入力してください');
return false;
} else {
hideFieldError(errorElement);
return true;
}
}
// メールアドレスの検証
function validateEmailField(field) {
let errorElement = document.getElementById(field.id + 'Error');
if (field.value.trim() === '') {
showFieldError(errorElement, 'メールアドレスは必須です');
return false;
} else if (!isValidEmail(field.value)) {
showFieldError(errorElement, '正しいメールアドレスを入力してください');
return false;
} else {
hideFieldError(errorElement);
return true;
}
}
// メールアドレスの形式チェック
function isValidEmail(email) {
let emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailPattern.test(email);
}
// フォーム完成度のチェック
function checkFormCompletion() {
let requiredFields = document.querySelectorAll('input[required]');
let submitButton = document.getElementById('submitBtn');
let allValid = true;
requiredFields.forEach(field => {
if (!validateField(field)) {
allValid = false;
}
});
// 送信ボタンの状態を更新
submitButton.disabled = !allValid;
submitButton.style.opacity = allValid ? '1' : '0.5';
submitButton.style.cursor = allValid ? 'pointer' : 'not-allowed';
}
// エラー表示の補助関数
function showFieldError(errorElement, message) {
errorElement.textContent = message;
errorElement.style.display = 'block';
}
function hideFieldError(errorElement) {
errorElement.style.display = 'none';
}

このように、複雑な動的フォームも作れるようになります。

リアルタイム計算機能

入力値の変更に応じて、リアルタイムで計算結果を表示する機能も作ってみましょう。

<div class="calculator-form">
<h3>料金計算フォーム</h3>
<div class="input-section">
<label for="basePrice">基本料金:</label>
<input type="number" id="basePrice" min="0" step="0.01" value="0">
<label for="quantity">数量:</label>
<input type="number" id="quantity" min="1" value="1">
<label for="discountRate">割引率:</label>
<input type="number" id="discountRate" min="0" max="100" step="0.1" value="0">%
<label for="shippingOption">配送オプション:</label>
<select id="shippingOption">
<option value="standard">通常配送 (無料)</option>
<option value="express">速達配送 (+500円)</option>
<option value="overnight">翌日配送 (+1000円)</option>
</select>
</div>
<div class="result-section">
<h4>計算結果</h4>
<div id="subtotal" class="result-item">小計: 0円</div>
<div id="discount" class="result-item">割引: -0円</div>
<div id="shipping" class="result-item">配送料: 0円</div>
<div id="tax" class="result-item">消費税 (10%): 0円</div>
<div id="total" class="result-total">合計: 0円</div>
</div>
</div>

計算ロジックを実装します。

class PriceCalculator {
constructor() {
this.taxRate = 0.10; // 消費税率10%
this.shippingRates = {
'standard': 0,
'express': 500,
'overnight': 1000
};
this.initializeElements();
this.attachEventListeners();
this.calculate(); // 初期計算
}
// 要素の初期化
initializeElements() {
this.basePriceInput = document.getElementById('basePrice');
this.quantityInput = document.getElementById('quantity');
this.discountRateInput = document.getElementById('discountRate');
this.shippingSelect = document.getElementById('shippingOption');
this.subtotalDisplay = document.getElementById('subtotal');
this.discountDisplay = document.getElementById('discount');
this.shippingDisplay = document.getElementById('shipping');
this.taxDisplay = document.getElementById('tax');
this.totalDisplay = document.getElementById('total');
}
// イベントリスナーの設定
attachEventListeners() {
// すべての入力要素にchangeとinputイベントを設定
[this.basePriceInput, this.quantityInput, this.discountRateInput].forEach(input => {
input.addEventListener('change', () => this.calculate());
input.addEventListener('input', () => this.calculate());
});
this.shippingSelect.addEventListener('change', () => this.calculate());
}
// メイン計算処理
calculate() {
// 入力値を取得
let basePrice = parseFloat(this.basePriceInput.value) || 0;
let quantity = parseInt(this.quantityInput.value) || 1;
let discountRate = parseFloat(this.discountRateInput.value) || 0;
let shippingOption = this.shippingSelect.value;
// 計算実行
let subtotal = this.calculateSubtotal(basePrice, quantity);
let discountAmount = this.calculateDiscount(subtotal, discountRate);
let discountedPrice = subtotal - discountAmount;
let shippingCost = this.getShippingCost(shippingOption);
let taxableAmount = discountedPrice + shippingCost;
let taxAmount = this.calculateTax(taxableAmount);
let total = taxableAmount + taxAmount;
// 表示を更新
this.updateDisplay(subtotal, discountAmount, shippingCost, taxAmount, total);
// 入力値の検証とフィードバック
this.validateInputs(basePrice, quantity, discountRate);
}
// 小計の計算
calculateSubtotal(basePrice, quantity) {
return basePrice * quantity;
}
// 割引額の計算
calculateDiscount(subtotal, discountRate) {
return subtotal * (discountRate / 100);
}
// 配送料の取得
getShippingCost(shippingOption) {
return this.shippingRates[shippingOption] || 0;
}
// 消費税の計算
calculateTax(amount) {
return amount * this.taxRate;
}
// 表示の更新
updateDisplay(subtotal, discount, shipping, tax, total) {
this.subtotalDisplay.textContent = `小計: ${this.formatCurrency(subtotal)}`;
this.discountDisplay.textContent = `割引: -${this.formatCurrency(discount)}`;
this.shippingDisplay.textContent = `配送料: ${this.formatCurrency(shipping)}`;
this.taxDisplay.textContent = `消費税 (10%): ${this.formatCurrency(tax)}`;
this.totalDisplay.textContent = `合計: ${this.formatCurrency(total)}`;
// 割引額に応じて色を変更
if (discount > 0) {
this.discountDisplay.style.color = 'red';
this.discountDisplay.style.fontWeight = 'bold';
} else {
this.discountDisplay.style.color = 'black';
this.discountDisplay.style.fontWeight = 'normal';
}
}
// 通貨フォーマット
formatCurrency(amount) {
return Math.round(amount).toLocaleString() + '円';
}
// 入力値の検証
validateInputs(basePrice, quantity, discountRate) {
// 基本料金の検証
if (basePrice < 0) {
this.showInputError(this.basePriceInput, '基本料金は0以上で入力してください');
} else {
this.clearInputError(this.basePriceInput);
}
// 数量の検証
if (quantity < 1) {
this.showInputError(this.quantityInput, '数量は1以上で入力してください');
} else if (quantity > 100) {
this.showInputError(this.quantityInput, '数量は100以下で入力してください');
} else {
this.clearInputError(this.quantityInput);
}
// 割引率の検証
if (discountRate < 0) {
this.showInputError(this.discountRateInput, '割引率は0以上で入力してください');
} else if (discountRate > 100) {
this.showInputError(this.discountRateInput, '割引率は100以下で入力してください');
} else {
this.clearInputError(this.discountRateInput);
}
}
// 入力エラーの表示
showInputError(input, message) {
input.style.borderColor = 'red';
let errorId = input.id + 'Error';
let errorElement = document.getElementById(errorId);
if (!errorElement) {
errorElement = document.createElement('div');
errorElement.id = errorId;
errorElement.className = 'error-message';
errorElement.style.color = 'red';
errorElement.style.fontSize = '0.8em';
input.parentNode.insertBefore(errorElement, input.nextSibling);
}
errorElement.textContent = message;
}
// 入力エラーの消去
clearInputError(input) {
input.style.borderColor = '';
let errorId = input.id + 'Error';
let errorElement = document.getElementById(errorId);
if (errorElement) {
errorElement.remove();
}
}
}
// ページ読み込み後に計算機を初期化
document.addEventListener('DOMContentLoaded', function() {
new PriceCalculator();
});

これで、リアルタイムで計算が更新される便利なフォームが完成します。

inputイベントとの違いを理解しよう

発生タイミングの違い

onchangeとoninputには、重要な違いがあります。

<div class="event-comparison">
<h3>onchangeとoninputの比較</h3>
<div class="demo-section">
<label for="changeDemo">onChange(フォーカスが外れたときに発火):</label>
<input type="text" id="changeDemo" placeholder="文字を入力して他をクリック">
<div id="changeResult">まだ変更されていません</div>
</div>
<div class="demo-section">
<label for="inputDemo">onInput(文字入力のたびに発火):</label>
<input type="text" id="inputDemo" placeholder="リアルタイムで反応">
<div id="inputResult">まだ入力されていません</div>
</div>
<div class="demo-section">
<label for="countDemo">両方のイベントカウント:</label>
<input type="text" id="countDemo" placeholder="入力してカウントを確認">
<div id="countResult">
input回数: <span id="inputCount">0</span> /
change回数: <span id="changeCount">0</span>
</div>
</div>
</div>

対応するJavaScriptです。

document.addEventListener('DOMContentLoaded', function() {
// onChangeのデモ
let changeDemo = document.getElementById('changeDemo');
let changeResult = document.getElementById('changeResult');
changeDemo.addEventListener('change', function(event) {
let value = event.target.value;
changeResult.textContent = `onChange発火: "${value}"`;
changeResult.style.color = 'blue';
// 一時的にハイライト
changeResult.style.backgroundColor = 'lightblue';
setTimeout(() => {
changeResult.style.backgroundColor = '';
}, 300);
});
// onInputのデモ
let inputDemo = document.getElementById('inputDemo');
let inputResult = document.getElementById('inputResult');
inputDemo.addEventListener('input', function(event) {
let value = event.target.value;
inputResult.textContent = `onInput発火: "${value}"`;
inputResult.style.color = 'green';
// 文字数も表示
if (value.length > 0) {
inputResult.textContent += ` (${value.length}文字)`;
}
});
// カウントのデモ
let countDemo = document.getElementById('countDemo');
let inputCountSpan = document.getElementById('inputCount');
let changeCountSpan = document.getElementById('changeCount');
let inputCounter = 0;
let changeCounter = 0;
countDemo.addEventListener('input', function(event) {
inputCounter++;
inputCountSpan.textContent = inputCounter;
inputCountSpan.style.fontWeight = 'bold';
// 一時的にハイライト
setTimeout(() => {
inputCountSpan.style.fontWeight = 'normal';
}, 200);
});
countDemo.addEventListener('change', function(event) {
changeCounter++;
changeCountSpan.textContent = changeCounter;
changeCountSpan.style.fontWeight = 'bold';
// 一時的にハイライト
setTimeout(() => {
changeCountSpan.style.fontWeight = 'normal';
}, 200);
});
});

使い分けのポイント

どちらのイベントを使うかは、目的によって決まります。

// 使い分けの例
class FormValidator {
constructor() {
this.initializeValidation();
}
initializeValidation() {
// パスワード強度はリアルタイムでチェック(input)
let passwordField = document.getElementById('password');
passwordField.addEventListener('input', (e) => {
this.checkPasswordStrength(e.target.value);
});
// ユーザー名の重複チェックは入力完了後(change)
let usernameField = document.getElementById('username');
usernameField.addEventListener('change', (e) => {
this.checkUsernameAvailability(e.target.value);
});
// メールアドレスの形式チェックは入力完了後(change)
let emailField = document.getElementById('email');
emailField.addEventListener('change', (e) => {
this.validateEmailFormat(e.target.value);
});
// 検索ボックスはリアルタイムで検索(input + デバウンス)
let searchField = document.getElementById('search');
this.setupSearch(searchField);
}
// パスワード強度のリアルタイムチェック
checkPasswordStrength(password) {
let strengthDisplay = document.getElementById('passwordStrength');
let strength = this.calculatePasswordStrength(password);
strengthDisplay.textContent = `強度: ${strength.label}`;
strengthDisplay.className = `strength-${strength.level}`;
}
// パスワード強度の計算
calculatePasswordStrength(password) {
if (password.length === 0) {
return { level: 'none', label: '-' };
}
let score = 0;
// 長さによるスコア
if (password.length >= 8) score += 1;
if (password.length >= 12) score += 1;
// 文字種によるスコア
if (/[a-z]/.test(password)) score += 1;
if (/[A-Z]/.test(password)) score += 1;
if (/[0-9]/.test(password)) score += 1;
if (/[^a-zA-Z0-9]/.test(password)) score += 1;
// スコアに応じた評価
if (score <= 2) return { level: 'weak', label: '弱い' };
if (score <= 4) return { level: 'medium', label: '普通' };
return { level: 'strong', label: '強い' };
}
// ユーザー名の重複チェック(APIを想定)
async checkUsernameAvailability(username) {
if (username.length < 3) return;
let statusDisplay = document.getElementById('usernameStatus');
statusDisplay.textContent = 'チェック中...';
statusDisplay.className = 'checking';
try {
// 実際のAPIコール(例)
// let response = await fetch(`/api/check-username/${username}`);
// let result = await response.json();
// ここではダミーのチェック
await this.delay(1000); // 1秒待機
let isAvailable = !['admin', 'user', 'test'].includes(username.toLowerCase());
if (isAvailable) {
statusDisplay.textContent = '✓ 利用可能';
statusDisplay.className = 'available';
} else {
statusDisplay.textContent = '✗ 既に使用されています';
statusDisplay.className = 'unavailable';
}
} catch (error) {
statusDisplay.textContent = 'チェックに失敗しました';
statusDisplay.className = 'error';
}
}
// 検索機能(デバウンス付き)
setupSearch(searchField) {
let searchTimeout;
searchField.addEventListener('input', (e) => {
let query = e.target.value;
// 前回のタイマーをクリア
clearTimeout(searchTimeout);
// 300ms後に検索実行
searchTimeout = setTimeout(() => {
this.performSearch(query);
}, 300);
});
}
// 検索の実行
performSearch(query) {
let resultsDisplay = document.getElementById('searchResults');
if (query.length === 0) {
resultsDisplay.innerHTML = '';
return;
}
if (query.length < 2) {
resultsDisplay.innerHTML = '<div class="search-message">2文字以上で検索してください</div>';
return;
}
// ダミーの検索結果
let results = this.getSearchResults(query);
this.displaySearchResults(results);
}
// ダミーの検索結果取得
getSearchResults(query) {
let allItems = [
'JavaScript基礎', 'HTML入門', 'CSS設計',
'React開発', 'Vue.js実践', 'Node.js活用',
'データベース設計', 'API開発', 'セキュリティ対策'
];
return allItems.filter(item =>
item.toLowerCase().includes(query.toLowerCase())
);
}
// 検索結果の表示
displaySearchResults(results) {
let resultsDisplay = document.getElementById('searchResults');
if (results.length === 0) {
resultsDisplay.innerHTML = '<div class="search-message">該当する結果がありません</div>';
return;
}
let resultsHTML = results.map(result =>
`<div class="search-result">${result}</div>`
).join('');
resultsDisplay.innerHTML = resultsHTML;
}
// 待機用のユーティリティ関数
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// ページ読み込み後に初期化
document.addEventListener('DOMContentLoaded', function() {
new FormValidator();
});

このように、用途に応じて適切なイベントを選ぶことが大切です。

まとめ

JavaScriptのonchangeイベントについて詳しく解説しました。

重要なポイント

  • 基本機能: フォーム要素の値変更を検知して処理を実行
  • 発生タイミング: 要素の種類によって異なる(テキスト入力はフォーカスアウト時など)
  • 実装方法: HTML直接指定とJavaScriptイベントリスナー登録の2つ
  • inputとの違い: changeは変更完了時、inputはリアルタイム

実践的な活用

  • 動的フォーム: 選択に応じてフィールドを表示・非表示
  • リアルタイム計算: 入力値に基づく即座の計算・表示
  • フォーム検証: 入力内容の自動チェックとフィードバック
  • 条件分岐: ユーザーの選択に応じた処理の切り替え

onchangeイベントを適切に活用することで、より使いやすく動的なWebフォームが作れるようになります。

まずは基本的な値変更の検知から始めて、だんだんと複雑な動的フォームにも挑戦してみましょう。 きっと、もっと便利で使いやすいWebアプリケーションが作れるようになりますよ。

ぜひ今日から、これらの知識を活用してインタラクティブなフォーム機能を実装してみませんか?

関連記事