読み込み中...

JavaScriptにおけるイベントハンドラの使い方を完全ガイド!20選の実践サンプルコード付き

JavaScriptイベントハンドラの使い方を学ぶための記事イメージ JS
この記事は約34分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

JavaScriptのイベントハンドラは、クリック、入力、送信、スクロールなどの操作に応じて処理を動かす仕組みです。そのため、静的なHTMLに反応を加えたい場面では、addEventListenerを中心に組み立てると保守しやすくなります。

初心者向けに押さえたいのは、onclickのようなプロパティ代入より、複数登録や削除に向いたaddEventListener()を優先する考え方です。一方、既存コードを読む場面では古い書き方も出てくるため、違いを知っておくと修正範囲を判断しやすいでしょう。

使い方の中心は、対象要素を取得し、イベント名を渡し、発生時に呼ばれる関数を書く流れになります。イベントハンドラの応用例では、フォーム検証、モーダル、タブ、検索、スクロール演出などを扱いるのが基本です。

動作確認環境
  • JavaScript: ECMAScript 2024相当の構文
  • HTML: HTML Living Standard
  • ブラウザ: Google Chrome 126以降 / Firefox 127以降 / Safari 17以降を想定
📖 この記事で学べること
  • JavaScriptのイベントハンドラの基本構造と使い方
  • clickinputsubmitなど主要イベントの扱い
  • フォーム、タブ、モーダルなどの応用例
  • イベント伝播、削除、非同期処理に関する注意点
  • カスタマイズサンプルコードを読み替える観点

イベントハンドラとは

<button id='helloButton'>押す</button>
<p id='message'></p>

<script>
const button = document.getElementById('helloButton');
const message = document.getElementById('message');

button.addEventListener('click', () => {
  message.textContent = 'クリックされました';
});
</script>

結果: 期待される表示は、ボタンを押したタイミングで段落に「クリックされました」と入る状態です。

この短いコードが、イベントハンドラの最小形です。buttonがイベントを受け取る対象、clickがイベント名、アロー関数が発生時に動く処理になります。

一般に、イベントはブラウザが発生させる通知だと整理できます。ユーザーが<button>を押す、<input>へ文字を入れる、<form>を送信する、といった変化をJavaScript側で受け取るのがイベントハンドラです。

公式ドキュメントによれば、MDNのaddEventListener解説では、指定した種類のイベントが対象へ届けられたときに関数を呼ぶ仕組みとして説明されているのが目安です。そのため、現在の実装ではEventTargetaddEventListenerを軸に考えるのが自然です。

一方、element.onclick = function () {}のような書き方も動作します。ただし、同じプロパティへ再代入すると前の処理が上書きされるため、複数の処理を組み合わせる画面ではaddEventListener()のほうが扱いやすいと言えるでしょう。

これに関連して、JavaScriptの配列処理を併用する画面では、forEach・mapの使い分けも理解しておくと、一覧要素へのイベント登録が読みやすくなります。複数のボタンへ同じ処理を付ける場合、querySelectorAllforEachを組み合わせる場面が多いからです。

💡 Tips: イベントハンドラは「要素」「イベント名」「処理」の組み合わせで読むと理解しやすくなるのがポイントです。初心者向けの学習では、最初から複雑なUIを作るより、1個のボタンで反応を確認する流れが現実的です。
分類イベント主な対象使い方の要点注意点
クリックclick<button>ボタン操作の起点にする連打対策を考える
ダブルクリックdblclick<div>特殊操作に限定するモバイルでは相性に注意
入力input<input>入力中に値を読む処理が重い場合は間引く
変更change<select>選択確定時に使うinputとの発火時点が違う
送信submit<form>検証後に送信を制御するpreventDefaultの要否を見る
キー入力keydowndocumentショートカットに使う入力欄との衝突に注意
キー解放keyup<input>入力後の判定に使うIME入力を考慮する
フォーカスfocus<input>入力開始を検知する伝播しない性質を知る
フォーカス解除blur<textarea>入力完了後の検証に向く即時エラー表示は慎重に扱う
マウス進入mouseenter<nav>ホバーUIに使うタッチ端末では代替が必要
マウス退出mouseleave<nav>開いたUIを戻す子要素との関係を確認する
マウス移動mousemovewindowドラッグ中の座標に使う高頻度発火を抑える
ポインター押下pointerdown<canvas>マウスとタッチをまとめる古い環境の対応を見る
ポインター移動pointermovedocumentドラッグ操作を追う不要時は解除する
ポインター解放pointerupdocumentドラッグ終了を扱う範囲外解放を考える
読み込みDOMContentLoadeddocumentDOM構築後に初期化する画像読込完了とは違う
ページ読込loadwindow画像も含めて待つ初期表示が遅れやすい
スクロールscrollwindow表示位置に応じて処理する連続発火に注意する
リサイズresizewindow画面幅の変化を読むCSSで済む処理はCSSへ寄せる
コピーcopydocumentコピー操作を検知する過度な制限は避ける
貼り付けpaste<input>貼り付け値を整えるユーザー操作を妨げない
ドラッグ開始dragstart<li>移動対象を記録するアクセシビリティを補う
ドロップdrop<ul>移動先で処理するdragoverの制御が必要
アニメーション終了animationend<div>CSS演出後に片付ける中断時の状態を見る
遷移終了transitionend<div>開閉後の処理に使う複数プロパティで複数回動く
エラーerror<img>代替画像へ切り替える無限再試行を避ける
メディア再生play<video>再生開始を検知する自動再生制限を考慮する
メディア停止pause<video>停止時のUIを戻す状態管理と合わせる
カスタムCustomEventdocument独自の通知を作る名前の衝突を避ける
解除removeEventListenerElement不要な処理を外す同じ関数参照が必要

イベントハンドラの使い方

使い方は、document.getElementByIddocument.querySelectorで対象を取得し、addEventListenerでイベント名と関数を渡す流れになります。その構造を覚えると、クリック、入力、キー操作の違いはイベント名と読み取る値の違いとして整理できます。

サンプルコード1:クリックイベント

これが最も短いクリック処理の形です。textContentで表示文字列を変えるため、alertより画面上の変化が追いやすくなるのが一般的です。

<button id='saveButton'>保存</button>
<p id='status'>未保存</p>

<script>
const saveButton = document.getElementById('saveButton');
const status = document.getElementById('status');

saveButton.addEventListener('click', () => {
  status.textContent = '保存待ちの処理を受け付けました';
});
</script>

結果: 期待される表示は、保存ボタンを押すと「保存待ちの処理を受け付けました」に変わる状態です。

サンプルコード2:マウスオーバーイベント

その要素へポインターが入ったときと出たときを分けると、簡単なホバー演出を作れます。ただし、タッチ端末では同じ操作感にならないため、クリック操作でも同等の情報へ届く設計が必要です。

<div id='notice' style='padding:16px;background:#dbeafe;'>お知らせ</div>

<script>
const notice = document.getElementById('notice');

notice.addEventListener('mouseenter', () => {
  notice.style.backgroundColor = '#fde68a';
});

notice.addEventListener('mouseleave', () => {
  notice.style.backgroundColor = '#dbeafe';
});
</script>

結果: 期待される表示は、ポインターが重なった間だけ背景色が黄色系へ変わる状態です。

サンプルコード3:キーボードイベント

このとき、入力値そのものを扱うならkeyupよりinputが合う場合もあります。キーそのものを判定したい場面では、KeyboardEventkeyを読みます。

<input id='shortcutInput' placeholder='Enterで確定'>
<p id='shortcutMessage'></p>

<script>
const shortcutInput = document.getElementById('shortcutInput');
const shortcutMessage = document.getElementById('shortcutMessage');

shortcutInput.addEventListener('keydown', (event) => {
  if (event.key === 'Enter') {
    shortcutMessage.textContent = `入力値: ${shortcutInput.value}`;
  }
});
</script>

結果: 期待される表示は、入力欄でEnterキーを押すと、その時点の入力値が段落へ反映される状態です。

同様に、配列の各要素へイベントを設定する処理ではNodeListを回す必要があります。forEachでreturnを扱う考え方を知っていると、途中終了したい処理と全件処理したい処理を分けやすくなります。

イベントハンドラの応用例

応用例では、イベントが発生したあとに何を更新するかが主役になるのが現実的です。フォームなら入力値、モーダルなら表示状態、検索なら候補一覧というように、対象データを明確にするとイベントハンドラの責務が過度に広がりません。

サンプルコード4:フォームバリデーション

具体的には、submitイベントで送信前の値を読み、条件に合わない場合だけpreventDefault()で通常送信を止めます。HTMLのrequiredtype='email'と組み合わせると、JavaScript側の処理を絞れます。

<form id='signupForm'>
  <label>メール <input id='email' type='email' required></label>
  <button type='submit'>登録</button>
</form>
<p id='formMessage'></p>

<script>
const signupForm = document.getElementById('signupForm');
const email = document.getElementById('email');
const formMessage = document.getElementById('formMessage');

signupForm.addEventListener('submit', (event) => {
  event.preventDefault();
  formMessage.textContent = email.value.includes('@')
    ? '送信可能な形式です'
    : 'メールアドレスの形式を確認してください';
});
</script>

結果: 期待される表示は、送信操作のあとにメール形式の判定メッセージが表示される状態です。

サンプルコード5:ドラッグ操作

これらのドラッグ処理では、開始時のずれを記録し、移動中にstyle.leftstyle.topを更新します。一方、実運用ではキーボード操作やスクリーンリーダーへの代替操作も検討対象です。

<div id='area' style='position:relative;width:280px;height:160px;border:1px solid #ccc;'>
  <div id='card' style='position:absolute;width:64px;height:64px;background:#bfdbfe;cursor:grab;'></div>
</div>

<script>
const card = document.getElementById('card');
let dragging = false;
let offsetX = 0;
let offsetY = 0;

card.addEventListener('pointerdown', (event) => {
  dragging = true;
  offsetX = event.offsetX;
  offsetY = event.offsetY;
  card.setPointerCapture(event.pointerId);
});

card.addEventListener('pointermove', (event) => {
  if (!dragging) return;
  card.style.left = `${event.clientX - offsetX}px`;
  card.style.top = `${event.clientY - offsetY}px`;
});

card.addEventListener('pointerup', () => {
  dragging = false;
});
</script>

結果: 期待される表示は、青い四角形を押したまま動かすと位置が変わる状態です。

サンプルコード6:スライダー

その場で表示する番号をcurrentとして保持すると、前後ボタンの処理が単純になります。画像を使う場合はaltを入れ、読み込み失敗時の代替表示も考えるとよいでしょう。

<div id='slideText'>スライドA</div>
<button id='prevSlide'>前へ</button>
<button id='nextSlide'>次へ</button>

<script>
const slides = ['スライドA', 'スライドB', 'スライドC'];
const slideText = document.getElementById('slideText');
let current = 0;

function renderSlide() {
  slideText.textContent = slides[current];
}

document.getElementById('prevSlide').addEventListener('click', () => {
  current = (current + slides.length - 1) % slides.length;
  renderSlide();
});

document.getElementById('nextSlide').addEventListener('click', () => {
  current = (current + 1) % slides.length;
  renderSlide();
});
</script>

結果: 期待される表示は、前後ボタンで「スライドA」「スライドB」「スライドC」が循環する状態です。

サンプルコード7:モーダルウィンドウ

ただし、モーダルは表示非表示だけでは完成しません。フォーカス移動、Escキー、背景スクロールの扱いなどの注意点があり、初心者向けの段階では最低限の開閉から理解するとよいです。

<button id='openModal'>開く</button>
<div id='modal' hidden>
  <p>設定画面</p>
  <button id='closeModal'>閉じる</button>
</div>

<script>
const modal = document.getElementById('modal');

document.getElementById('openModal').addEventListener('click', () => {
  modal.hidden = false;
});

document.getElementById('closeModal').addEventListener('click', () => {
  modal.hidden = true;
});
</script>

結果: 期待される表示は、「開く」で設定画面が表示され、「閉じる」で非表示へ戻る状態です。

サンプルコード8:アコーディオンメニュー

同様に、開閉UIではhidden属性やaria-expandedを更新すると、状態を読み取りやすくなります。見た目だけをdisplayで切り替えるより、意味も合わせて変える構成です。

<button class='accordion' aria-expanded='false'>詳細</button>
<div class='panel' hidden>追加の説明文です。</div>

<script>
const accordion = document.querySelector('.accordion');
const panel = document.querySelector('.panel');

accordion.addEventListener('click', () => {
  const opened = accordion.getAttribute('aria-expanded') === 'true';
  accordion.setAttribute('aria-expanded', String(!opened));
  panel.hidden = opened;
});
</script>

結果: 期待される表示は、詳細ボタンを押すたびに説明文の表示と非表示が切り替わる状態です。

サンプルコード9:タブ切り替え

使い分けると、タブは「現在選ばれている識別子」を状態として持つUIです。datasetに対応先のIDを入れておけば、HTMLとJavaScriptの対応関係が追いやすくなります。

<button class='tab' data-panel='panelA'>A</button>
<button class='tab' data-panel='panelB'>B</button>
<div id='panelA'>Aの内容</div>
<div id='panelB' hidden>Bの内容</div>

<script>
const tabs = document.querySelectorAll('.tab');
const panels = ['panelA', 'panelB'].map((id) => document.getElementById(id));

tabs.forEach((tab) => {
  tab.addEventListener('click', () => {
    panels.forEach((panel) => {
      panel.hidden = panel.id !== tab.dataset.panel;
    });
  });
});
</script>

結果: 期待される表示は、AまたはBのボタンを押すと対応する内容だけが表示される状態です。

サンプルコード10:インクリメンタルサーチ

これにより、入力欄の値に合わせて候補を絞り込めます。大量データやAPI検索ではdebounceを入れる注意点がありますが、小さな配列なら即時更新で十分に読めるコードになります。

<input id='searchBox' placeholder='果物名を検索'>
<ul id='results'></ul>

<script>
const items = ['Apple', 'Banana', 'Cherry', 'Grape', 'Orange'];
const searchBox = document.getElementById('searchBox');
const results = document.getElementById('results');

searchBox.addEventListener('input', () => {
  const keyword = searchBox.value.toLowerCase();
  results.innerHTML = '';

  items
    .filter((item) => item.toLowerCase().includes(keyword))
    .forEach((item) => {
      const li = document.createElement('li');
      li.textContent = item;
      results.appendChild(li);
    });
});
</script>

結果: 期待される表示は、入力文字に一致する果物名だけがリストに残る状態です。

こうした検索UIは、別記事のJavaScriptイベントの使い方と応用例とも相性がよい領域です。イベントハンドラを部品として見れば、入力、絞り込み、描画の責務を分けて改善できます。

注意点と対処法

注意点として特に押さえたいのは、イベントの登録先、伝播、解除、処理頻度です。そのため、意図しない二重実行や画面の重さが出たときは、イベントハンドラの数と発火条件を確認します。

一般に、clickは内側の要素から外側の要素へ伝わりますし、ここがポイントです。この伝播を止めたい場合はstopPropagation()を使えますが、親要素でまとめて処理するイベントデリゲーションを壊す可能性もあるため、使う範囲を限定するのが現実的です。

一方、通常動作を止めたい場面ではpreventDefault()を使いるのが基本です。フォーム送信やリンク遷移を止める処理で使われますが、ユーザーが期待する標準動作まで止めると操作性が落ちるため、必要な条件だけに絞りますし、ここがポイントです。

初心者がつまずきやすいのは、removeEventListenerへ匿名関数を渡しても解除できない点です。削除したいイベントハンドラは、同じ関数参照を渡す必要があると整理できます。

⚠️ 注意: scrollresizeは短時間に何度も発火するのが目安です。重い処理を直接入れると描画が詰まりやすいため、requestAnimationFrameや間引き処理を検討します。
<button id='toggleLog'>ログ切り替え</button>
<button id='targetButton'>対象ボタン</button>

<script>
const toggleLog = document.getElementById('toggleLog');
const targetButton = document.getElementById('targetButton');
let enabled = false;

function logClick() {
  console.log('対象ボタンが押されました');
}

toggleLog.addEventListener('click', () => {
  enabled = !enabled;

  if (enabled) {
    targetButton.addEventListener('click', logClick);
  } else {
    targetButton.removeEventListener('click', logClick);
  }
});
</script>

結果: 期待される出力は、ログ有効時に対象ボタンを押すとコンソールへメッセージが出る状態です。

この例では、logClickを名前付き関数にしているため、登録と解除で同じ参照を渡せます。注意点を減らすには、後から外す可能性があるイベントハンドラを匿名関数だけで書き切らない判断が必要になります。

公式ドキュメントによれば、MDNのEvent解説では、イベントがDOM要素やDocumentWindowなどと結びつく仕組みを確認できると理解できるのがポイントです。そのため、対象がElementなのかwindowなのかを見分けることが、原因調査の近道になるのが基本です。

問題起こりやすい原因対処法
処理が二重に動く初期化が複数回走っている登録場所を見直し、必要なら解除する
解除できない匿名関数を別物として渡している名前付き関数を使う
親の処理も動くイベントが伝播しているstopPropagationの必要性を確認する
送信されないpreventDefaultで止めている条件分岐の位置を見る
画面が重い高頻度イベントで描画している処理を間引く

カスタマイズ方法

カスタマイズ方法では、イベントハンドラを単発の反応ではなく、部品同士をつなぐ通知として扱います。カスタマイズサンプルコードを読むときは、発火する側、受け取る側、更新される状態を分けると理解しやすくなります。

具体的には、CustomEventで独自イベントを作る、親要素へイベントデリゲーションを置く、非同期処理と組み合わせる、画像やスクロールに合わせてUIを変える、といった応用例があると覚えるとよいでしょう。初心者向けでも、部品単位に分ければ段階的に読める構成です。

サンプルコード11:カスタムイベント

カスタムイベントは、アプリ内の独自通知を作りたいときに使いるのが一般的です。公式ドキュメントによれば、MDNのCustomEvent解説では、detailへ追加データを持たせられるインターフェースとして扱われています。

const userEvent = new CustomEvent('user:login', {
  detail: { name: 'Sato' }
});

document.addEventListener('user:login', (event) => {
  console.log(`${event.detail.name}さんがログインしました`);
});

document.dispatchEvent(userEvent);

結果: 期待される出力は、コンソールに「Satoさんがログインしました」と出る状態です。

サンプルコード12:イベントデリゲーション

その方法では、子要素それぞれにイベントハンドラを付けず、親要素でまとめて受け取ります。動的に追加される項目にも同じ処理が効くため、リストUIでよく使われます。

<ul id='taskList'>
  <li>設計</li>
  <li>実装</li>
</ul>

<script>
const taskList = document.getElementById('taskList');

taskList.addEventListener('click', (event) => {
  if (event.target.matches('li')) {
    event.target.classList.toggle('done');
  }
});
</script>

結果: 期待される表示は、リスト項目を押すたびにdoneクラスの有無が切り替わる状態です。

サンプルコード13:イベントの停止と再開

これを状態管理として扱うと、停止中かどうかをbooleanで保持できます。イベントを完全に削除するのではなく、処理の中で分岐する使い方もあります。

<button id='pauseButton'>一時停止</button>
<button id='countButton'>加算</button>
<p id='countText'>0</p>

<script>
let paused = false;
let count = 0;
const countText = document.getElementById('countText');

document.getElementById('pauseButton').addEventListener('click', () => {
  paused = !paused;
});

document.getElementById('countButton').addEventListener('click', () => {
  if (paused) return;
  count += 1;
  countText.textContent = String(count);
});
</script>

結果: 期待される表示は、一時停止中は加算ボタンを押しても数値が増えない状態です。

サンプルコード14:動的追加と削除

一方、イベントハンドラ自体を必要な期間だけ付けたい場合は、addEventListenerremoveEventListenerを対にします。カスタマイズサンプルコードとしては、編集モード中だけドラッグを許可するような場面に近いです。

<button id='editMode'>編集モード切り替え</button>
<div id='editableBox'>クリック対象</div>

<script>
const editableBox = document.getElementById('editableBox');
let editing = false;

function selectBox() {
  editableBox.textContent = '選択中';
}

document.getElementById('editMode').addEventListener('click', () => {
  editing = !editing;
  if (editing) {
    editableBox.addEventListener('click', selectBox);
  } else {
    editableBox.removeEventListener('click', selectBox);
    editableBox.textContent = 'クリック対象';
  }
});
</script>

結果: 期待される表示は、編集モード中だけ対象を押すと「選択中」に変わる状態です。

サンプルコード15:非同期処理との連携

非同期処理では、クリック後にfetchPromiseの完了を待ちます。ただし、外部APIの実在しない応答を前提にすると説明が不正確になるため、下の例ではsetTimeoutで疑似的な待ち時間を作ります。

<button id='loadButton'>読み込み</button>
<p id='loadStatus'>待機中</p>

<script>
const loadStatus = document.getElementById('loadStatus');

function waitData() {
  return new Promise((resolve) => {
    setTimeout(() => resolve('データを受け取りました'), 600);
  });
}

document.getElementById('loadButton').addEventListener('click', async () => {
  loadStatus.textContent = '読み込み中';
  loadStatus.textContent = await waitData();
});
</script>

結果: 期待される表示は、ボタンを押すと一度「読み込み中」になり、その後「データを受け取りました」へ変わる状態です。

サンプルコード16:画像プリロード

画像の読み込みでは、Imageオブジェクトのloaderrorを使って成功と失敗を分けます。そのため、表示ボタンを押す前に読み込み状態を把握できます。

<button id='showImageButton'>画像を表示</button>
<p id='imageStatus'>準備中</p>

<script>
const imageStatus = document.getElementById('imageStatus');
let ready = false;
const image = new Image();

image.addEventListener('load', () => {
  ready = true;
  imageStatus.textContent = '画像を表示できます';
});

image.addEventListener('error', () => {
  imageStatus.textContent = '画像を読み込めませんでした';
});

image.src = '/sample-image.jpg';

document.getElementById('showImageButton').addEventListener('click', () => {
  if (ready) document.body.appendChild(image);
});
</script>

結果: 期待される表示は、画像の読み込み状態に応じてメッセージが変わり、準備後にボタンで画像が追加される状態です。

サンプルコード17:スムーズスクロール

基本的に、現在のブラウザではscrollIntoViewbehavior: 'smooth'を渡すだけで滑らかな移動を作れます。複雑な計算を増やす前に、標準APIで足りるかを確認します。

<a href='#targetSection' id='scrollLink'>対象へ移動</a>
<div style='height:600px'></div>
<section id='targetSection'>移動先</section>

<script>
document.getElementById('scrollLink').addEventListener('click', (event) => {
  event.preventDefault();
  document.getElementById('targetSection').scrollIntoView({
    behavior: 'smooth'
  });
});
</script>

結果: 期待される表示は、リンクを押すと対象セクションまで滑らかに移動する状態です。

サンプルコード18:スクロールイベントとアニメーション

ただし、スクロールイベントで毎回すべての要素を計算すると負荷が増えます。単純な学習用ではgetBoundingClientRectで位置を読み、実務寄りの実装ではIntersectionObserverも候補になります。

<style>
.fadeTarget { opacity: 0; transition: opacity .4s; }
.fadeTarget.visible { opacity: 1; }
</style>
<div class='fadeTarget'>表示領域に入ると表示</div>

<script>
const fadeTarget = document.querySelector('.fadeTarget');

function checkVisible() {
  const rect = fadeTarget.getBoundingClientRect();
  if (rect.top < window.innerHeight) {
    fadeTarget.classList.add('visible');
  }
}

window.addEventListener('scroll', checkVisible);
window.addEventListener('load', checkVisible);
</script>

結果: 期待される表示は、対象要素が画面内に入ると透明度が上がる状態です。

サンプルコード19:リサイズイベント

リサイズ処理はレスポンシブUIの補助に使えます。一方、表示切り替えだけならCSSの@mediaで済むことが多く、JavaScriptは状態の同期が必要な場面へ絞ると保守しやすくなります。

<p id='sizeLabel'></p>

<script>
const sizeLabel = document.getElementById('sizeLabel');

function updateSizeLabel() {
  sizeLabel.textContent = window.innerWidth >= 768
    ? 'デスクトップ幅'
    : 'モバイル幅';
}

window.addEventListener('resize', updateSizeLabel);
window.addEventListener('load', updateSizeLabel);
</script>

結果: 期待される表示は、画面幅が768px以上かどうかでラベルが切り替わる状態です。

サンプルコード20:トグルボタン

カスタマイズサンプルコードの最後は、状態に応じてクラスとラベルを同時に変える例です。こうした応用例では、classList.toggleclassList.containsを組み合わせると読みやすくなります。

<button id='themeButton'>ダークモード</button>

<script>
const themeButton = document.getElementById('themeButton');

themeButton.addEventListener('click', () => {
  document.body.classList.toggle('dark-mode');
  const dark = document.body.classList.contains('dark-mode');
  themeButton.textContent = dark ? 'ライトモード' : 'ダークモード';
});
</script>

結果: 期待される表示は、ボタンを押すたびにdark-modeクラスとボタン文字列が切り替わる状態です。

ℹ️ 補足: カスタマイズサンプルコードを自分の画面へ移すときは、ID名、クラス名、初期状態、解除条件を先に合わせます。名前だけ変えて構造を変えないほうが、初心者向けの学習では差分を追いやすいでしょう。

カレンダーUIのように日付選択とクリック処理が絡む画面では、JavaScriptでカレンダーを作成するサンプルも参考になります。拡張子やファイル名に応じた処理へ広げる場合は、JavaScriptで拡張子を扱う方法と組み合わせると、アップロード前の確認UIを作りやすくなると考えられますし、ここがポイントです。

まとめ

JavaScriptのイベントハンドラは、ユーザー操作と画面更新をつなぐ中核の仕組みです。使い方は、対象要素を取得し、イベント名を選び、処理を関数として渡す流れで整理できるのが目安です。

そのうえで、応用例ではフォーム検証、モーダル、タブ、検索、スクロール、リサイズなどへ広げられます。注意点としては、イベント伝播、通常動作の停止、解除できない匿名関数、高頻度イベントの負荷を確認する必要があると言えるでしょう。

カスタマイズサンプルコードを読むときは、発火元、イベント名、変更される状態を分けて追うと理解しやすくなるのが現実的です。初心者向けの学習でも、addEventListenerevent.targetclassListdatasetpreventDefaultを押さえれば、多くのUI処理を読み解けるでしょう。

著者: Japanシーモア編集部

Japanシーモアは、Web/IoT/APP/SYS 分野のプログラミング情報を体系的に提供するメディアです。本記事は編集部による執筆とAI支援を組み合わせて制作し、公開前に編集部が校正しています。誤りや改善案がございましたらお問い合わせよりご連絡ください。

※本記事は実在のエンジニア複数名で構成される Japanシーモア編集部が、AI支援を活用して作成・校正・公開しています。

関連記事