event オブジェクト

このレッスンで分かること

  • イベントの処理には、出来事の情報が詰まった event オブジェクトが 1 つ渡されます
  • event.target は実際に押された要素、event.currentTarget は登録した要素です
  • 親に 1 つだけ登録して子を判定する書き方をイベント委譲と呼びます

event オブジェクト とは

発生したイベントの情報を持つオブジェクトで、処理の第 1 引数として自動で渡されます。

押されたことは分かる。どれが押されたかは

前回のボタンは 1 つだけでした。押されたら決まった文字を出せばよかったので、それ以上の情報は要りません。ところが作品ギャラリーには、いまカードが 5 枚ならんでいます。しかも第8章で配列から描いているので、データを足せば 10 枚にも 20 枚にも増えます。「押された」だけでは足りず「どれが押されたか」を知る必要があります。

その答えが event オブジェクトです。イベントが起きたとき、ブラウザは処理の関数を呼ぶだけでなく、その出来事に関する情報をまとめたオブジェクトを第 1 引数として渡してくれます。受け取るかどうかは自由で、要らなければ書かなくてかまいません。要るときは引数名を 1 つ書くだけです。

gallery.addEventListener("click", function (event) { console.log(event.type); console.log(event.target); });

引数の名前は何でも動きますが、evente と書くのが慣習です。この教材では読みやすさを優先して event で統一します。

event は自分で作って渡すものではありません。ブラウザが作って渡してきます。だから引数を書き忘れても undefined になるだけでエラーにはならず、次の行の event.target ではじめて止まります。

よく使う 3 つのプロパティ

event の中身はイベントの種類によって変わりますが、どのイベントでも使えるものが 3 つあります。

プロパティ何が入るか使いどころ
target実際に出来事が起きた、いちばん内側の要素どれが押されたかの判定
currentTargetaddEventListener を付けた側の要素まとめ役の要素そのものを触りたいとき
typeイベントの種類の文字列。"click" など1 つの関数で複数の種類を受けるとき

targetcurrentTarget の違いが、この回のいちばんの山です。作品カードの中には h3p が入っています。カードの文字のところを押すと、いちばん内側で出来事が起きたのは p です。つまり event.targetp になります。カードそのものではありません。

いっぽう currentTarget は、addEventListener を書いた相手です。.work-list に登録したのなら、どこを押しても currentTarget.work-list のままです。

イベント委譲という考え方

カードが 5 枚あるとき、素直に考えれば 5 枚それぞれに addEventListener を付けることになります。しかし第8章で、カードは配列から JavaScript が描くようになりました。あとから増えたカードには、登録済みの処理は付いていません。増えるたびに登録し直す、という面倒がついてまわります。

そこで使うのが、親にまとめて 1 つだけ登録するやり方です。イベント委譲と呼びます。

gallery.addEventListener("click", function (event) { const card = event.target.closest(".work-card"); if (!card) { return; } const title = card.querySelector("h3").textContent; workMessage.textContent = title + " を選びました"; });

closest は、その要素から親をたどって、条件に合う最初の要素を返すメソッドです。p から始めても、その親のカードが見つかります。カードの外側の余白を押したときは見つからないので null が返り、第2章で学んだ falsy の判定で早めに抜けます。

この書き方なら、登録は最初の 1 回だけです。あとからカードが何枚増えても、押せばそのまま反応します。第8章で .work-list を空にしてデータから描くようにしたことが、ここで効いてきます。

クリックは、押された要素から親へ順に伝わっていきます。この伝わり方をバブリングと呼びます。イベント委譲は、この性質をそのまま使っているだけです。子で起きた出来事が親まで届くから、親で待ち構えていられます。

target をそのまま使わない

event.target を判定にそのまま使うと、押した場所で結果が変わってしまいます。

// 押した場所が h3 のときだけ動く、あてにならない書き方 if (event.target.className === "work-card") { // ... }

見出しを押せば h3、説明文を押せば p、余白を押せばカード本体。3 通りに分かれます。closest を挟むことで、どこを押しても同じ「カード」にそろえられます。イベント委譲では closest とセットで使う、と覚えておくと安全です。

1 つの関数で複数の要素をさばく

イベント委譲のもう 1 つの利点は、処理が 1 か所にまとまることです。カードごとに関数を用意すると、直したいときに 5 か所を直して回ることになります。委譲なら関数は 1 本で、押されたカードの違いは event から読み取ります。

同じ考え方は、ボタンがたくさん並ぶ画面でよく使われます。一覧の各行に削除ボタンが並ぶ画面を想像してください。行が 100 行あれば登録も 100 回です。親の表に 1 つ登録し、event.target.closest("tr") でどの行かを判定すれば、登録は 1 回で済みます。行が増えても減っても、処理は書き換えなくてかまいません。

event.currentTarget は、この委譲の書き方をしているときにこそ意味を持ちます。まとめ役の要素そのものを触りたい場面、たとえば一覧全体に「選択中」の class を付けたいときなどに使います。委譲を使わず要素ごとに登録している場合は、targetcurrentTarget がたいてい同じものを指すので、違いを意識する機会がありません。

event には他にも情報が入っています。マウスの座標を持つ clientXclientY、押されていた修飾キーを表す shiftKeyctrlKey などです。使う場面が来たときに調べれば十分ですが、eventconsole.log に出して中身をながめておくと、どんな情報が取れるのか手触りがつかめます。

今回書くこと

作品のところに id="work-message" の空の段落を足します。JavaScript では、カード 1 枚ずつではなく .work-listclick を 1 つだけ登録します。処理の中で event.target.closest(".work-card") を使って押されたカードを特定し、その中の h3 の文字を読み取って段落に表示します。カードの外を押したときは何もしないよう、早期 return を入れてください。

よくある間違い

引数を書き忘れる

gallery.addEventListener("click", function () { const card = event.target.closest(".work-card"); });

引数がないので event は未定義です。event is not defined で止まります。関数の丸かっこの中に event と書き足します。

closest を挟まずに target を使う

const title = event.target.querySelector("h3").textContent;

説明文を押すと event.targetp で、その中に h3 はありません。null に対して .textContent を読もうとして止まります。まず closest でカードまで戻します。

カードの外を押したときを考えていない

const card = event.target.closest(".work-card"); const title = card.querySelector("h3").textContent;

カードとカードのすき間を押すと cardnull です。if (!card) return; の 1 行を入れるだけで防げます。

課題

  1. HTML の #works の中、.work-list のすぐ下に id="work-message" の空の p を足す
  2. JavaScript で .work-listclick を 1 つだけ登録する
  3. 処理の引数に event を受け取り、event.target.closest(".work-card") で押されたカードを特定する
  4. カードが見つからなければ早期 return し、見つかれば中の h3 の文字を使って「〇〇 を選びました」と表示する

ヒント

index.html
style.css
script.js
プレビュー

できているか

  • #work-message の段落がある
  • #work-message が作品のところにある
  • 第8章で描いた作品カードがそのまま残っている
  • 1 枚目のカードを押すと題名が出る
  • 2 枚目のカードを押すと表示が入れ替わる