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);
});引数の名前は何でも動きますが、event か e と書くのが慣習です。この教材では読みやすさを優先して event で統一します。
eventは自分で作って渡すものではありません。ブラウザが作って渡してきます。だから引数を書き忘れてもundefinedになるだけでエラーにはならず、次の行のevent.targetではじめて止まります。
よく使う 3 つのプロパティ
event の中身はイベントの種類によって変わりますが、どのイベントでも使えるものが 3 つあります。
| プロパティ | 何が入るか | 使いどころ |
|---|---|---|
| target | 実際に出来事が起きた、いちばん内側の要素 | どれが押されたかの判定 |
| currentTarget | addEventListener を付けた側の要素 | まとめ役の要素そのものを触りたいとき |
| type | イベントの種類の文字列。"click" など | 1 つの関数で複数の種類を受けるとき |
target と currentTarget の違いが、この回のいちばんの山です。作品カードの中には h3 と p が入っています。カードの文字のところを押すと、いちばん内側で出来事が起きたのは p です。つまり event.target は p になります。カードそのものではありません。
いっぽう 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 を付けたいときなどに使います。委譲を使わず要素ごとに登録している場合は、target と currentTarget がたいてい同じものを指すので、違いを意識する機会がありません。
eventには他にも情報が入っています。マウスの座標を持つclientXとclientY、押されていた修飾キーを表すshiftKeyやctrlKeyなどです。使う場面が来たときに調べれば十分ですが、eventをconsole.logに出して中身をながめておくと、どんな情報が取れるのか手触りがつかめます。
今回書くこと
作品のところに id="work-message" の空の段落を足します。JavaScript では、カード 1 枚ずつではなく .work-list に click を 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.target は p で、その中に h3 はありません。null に対して .textContent を読もうとして止まります。まず closest でカードまで戻します。
カードの外を押したときを考えていない
const card = event.target.closest(".work-card");
const title = card.querySelector("h3").textContent;カードとカードのすき間を押すと card は null です。if (!card) return; の 1 行を入れるだけで防げます。
課題
- HTML の
#worksの中、.work-listのすぐ下にid="work-message"の空のpを足す - JavaScript で
.work-listにclickを 1 つだけ登録する - 処理の引数に
eventを受け取り、event.target.closest(".work-card")で押されたカードを特定する - カードが見つからなければ早期 return し、見つかれば中の
h3の文字を使って「〇〇 を選びました」と表示する