図形による画像のクリップをクロスブラウザで実現する

2017年5月10日追記:
この記事の内容は全体的に古いので、クロスブラウザ周りの情報はあまりあてにしないでください。

Webサイト上で、画像を六角形にクリップしたいということがあった。
やり方はいろいろあるけど、クロスブラウザで動作するようにと考えるとSVGのclipPathを使った方法がよさそうだった。
ただ、ブラウザによる解釈の違いではまるところもあったので備忘録として書いておく。

まず、clipPathを定義するSVGをHTML内にインラインで書く。
外部ファイルに記述することもできるけど、クロスブラウザでは動作しない。
以下のコードは六角形にくり抜くclipPath。

<svg width="0" height="0" style="position: absolute;">
  <defs>
    <clipPath id="hexagon" clipPathUnits="objectBoundingBox">
      <polygon points=".5,0 .933,.25 .933,.75 .5,1 .067,.75 .067,.25"/>
    </clipPath>
  </defs>
</svg>

clipPathUnits="objectBoundingBox"という属性をつけると、要素に対して相対的にクリップできる。
これを使わない場合だと、クリップするためのSVG要素をpxで指定することになって、伸縮する要素に対応できない。
インラインスタイルでposition: absolute;を指定しているのは、これがないとこのSVG要素分の空間ができてしまうので、この指定で高さを無くしている。
あと、このclipPathを参照できるようにIDをつけておく。
この記述を、HTML内のどこに書いても問題なさそうだけど、bodyの終了直前に書くのが多いっぽい。

次に、実際にクリップされた要素を配置する場所にSVGを記述する。

<svg width="500" height="500">
  <image class="clipped" xlink:href="./img/photo.jpg" width="100%" height="100%"/>
</svg>

クリップする要素にCSSから参照する用のクラスをつけておく。 複数の要素をクリップするときはg要素とかで囲ってやるといい。

<svg width="500" height="500">
  <g class="clipped">
    <image xlink:href="./img/photo.jpg" width="100%" height="100%"/>
    <rect width="100%" height="100%" style="fill: rgba(0,0,0,.8);"/>
  </g>
</svg>

そして、次のようなCSSを書く。

.clipped {
  clip-path: url("#hexagon");
}

-webkit-をつけるとSafariで動作しないので外しておく。
Autoprefixerとかを使ってると勝手につけてくれるのでそれも考慮しておかないといけない。
PostCSSの設定で特定のプロパティだけ無視するみたいなことができるかもしれないけど、やり方がわからないので、僕はAutoprefixerの実行後に不要なベンダープリフィックスを削除するようにしてる。

あと、Firefoxではまるところがあって、clipPathのurlとして指定するIDの前に、CSSファイルの位置から見たHTMLファイルまでのパスを書く必要がある。

css/
- style.css

index.html

みたいな階層だと、

.clip-path {
  clip-path: url("..#hexagon");
}

という風に書いたりする。ルートまでの指定だったら

.clip-path {
  clip-path: url("/#hexagon");
}

とか。
この辺で複数ファイルがあったときの挙動とかは調べてないけど、どうしても問題があればHTML内にインラインCSSで書いてやれば動くはず。

デモ作った

で、実際に作ったものとソース)はもうちょっと複雑だったけど、CSSのpositionを駆使すれば実現できるレベルだった。
とはいえ若干手間でわかりにくいので、IEとかもclipPathの実装はがんばってほしいとしか言いようがない。