「ボタンをクリックしたら何か動作させたい!」
「キーボードで入力したら、リアルタイムに反応するページを作りたい」
Webページをインタラクティブにするには、イベント処理の理解が欠かせません。
ユーザーのクリック、マウスの動き、キーボード入力など、あらゆる操作に反応できるのがJavaScriptの魅力です。でも、イベントの仕組みって意外と奥が深くて、最初は戸惑うこともありますよね。
この記事では、JavaScriptのイベント処理について、基礎から実践的なテクニックまで徹底的に解説していきます。実際に使えるコード例も豊富に用意していますよ!
イベントとは?基本を理解しよう
イベントの概念
イベントとは、Webページ上で発生する「出来事」のことです。
代表的なイベント:
- ユーザーがボタンをクリックした
- マウスを要素の上に乗せた
- キーボードでテキストを入力した
- ページの読み込みが完了した
- フォームを送信した
これらの出来事に対して、JavaScriptで反応する処理を書くことができます。
イベント駆動プログラミング
JavaScriptはイベント駆動型の言語です。
従来のプログラム:
処理1 → 処理2 → 処理3 → 終了
イベント駆動型:
待機中...
↓ ユーザーがクリック
→ クリック時の処理を実行
↓ ユーザーが入力
→ 入力時の処理を実行
ユーザーの操作を待って、その都度反応するスタイルなんです。
イベントリスナーの基本
イベントリスナーとは
イベントリスナーは、特定のイベントを「聞き耳を立てて待つ」仕組みです。
イベントが発生したら、あらかじめ登録しておいた関数(イベントハンドラー)が実行されます。
addEventListener の使い方
最も推奨される方法です。
基本構文:
要素.addEventListener('イベント名', 実行する関数);
具体例:
// ボタン要素を取得
const button = document.getElementById('myButton');
// クリックイベントを登録
button.addEventListener('click', function() {
alert('ボタンがクリックされました!');
});
アロー関数での書き方:
button.addEventListener('click', () => {
alert('ボタンがクリックされました!');
});
HTML属性での登録(非推奨)
HTML内に直接書く方法もありますが、推奨されません。
<!-- 非推奨 -->
<button onclick="alert('クリック!')">ボタン</button>
推奨されない理由:
- HTMLとJavaScriptが混在して管理しづらい
- 複数のイベントハンドラーを登録できない
- 保守性が悪い
プロパティでの登録(古い方法)
// あまり推奨されない方法
button.onclick = function() {
alert('クリックされました');
};
デメリット:
- 一つのイベントに一つのハンドラーしか登録できない
- 後から上書きされる可能性がある
addEventListenerを使うべき理由:
- 複数のハンドラーを登録できる
- 削除も簡単にできる
- より柔軟な制御が可能
主なイベントの種類
マウスイベント
click:
要素がクリックされた時
element.addEventListener('click', (e) => {
console.log('クリックされました');
});
dblclick:
ダブルクリックされた時
element.addEventListener('dblclick', (e) => {
console.log('ダブルクリックされました');
});
mousedown / mouseup:
マウスボタンを押した時 / 離した時
element.addEventListener('mousedown', (e) => {
console.log('マウスボタンが押されました');
});
mouseenter / mouseleave:
マウスが要素に入った時 / 出た時
element.addEventListener('mouseenter', (e) => {
element.style.backgroundColor = 'lightblue';
});
element.addEventListener('mouseleave', (e) => {
element.style.backgroundColor = '';
});
mouseover / mouseout:
mouseenter/mouseleaveと似ていますが、子要素でも発火します
mousemove:
マウスが要素内で動いた時
element.addEventListener('mousemove', (e) => {
console.log(`座標: ${e.clientX}, ${e.clientY}`);
});
キーボードイベント
keydown:
キーが押された時
document.addEventListener('keydown', (e) => {
console.log(`押されたキー: ${e.key}`);
});
keyup:
キーが離された時
document.addEventListener('keyup', (e) => {
console.log('キーが離されました');
});
keypress:
文字キーが押された時(非推奨、keydownを使う)
実用例:Enterキーで送信
inputElement.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
submitForm();
}
});
フォームイベント
submit:
フォームが送信された時
form.addEventListener('submit', (e) => {
e.preventDefault(); // デフォルトの送信を防ぐ
console.log('フォーム送信処理');
});
input:
入力値が変更された時(リアルタイム)
input.addEventListener('input', (e) => {
console.log(`現在の値: ${e.target.value}`);
});
change:
入力値が確定した時(フォーカスが外れた時など)
select.addEventListener('change', (e) => {
console.log(`選択された値: ${e.target.value}`);
});
focus / blur:
要素がフォーカスされた時 / フォーカスが外れた時
input.addEventListener('focus', (e) => {
e.target.style.borderColor = 'blue';
});
input.addEventListener('blur', (e) => {
e.target.style.borderColor = '';
});
ページ・ウィンドウイベント
load:
ページの読み込みが完了した時
window.addEventListener('load', () => {
console.log('ページ読み込み完了');
});
DOMContentLoaded:
HTMLの解析が完了した時(画像などは未完了でもOK)
document.addEventListener('DOMContentLoaded', () => {
console.log('DOM構築完了');
});
scroll:
スクロールされた時
window.addEventListener('scroll', () => {
console.log(`スクロール位置: ${window.scrollY}`);
});
resize:
ウィンドウサイズが変更された時
window.addEventListener('resize', () => {
console.log(`幅: ${window.innerWidth}, 高さ: ${window.innerHeight}`);
});
イベントオブジェクト
イベントが発生すると、詳細情報を含むイベントオブジェクトが自動的に渡されます。
基本的な使い方
element.addEventListener('click', (event) => {
// eventオブジェクトに様々な情報が含まれている
console.log(event);
});
よく使われる略称:e
、evt
、event
主なプロパティ
target:
イベントが発生した要素
button.addEventListener('click', (e) => {
console.log(e.target); // クリックされた要素
e.target.textContent = 'クリック済み';
});
currentTarget:
イベントリスナーが登録されている要素
// targetとcurrentTargetの違い
div.addEventListener('click', (e) => {
console.log(e.target); // 実際にクリックされた子要素
console.log(e.currentTarget); // divそのもの
});
type:
イベントの種類
element.addEventListener('click', (e) => {
console.log(e.type); // "click"
});
clientX / clientY:
ビューポート内でのマウスの座標
document.addEventListener('click', (e) => {
console.log(`X: ${e.clientX}, Y: ${e.clientY}`);
});
pageX / pageY:
ページ全体でのマウスの座標(スクロール量を含む)
key / code:
押されたキーの情報
document.addEventListener('keydown', (e) => {
console.log(`Key: ${e.key}, Code: ${e.code}`);
});
イベントの伝播(バブリングとキャプチャリング)
イベント伝播の仕組み
イベントは、DOMツリーを伝わって広がります。
例:
<div id="parent">
<button id="child">クリック</button>
</div>
ボタンをクリックすると:
- キャプチャリングフェーズ:document → div → button
- ターゲットフェーズ:button(イベント発生元)
- バブリングフェーズ:button → div → document
バブリング(デフォルト)
子要素で発生したイベントが、親要素に向かって伝わります。
const parent = document.getElementById('parent');
const child = document.getElementById('child');
parent.addEventListener('click', () => {
console.log('親要素がクリックされました');
});
child.addEventListener('click', () => {
console.log('子要素がクリックされました');
});
// 子要素をクリックすると:
// "子要素がクリックされました"
// "親要素がクリックされました"
// の順で表示される
キャプチャリング
逆に、親から子に向かって伝わる方式です。
// 第3引数にtrueを指定
parent.addEventListener('click', () => {
console.log('親要素(キャプチャリング)');
}, true);
child.addEventListener('click', () => {
console.log('子要素');
});
// 子要素をクリックすると:
// "親要素(キャプチャリング)"
// "子要素"
// の順で表示される
stopPropagation()
イベントの伝播を止めるメソッドです。
child.addEventListener('click', (e) => {
e.stopPropagation(); // ここで伝播を止める
console.log('子要素');
});
parent.addEventListener('click', () => {
console.log('親要素'); // これは実行されない
});
preventDefault()
デフォルトの動作を防ぐメソッドです。
よくある使用例
リンクのページ遷移を防ぐ:
link.addEventListener('click', (e) => {
e.preventDefault();
console.log('ページ遷移をキャンセルしました');
});
フォームの送信を防ぐ:
form.addEventListener('submit', (e) => {
e.preventDefault();
// 独自の送信処理を実行
const formData = new FormData(e.target);
console.log('カスタム送信処理');
});
右クリックメニューを防ぐ:
element.addEventListener('contextmenu', (e) => {
e.preventDefault();
console.log('右クリックメニューを無効化');
});
テキスト選択を防ぐ:
element.addEventListener('selectstart', (e) => {
e.preventDefault();
});
イベント委譲(Event Delegation)
親要素にイベントリスナーを登録して、子要素のイベントを処理する手法です。
なぜイベント委譲が必要?
問題:動的に追加される要素
// これは動的に追加された要素には効かない
const buttons = document.querySelectorAll('.button');
buttons.forEach(button => {
button.addEventListener('click', () => {
console.log('クリック');
});
});
イベント委譲の実装
// 親要素にリスナーを登録
const container = document.getElementById('container');
container.addEventListener('click', (e) => {
// クリックされた要素が.buttonクラスを持つか確認
if (e.target.classList.contains('button')) {
console.log('ボタンがクリックされました');
console.log(e.target.textContent);
}
});
// 後から追加されたボタンでも動作する
const newButton = document.createElement('button');
newButton.className = 'button';
newButton.textContent = '新しいボタン';
container.appendChild(newButton);
メリット
パフォーマンスの向上:
多数の要素に個別にリスナーを登録するより、メモリ効率が良い
動的な要素に対応:
後から追加された要素でも、自動的にイベント処理が適用される
実用例:リストのクリック処理
const list = document.getElementById('todoList');
list.addEventListener('click', (e) => {
if (e.target.tagName === 'LI') {
e.target.classList.toggle('completed');
}
// 削除ボタンの処理
if (e.target.classList.contains('delete-btn')) {
e.target.closest('li').remove();
}
});
実践的なイベント処理パターン
パターン1:ホバーエフェクト
const card = document.querySelector('.card');
card.addEventListener('mouseenter', () => {
card.style.transform = 'scale(1.05)';
card.style.boxShadow = '0 10px 20px rgba(0,0,0,0.2)';
});
card.addEventListener('mouseleave', () => {
card.style.transform = 'scale(1)';
card.style.boxShadow = '';
});
パターン2:リアルタイム検索
const searchInput = document.getElementById('search');
const resultsList = document.getElementById('results');
searchInput.addEventListener('input', (e) => {
const query = e.target.value.toLowerCase();
// 検索処理(簡略版)
const items = document.querySelectorAll('.item');
items.forEach(item => {
const text = item.textContent.toLowerCase();
if (text.includes(query)) {
item.style.display = 'block';
} else {
item.style.display = 'none';
}
});
});
パターン3:スムーススクロール
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', (e) => {
e.preventDefault();
const targetId = anchor.getAttribute('href');
const targetElement = document.querySelector(targetId);
targetElement.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
});
});
パターン4:フォームバリデーション
const form = document.getElementById('registrationForm');
const email = document.getElementById('email');
const password = document.getElementById('password');
email.addEventListener('blur', () => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email.value)) {
email.classList.add('error');
showError(email, 'メールアドレスが正しくありません');
} else {
email.classList.remove('error');
clearError(email);
}
});
form.addEventListener('submit', (e) => {
e.preventDefault();
if (password.value.length < 8) {
showError(password, 'パスワードは8文字以上必要です');
return;
}
// 送信処理
console.log('フォーム送信');
});
パターン5:ドラッグ&ドロップ
const draggable = document.getElementById('draggable');
draggable.addEventListener('dragstart', (e) => {
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', e.target.innerHTML);
e.target.style.opacity = '0.5';
});
draggable.addEventListener('dragend', (e) => {
e.target.style.opacity = '1';
});
const dropZone = document.getElementById('dropZone');
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
dropZone.style.backgroundColor = 'lightblue';
});
dropZone.addEventListener('dragleave', () => {
dropZone.style.backgroundColor = '';
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
const data = e.dataTransfer.getData('text/html');
dropZone.innerHTML = data;
dropZone.style.backgroundColor = '';
});
パフォーマンスの最適化
デバウンス(Debounce)
連続するイベントの実行を制御する手法です。
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// 使用例:リサイズイベント
window.addEventListener('resize', debounce(() => {
console.log('リサイズ処理');
}, 250));
// 検索入力
searchInput.addEventListener('input', debounce((e) => {
performSearch(e.target.value);
}, 300));
スロットル(Throttle)
一定間隔でのみイベントを実行する手法です。
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// 使用例:スクロールイベント
window.addEventListener('scroll', throttle(() => {
console.log('スクロール位置:', window.scrollY);
}, 100));
パッシブリスナー
スクロールやタッチイベントのパフォーマンスを向上させます。
// パフォーマンス向上
element.addEventListener('touchstart', handler, { passive: true });
// preventDefault()を使う場合はpassive: false
element.addEventListener('touchmove', (e) => {
e.preventDefault();
}, { passive: false });
イベントリスナーの削除
removeEventListener
登録したイベントリスナーを削除できます。
function handleClick() {
console.log('クリックされました');
}
// 登録
button.addEventListener('click', handleClick);
// 削除
button.removeEventListener('click', handleClick);
注意点:
- 削除するには、登録時と同じ関数を指定する必要がある
- 無名関数では削除できない
削除できない例:
// これは削除できない
button.addEventListener('click', () => {
console.log('クリック');
});
// 無名関数を変数に格納すれば削除可能
const handler = () => {
console.log('クリック');
};
button.addEventListener('click', handler);
button.removeEventListener('click', handler);
once オプション
一度だけ実行して自動的に削除されます。
button.addEventListener('click', () => {
console.log('一度だけ実行');
}, { once: true });
よくある質問
Q1. addEventListenerとonclick、どちらを使うべき?
addEventListener を使いましょう。
複数のハンドラーを登録できる、削除も簡単、より柔軟な制御が可能など、多くの利点があります。
Q2. イベントリスナーが多すぎるとパフォーマンスに影響する?
はい、影響します。
数千個のリスナーを登録すると、メモリ消費が増えます。イベント委譲を使うことで、大幅に改善できます。
Q3. イベントの実行順序は保証される?
同じ要素の同じイベントに対しては、登録順に実行されます。
ただし、バブリングとキャプチャリングでは順序が変わるので注意しましょう。
Q4. jQueryとの違いは?
jQueryは内部的に同じ仕組みを使っていますが、より簡潔に書けます。
// vanilla JavaScript
document.getElementById('btn').addEventListener('click', handler);
// jQuery
$('#btn').on('click', handler);
現在は、vanilla JavaScriptでも十分シンプルに書けるようになっています。
Q5. カスタムイベントは作れる?
はい、作れます。
// カスタムイベントの作成
const event = new CustomEvent('myEvent', {
detail: { message: 'Hello!' }
});
// リスナー登録
element.addEventListener('myEvent', (e) => {
console.log(e.detail.message);
});
// イベント発火
element.dispatchEvent(event);
まとめ:イベント処理をマスターして動的なWebページを作ろう
JavaScriptのイベント処理について、基礎から実践まで解説してきました。
重要ポイントのおさらい:
- addEventListenerが基本
柔軟性が高く、複数のハンドラーを登録可能 - イベントオブジェクトを活用
target、preventDefault、stopPropagationなど - イベント委譲でパフォーマンス向上
親要素で子要素のイベントをまとめて処理 - デバウンスとスロットルで最適化
頻繁に発火するイベントの処理を効率化 - 実践的なパターンを学ぶ
ホバーエフェクト、フォームバリデーション、ドラッグ&ドロップなど
イベント処理は、JavaScriptでインタラクティブなWebページを作る上で最も重要な要素です。この記事の内容を実際に試しながら、自分のものにしていってくださいね!
コメント