🍜 複雑なヒットエリアはSVGの専売特許じゃないんだぜ!と言ってcanvasを持ち出す話
作成日: 2021/09/05
1
読み飛ばして良いまえがき

前回の記事を書いた際「このサンプルでは、基本SVGでやる方が良いように見えるよな……。」と読めるような気がしたので、他の方法でもちゃんと複雑なヒットエリアを制御できるよ!という解説です。



早速本題

スクリーンショット 2021-09-05 14.55.46.png
左のボタンは普通にaタグと画像で作った四角いボタンなんですが、右の星はcanvasを使用して描画し、そのcanvasの黒い部分が左側のボタンでマウスオーバーやクリック(スマホではタップ)が反応する場所、白い部分が反応しない場所となり、四角いボタンなのにヒットエリアは星型というものです。
サンプルのCodePenは https://codepen.io/kaitou1192/pen/vYZXpmx に置いてあるので、実際に触っていただいたほうがイメージがつきやすいと思います。

さて、例によって肝のところだけ抽出します。

<div class="wrapper">
  <div class="buttonArea">
    <a href="https://www.yahoo.co.jp/" target="_blank" rel="noopener">
      <img src="https://drive.google.com/uc?export=view&id=1f0kfVAqAe7Yescjqk6uzxTEqpTh6qXBR">
    </a>
  </div>
  <div class="settingArea">
    <canvas width="200" height="200" />
  </div>
</div>
const buttonArea = document.querySelector('.buttonArea');
const a = document.querySelector('.buttonArea a');

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');

【中略】

buttonArea.addEventListener('mousemove', (event) => {
  const colorData = ctx.getImageData(event.offsetX, event.offsetY, 1, 1);
  
  if(colorData.data[3] > 0) {
    a.removeAttribute('style');
  } else {
    a.setAttribute('style','pointer-events: none;');
  }
});

div.buttonArea 上のマウス座標を offsetX と offsetY で取得し、canvasにおける同じ座標の色情報(今回はアルファチャンネル)をみて、その内容によって div.buttonArea 内の a の pointer-events: none; をつけたり、外したりして制御しています。
(canvasについて詳しく知りたいという方は こちら を参照してください。)

これだけだと前回の SVG のやつの方が良いんじゃないの?ということになるんですが、canvasではPNG etc.の外部画像をcanvasに読み込んだ上で、その座標の色を取得するということができます。
今回のサンプルがパスで書かれているのは、canvasに外部画像を読み込んだ上でJSで操作しようとするとクロスオリジンの制限に引っかかってしまうため…… 今回は泣く泣くパスにしていますが、同じドメイン内の画像etc.でクロスオリジンの問題に引っかからないようにすれば大丈夫です。

色に関しては、今回は colorData.data[3] という指定でアルファチャンネルを情報をみていますがRGBAで取得できます。

const r = colorData.data[0];//赤 0 〜 255
const g = colorData.data[1];//緑 0 〜 255
const b = colorData.data[2];//青 0 〜 255
const a = colorData.data[3];//透明度 0 〜 255

(canvasの色の取得方法について詳しく知りたいという方は こちら を参照してください。)

あと、今回はわかりやすいようにボタンとヒットエリアを並べたサンプルを作りましたが、canvasの位置を position: absolute;left: -9999px;みたいな指定で表示領域外に出してしまえば、普通に閲覧しているユーザーからこのcanvasが見えることはありません。




せっかくなので他の部分のワンポイント解説

今回はcanvasが登場したので vertical-align: top; について。
サンプルのコードには下記のような箇所があると思います。

img, canvas {
  vertical-align: top;
}

この vertical-align の指定を取ってしまうとこんな感じになります。
サンプルのCodePenは https://codepen.io/kaitou1192/pen/vYZXpod に置きました。
スクリーンショット 2021-09-05 11.36.36.png
画像には、何か vertical-align を指定しなさいということが書かれていることが良くありますが、canvasも同様で vertical-align は baseline 以外の何かを指定しないと余白ができることがあります。日本語を使っているとあまりわかりませんが、baselineは大雑把に説明するとアルファベットの「a」や「e」の下端にあわせて、位置をそろえろという指定になります。この際「y」とか「g」というのは、「a」や「e」の下端よりも下に出る部分がありますが、この下に出る部分が確保されている状態となります。つまり baseline 以外のものを指定するとその部分が考慮されなくなる結果、余計な余白が無くなったと感じる挙動になります。
これは display: inline-block; etc.の…… 大雑把に言うとHTML5より前でいうところのインライン要素に属するもので発生するので要注意です。
(vertical-alignについて理解しやすいのは こちら だと思います。参考にしてみてください。)



こういうメモをTwitterにも書いているので良かったらフォローしてください。✨
https://twitter.com/Kaitou1192/status/1434333484214992900

本業はコードを書かせてもらえないフロントエンドエンジニアです。 こんなサービス作っています。 https://lp.re-shine.jp