🍜 デザイナーに「おいおい、そんな気軽に三角形のボタンを作るなよ。」と思った時に見るページ
作成日: 2021/09/04
1
読み飛ばして良いまえがき

現実世界のお仕事は、必ずしも自分のやりやすいフローで進むとは限りません。
自分が参画したときには、すでにデザインが完成していて承認済み。さらにデザイナーは離脱後。 ……ということも、よく起こることではあると思います。
その時に、HTMLやCSSについてよく知っているデザイナーが作ってくれたものであれば何の問題もないのですが…… あまりWebに詳しくないデザイナーさんだと、そんなところにそんなカタチのボタンは置けないよ…… みたいなことも、極稀によく起こります。
今回は、そんなあなたのための解説です。



三角のボタンを作るには?

おそらく一番最初に思いつくのは透過GIFや透過PNGを使うことになると思います。四角形のボタンの上に、三角形の透過画像を置いて、両方ともにリンクを設定したと仮定します。その状態がこんな感じ。
hitarea_non.png

しかし、これには問題があります。
HTMLやCSSの世界は、基本的に「矩形」から逃れられないため、上に重なった三角形の透過画像の透明部分あたりにポインタを置いた場合、ユーザーは下の四角形のボタンを選んでいるつもりが、コード上は上の三角形を表現した画像の領域に入ってしまい、下の四角形を選択することはできません。(ピンクの領域が、その三角形を表現した透過画像の透明部分です。)
hitarea.png
この状態のCodePenは https://codepen.io/kaitou1192/pen/QWgKbJE

SVGを使って解決する方法

さて、早速ですが解決方法その1です。
SVG経由で画像を読み込み、その画像に clipPath を設定して、ヒットエリアを制御しようという方法です。
サンプルのCodePenは https://codepen.io/kaitou1192/pen/BaZLNXo

肝のところだけ抽出します。

<div class="sankaku">
  <svg width="200" height="200"
  xmlns="http://www.w3.org/2000/svg">
    <clipPath id="triangle">
      <polygon points="0,0 200,0 0,200" />
    </clipPath>
    <a href="#" clip-path="url(#triangle)">
        <image href="https://drive.google.com/uc?export=view&id=1LYx6-7KZRAGTFT0y5T-Ed-FqA4twy3Y-" />
    </a>
  </svg>
</div>
.sankaku {
  pointer-events: none;
}
svg a {
  pointer-events: visible;
}

三角形を作るためのSVGをくくっている div.sankaku に pointer-events: none; をかけて、この div 自体にはポインタが反応しないようにしています。
ただし、svg の中の a の見えている箇所に関しては pointer-events: visible; で、ポインタが反応するようにしています。
(pointer-eventsについて詳しく知りたいという方は こちら を参照してください。)

ただSVGに不慣れな方だと、ここはちょっと意味不明だと思うので補足します。
ここでは三角形を表現している透過画像に、あらためてSVGのclipPathで見える場所の制御をかけています。つまりclipPathで設定しているpolygonの範囲が、SVG的には見えている箇所ということになります。(人間が見えている箇所という意味では透過画像のときからすでに三角形なんですが、何もしないままだとSVG的には透明部分も含めての四角形の画像と解釈します。)
また今回はpolygonで指定しましたが、別にcircleでもpathでも構いませんので、実際には三角形に限らず、好きな形のボタンが作れるというわけです。
(clipPathについて詳しく知りたいという方は こちら を参照してください。)

JSを使って解決する方法

解決方法その2です。
SVGが使えないというシチュエーションが、極稀によく起こりますが、そういう場合にはやっぱりJSを使って解決することになります。
サンプルのCodePenは https://codepen.io/kaitou1192/pen/yLXaYOP

同じく肝のところだけ抽出します。

<div class="wrapper">
  <div class="shikaku"><a href="#"><img src="https://drive.google.com/uc?export=view&id=1-ToENR95Te20ZM-VMKRU0lKveQCLkla3"></a></div>
  <div class="sankaku"><a href="#"><img src="https://drive.google.com/uc?export=view&id=1LYx6-7KZRAGTFT0y5T-Ed-FqA4twy3Y-"></a></div>
</div>
const wrapper = document.querySelector('.wrapper');
const sankaku = document.querySelector('.sankaku');

wrapper.addEventListener('mousemove', (event) => {
  if(event.offsetX + event.offsetY < 200) {
    sankaku.removeAttribute('style');
  } else {
    sankaku.setAttribute('style','pointer-events: none;');
  }
});

先ほどまでとは異なり、1つ親に div.wrapper というのを設定しています。
領域内でのポインタの座標が知りたいので addEventListener mousemove を使うのですが、pointer-events: none; の状態では mousemove は、反応しません。したがって、div.sankaku の pointer-events を制御するために、親でのポインタの座標の位置を調べて処理を振り分けています。




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

pointer-events は、あくまでもポインタのイベントを制御しているだけというのは忘れがちです。
これは a に pointer-events: none; をかけている状態なのですが、キーボードで操作するとちゃんと選択でき、ここに設定した動きもちゃんとしてしまいます。none して油断していると、思わぬ操作につながってしまうので、扱う際には要注意です。
hitarea_key.png

せっかくなのでこの状況のCodePenも用意しました。実際に触って確かめていただければ。 🙇🏻‍♂️
https://codepen.io/kaitou1192/pen/dyRpNGL




今回の続きで canvas と JS を組み合わせて、普通のaタグのヒットエリアを制御するというものも書いてみました。
https://ticketnote.dev/ticket/wBQpE0n3fV1Io7ifOUvF

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