Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails以外の開発一般

画像のボタンがクリックできない?target プロパティについての初歩的な勘違い

TL; DR

click イベントでボタンの要素を参照したい場合は target プロパティではなく currentTarget プロパティを使う。

顛末

事の発端

あるプロジェクトで、SVG画像を埋め込んだボタンのクリックイベントが作動しないというので、img 要素に pointer-events: none を設定しているコードをレビューしました。試してみると、確かに pointer-events: none を設定しないとボタンが作動しません。再現コードは以下のようなものですが、JavaScript はレビュー範囲外の既存箇所に属していました。当時は私も原因が分からなかったので、そういうものかと思って Approve してしまいました。

再現コード

HTML
<a href="#" class="button my-action">
  <img src="icon.svg" alt="アイコン">
</a>
SVG
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg">
  <rect x="0" y="0" width="24" height="24" fill="blue"/>
</svg>
CSS
a.button img {
  pointer-events: none; // これがないとボタンが作動しない
}
JavaScript
document.querySelector('.button').addEventListener('click', e => {
  e.preventDefault();
  if (e.target.classList.contains('my-action')) {
    alert('押したよー!');
  }
});

違和感

一方、別のあるコードでは同様にSVG画像をボタンに埋め込んでいるにも関わらず、pointer-events: none なしで動作していました。そのボタンのコードは次のようなものでした。

<a href="https://www.google.com" class="custom-button">
  <img src="icon.svg" alt="アイコン">
</a>

どちらのボタンも本質的には click イベントを発動させるものであることは同じで、後者では prevent しなかった default action として href 属性に書かれたURLに画面遷移するものなので、前者だけ pointer-events: none が必要になるというのは納得がいきません。

原因解明まで

そこで、Claude Sonnet 4 に相談してみました。

CSSで、a 要素内のSVGのパーツが pointer-events を取ってしまう場合とそうでない場合があります。違いはなんでしょうか?

すると、「SVG内に fill 属性があると pointer-events を取ってしまうのでは?」という仮説が得られました。SVG内 rect 要素の fill 属性を次のように除去して再度実験してみます。

<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg">
  <rect x="0" y="0" width="24" height="24"/>
</svg>

…変わりません。続けて質問しました。

SVGを img 要素で埋め込んでいるのですが、同様ですか?

a 要素に click イベントを設定している場合に click イベントがいかないのかも
click イベントを設定せずに href で飛ばしているだけだと想定通り動いてるぽいです

すると、Claude Sonnet 4 は色々と考察するのですが、その中で次のように述べました。

img 要素自体がポインターイベントを受け取る

img 要素が e.target になる

e.target だと…?確かに、pointer-events: none がないと動かないケースでは Event オブジェクトの target プロパティを参照しています。ですが、我々のコードで想定している target プロパティの内容は a 要素であり、img 要素ではありません。そこで、デバッガで実際の動作を見てみたところ、確かに e.target は img 要素になっていることが分かりました。つまり、原因はイベントハンドラの e.target.classList.contains('my-action') という判定部分にありました。

この結果を伝えると、Claude Sonnet 4 は3つの解決策を提案します。

  1. pointer-eventsで解決(推奨)
  2. イベントハンドラで対応…: e.currentTarget
  3. イベント委譲で対応…: e.target.closest('a')

ここまで来ればもう分かりますね。正解はどう考えても2の currentTarget プロパティです。リファレンスを貼っておきます。

ちなみに、fill 属性云々は全く関係なく、画像ファイルをPNGやJPEGにしても同様に target プロパティは img 要素になります。この辺は Claude に問題を投げる時点で切り分けておけば混乱を減らせたかもしれません。

愚痴

そもそも target と currentTarget っていう命名はどうなのよ?



CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。