CSS4のconic-gradientを先取りする方法

CSS3のグラデーションには線形グラデーションの`linear-gradient`と放射状グラデーションの`radial-gradient`がありますが、[CSS4のWorking Draft](https://www.w3.org/TR/css-images-4/#conic-gradients)には円錐状にグラデーションさせるための`conic-gradient`という項目が存在します。ただし、`conic-gradient`はまだ草案段階なので2017/7時点ではどのブラウザでも使用する事はできません。

<img width="915" alt="スクリーンショット 2017-07-12 23.52.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/57442/7f3851e8-6e04-afb9-6272-ef5561bdea78.png">

今回、デザイナーに相談されて色々と調べてみた結果、いくつかの方法で`conic-gradient`を実現する事ができたので、その方法についてご紹介したいと思います。

# conic-gradientを実現する方法

私の方で調べた限りだと以下の３つの方法が見つかりました。

1. [CSS conic-gradient() polyfill](https://leaverou.github.io/conic-gradient/)を使用する
2. CSSのclipプロパティを使って表現する（[サンプル](https://codepen.io/shankarcabus/pen/jcyDA)）
3. SVGのマスキングを使って表現する（[サンプル](https://codepen.io/zapplebee/details/ByvPMN)）

1はライブラリ依存にはなりますが、簡単＆多機能なので実装方針などで問題なければ一番手軽な手段だと思います。
2はJavaScriptを使わずにCSSだけで表現できるので比較的パフォーマンスで優位な場合がありますが、clipプロパティ依存なのでIEだと正しく動作しません。
今回、私は3の方法で実装してみた（[DEMO](https://codepen.io/osumi/pen/QgZWdM)）ので、その実装方法と仕組みについて解説します。

# SVGのマスキングを使って表現する方法

`conic-gradient`を擬似的に表現する手順はざっくり言うと以下の通りです。各項の詳細については後述します。

1. 円状にグラデーションが適用されたマスキング要素を生成する
2. 図形にマスキングを適用する
3. マスキングされた図形を重ねる

手順の詳細を説明する前に抑えておきたい事柄として、SVGのマスキングについて簡単に紹介しておきます。

## SVGのマスキングとは

SVGのマスキングを使うと明度の度合いによって要素を部分的に透過させる事ができます。明度が低い（黒色に近い）ほど透明になり、明度が高い（白色に近い）ほど不透明になります。
以下のコードは、赤色のrect要素にマスクをかけてグラデーション状に透過させる例です。

```html:HTML
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <defs>
    <linearGradient id="Gradient">
      <stop offset="0" stop-color="white" stop-opacity="0" />
      <stop offset="1" stop-color="white" stop-opacity="1" />
    </linearGradient>
    <mask id="Mask">
      <rect x="0" y="0" width="200" height="200" fill="url(#Gradient)"  />
    </mask>
  </defs>

  <rect x="0" y="0" width="200" height="200" fill="green" />
  <rect x="0" y="0" width="200" height="200" fill="red" mask="url(#Mask)" />
</svg>
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/57442/3d1f5444-4fb8-8811-3d0f-dbb5d522eca2.png)

参考：[クリッピングとマスキング - SVG | MDN](https://developer.mozilla.org/ja/docs/Web/SVG/Tutorial/Clipping_and_masking)

このマスキングを使って、`conic-gradient`を擬似的に表現する方法についてご説明します。

## 1. 円状にグラデーションが適用されたマスキング要素を生成する

この部分がキモになるのですが、rect要素を少しずつ回転させて、且つ回転する毎に白色→黒色に変化させ、以下のようなイメージの図形を描画します。

<img width="462" alt="スクリーンショット 2017-07-09 20.52.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/57442/17f1e688-238c-69cc-7f90-aeda5533a0a3.png">

図形を描画すると円錐状にグラデーションがかかった円が出来上がります。具体的なコード例を以下に記載します。

```html:HTML
<svg id="mySvg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 160 160" version="1.1">
  <defs>
    <mask id="angle">
      <g id="circleMask">
      </g>
    </mask>
  </defs>
</svg>
```

```js:JavaScript
const changedDegree = 255;
let mask = document.querySelector('#circleMask');

function makeRGBA(degree){
  var ratio = 1 - Math.abs(degree / changedDegree);
  var colorVal = Math.floor(255 * ratio);
  var colorArray = [colorVal,colorVal,colorVal]
  return 'rgba('+colorArray.join(',')+',1)';
}

for(i = 1 ; i < changedDegree ; i++){
  let rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
  rect.setAttribute('width', 80);
  rect.setAttribute('height', 80);
  rect.setAttribute('fill', makeRGBA(i));
  rect.setAttribute('transform', 'rotate('+ (i + 90) +' 80 80)');
  mask.appendChild(rect);
}
```

## 2. 図形にマスキングを適用する

1で作成したマスキング要素を特定のSVG要素に適用します。

```html:HTML
<circle r="60" cx="80" cy="80" fill="orange" mask="url(#angle)"/>
```

そうすると、以下のようにマスキングされた状態になります。

<img width="192" alt="スクリーンショット 2017-07-09 18.52.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/57442/2fb3d1c6-fbfb-30aa-fd27-94bb811e3f68.png">

## 3. マスキングされた図形を重ねる

あとはマスキングされたSVG要素と同サイズのSVG要素に重ねると出来上がりです。

```html:HTML
<circle r="60" cx="80" cy="80" fill="red"/>
<circle r="60" cx="80" cy="80" fill="orange" mask="url(#angle)"/>
```

<img width="236" alt="スクリーンショット 2017-07-09 18.57.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/57442/58af99d8-201e-6b4b-2489-2069ad6cc0f7.png">

# 所感

`conic-gradient`を表現するのは思ったより簡単ではありましたが、パフォーマンスや保守性を考慮すると`linear-gradient`、もしくは`radial-gradient`で表現可能なデザインに変更する方が望ましいかもしれません。
CSS4で`conic-gradient`が勧告される日を楽しみに待ちたいと思います。