AdobeXDでCSSグラデーションを書き出すプラグインを作ってみた

## これは
2018/10/15にリリースされた Adobe XD 13 から、プラグインを作れるようになったらしい
JavaScriptで作れて便利そうなので、試しに作ってみた
- GitHub: [CSS Gradient plugin](https://github.com/amotarao/adobexd-css-gradient-plugin)
- 成果物: [adobexd-css-gradient-plugin_0.0.1.xdx](https://github.com/amotarao/adobexd-css-gradient-plugin/releases/tag/v0.0.1)

### 参考にする/したもの
- [はじめてのAdobe XDプラグイン開発！定番のHello Worldを表示させてみよう](https://blogs.adobe.com/creativestation/web-getting-started-with-xd-plugin-development)
- [Adobe XD Platform API Reference](https://adobexdplatform.com/plugin-docs/reference/how-to-read.html)
- [公式プラグインサンプル集](https://github.com/AdobeXD/plugin-samples)

## 開発準備
基本的には、 [誰もが見ているはずのHelloWorldの記事](https://blogs.adobe.com/creativestation/web-getting-started-with-xd-plugin-development) を見れば分かるはずなのでここでは説明しない
本記事では、上の記事の `helloHandlerFunction` を編集するものとして説明する

## 何を作るか
XDにはcssを書き出す機能がないので、それを作ればよさそうと思った
何を自動で生成させるとよさそうかと考えたとき、グラデーションをcssにできれば便利そうだと思った

### Adobe XDで作れるグラデーション

#### 線形グラデーション (linear-gradient)
<img width="300" alt="スクリーンショット 2018-10-17 1.10.18.png" src="https://qiita-image-store.s3.amazonaws.com/0/99161/129e92c2-92f7-76a0-3a75-691039d83ff9.png">

#### 円形グラデーション (radial-gradient)
※今回は作らない
<img width="300" alt="スクリーンショット 2018-10-17 1.10.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/99161/ba1d05a0-38dd-7516-1456-fff48e3e2d15.png">

## 作っていくよ

### とりあえず選択した図形の情報を取ってみる
選択した図形は `selection.items` に配列で入る
なので適当に `console.log` で出してあげれば何かわかりそう

```js
function helloHandlerFunction(selection) {
  console.log(selection.items);
}
```

<img width="200" alt="スクリーンショット 2018-10-17 1.19.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/99161/760aa360-89b6-311b-2fa1-d5e14dac0485.png">
これらの長方形、楕円形、線で試してみる

```
[ Rectangle ('長方形') {
    width: 258, height: 242
    global X,Y: 943, 5
    parent: Artboard ('Web 1920 – 1')
    stroke: ff707070
    fill: LinearGradientFill
  },
  Ellipse ('楕円形') {
    width: 273, height: 273
    global X,Y: 1259, 5
    parent: Artboard ('Web 1920 – 1')
    stroke: ff707070
    fill: fff08c8c
  },
  Line ('線') {
    width: 186, height: 229
    global X,Y: 1581.5, 18.5
    parent: Artboard ('Web 1920 – 1')
    stroke: ff7be539
  } ]
```

長方形の `LinearGradientFill` を見るとグラデーションがわかりそう

### 塗り情報を詳しく見てみる
単純にそれぞれの `fill` を見るだけ

```js
function helloHandlerFunction(selection) {
  const items = selection.items;

  items.forEach(item => {
    console.log(item.fill);
  });
}
```

さっきの図形たちで確認する

```
// 長方形 (グラデーション)
{ endY: 1,
  endX: 1,
  startY: 0,
  startX: 0,
  colorStops: [ { color: [Object], stop: 0 }, { color: [Object], stop: 1 } ],
  sx: 1,
  sy: 1 }

// 楕円形 (ベタ塗り)
{ value: 4293954700 }

// 線 (塗りの概念なし)
null
```
長方形は `LinearGradientFill` の詳細が見えた
[リファレンスはこちら](https://adobexdplatform.com/plugin-docs/reference/LinearGradientFill.html)

<img width="120" alt="スクリーンショット 2018-10-17 1.35.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/99161/0604f541-8cc6-e42c-ab0a-e7f3f7f29da9.png">
`startX` `startY` `endX` `endY` は、長方形の左上を `(0,0)` 右下を `(1,1)` としたときのグラデーション位置の座標
なお、 `sx` `sy` は、リファレンスに情報がないので謎
`colorStops` を見ると、色の設定がわかりそう

ちなみに、
ベタ塗りの楕円形は `#F08C8C` の上2桁に alpha情報 `FF` をつけた `FFF08C8C` の10進数
線は塗りの概念がないのでnull

塗りの数値をrgbaにするコードがこちら
~~多分もっといい書き方があると思う~~
~~(追記編集) String.slice 使うほうがいいじゃん~~
(更に追記編集) `Color.toRgba()` が用意されていた

```js
/**
 * 色をcssのrgba()に変換
 * @param {Color} color 変換する色
 */
function toCssRgba(color) {
  const {r, g, b, a} = color.toRgba();
  return `rgba(${r}, ${g}, ${b}, ${a})`;
}
```

### グラデーションの色情報を見てみる
`colorStops` を詳しく見てみる
またここからは楕円形、線は見ないことにする

```js
function helloHandlerFunction(selection) {
  const items = selection.items;

  items.forEach(item => {
    console.log(item.fill.colorStops);
  });
}
```

```
[ { color: { value: 4280795042 }, stop: 0 },
  { color: { value: 4282601983 }, stop: 1 } ]
```
色が切り替わるポイントの位置と、その位置の色が、ポイントの配列で分かる
`stop` がグラデーションの線分を1としたときの位置
パーセンテージにすればcssでそのまま利用できる
また、`color.value` で色が分かる

### グラデーションの角度を求める
長方形の幅、高さ、グラデーション線分の座標が取得できているので、計算するだけ
[2点間の距離と角度と座標の求め方](https://qiita.com/Hoshi_7/items/d04936883ff3eb1eed2d) を参考にした

```js
function helloHandlerFunction(selection) {
  const items = selection.items;

  items.forEach(item => {
    const startX = item.width * item.fill.startX;
    const startY = item.height * item.fill.startY;
    const endX = item.width * item.fill.endX;
    const endY = item.height * item.fill.endY;
    const deg = Math.atan2(endY - startY, endX - startX) * 180 / Math.PI + 90;
    console.log(`${deg}deg`);
  });
}
```

```
133.16716049405795deg
```

そのまま `linear-gradient()` にいれる角度として使える
また90度を足さないと、正しい角度にならないっぽいので足す

### cssに変換する
今まで出した情報を `linear-gradient()` にいれていくだけ

```js
function helloHandlerFunction(selection) {
  const items = selection.items;
  const css = items.map(item => {
    // degを求める
    const startX = item.width * item.fill.startX;
    const startY = item.height * item.fill.startY;
    const endX = item.width * item.fill.endX;
    const endY = item.height * item.fill.endY;
    const deg = Math.atan2(endY - startY, endX - startX) * 180 / Math.PI + 90;

    const colors = item.fill.colorStops
      .map(point => {
      // 色の10進数の数値を16進数の文字列に変更
      const color = toCssRgba(point.color);
      return `${color} ${point.stop * 100}%`;
       })
      .join(', ');

    return `.${item.name} { background: linear-gradient(${deg}deg, ${colors}); }`;
  });

  console.log(css.join('\n'));
}
```

これを実行すると

```css
.長方形 {
  background: linear-gradient(133.16716049405795deg, rgba(39, 191, 162, 255) 0%, rgba(67, 81, 255, 255) 100%);
}
```

無事 `linear-gradient` を取得できた

#### XDのグラデーション
<img width="304" alt="スクリーンショット 2018-10-17 2.06.36.png" src="https://qiita-image-store.s3.amazonaws.com/0/99161/e54ba1c4-ad80-2592-b8cc-64214d82f34f.png">

#### 出力したcssを適用させた\<div\>
<img width="266" alt="スクリーンショット 2018-10-17 2.07.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/99161/37272bef-99a9-bd5f-67af-92a8403a18b0.png">

**完全に一致**

## 今できていないこと

### グラデーション線分の位置が中途半端なときの計算
<img width="200" alt="スクリーンショット 2018-10-17 2.10.20.png" src="https://qiita-image-store.s3.amazonaws.com/0/99161/74ed905c-86e5-1c95-5646-4fb0395ac788.png">

グラデーションの開始・終了位置が変わるため、`linear-gradient` での色の位置を調整する必要がある
このまま出力するとこんなになり、全然違う

<img width="200" alt="スクリーンショット 2018-10-17 2.12.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/99161/42897f67-8c65-ba85-5fdc-932492a6cadc.png">

### radial-gradient
多分できるが、寝たいのでやらなかった

## プラグイン作成に便利そうなもの
### AdobeXD typings
AdobeXD公式が [typings](https://github.com/adobeXD/typings) を提供しており、TypeScriptでの開発も柔軟にできそう

## 終わりに
煮るなり焼くなりしていただけると幸い

- GitHub: [CSS Gradient plugin](https://github.com/amotarao/adobexd-css-gradient-plugin)
- 成果物のDLページ: [adobexd-css-gradient-plugin_0.0.1.xdx](https://github.com/amotarao/adobexd-css-gradient-plugin/releases/tag/v0.0.1)



プラグインの開発のしやすさがとても気持ちよかった
[はじめてのAdobe XDプラグイン開発！定番のHello Worldを表示させてみよう](https://blogs.adobe.com/creativestation/web-getting-started-with-xd-plugin-development) を見ればいいが、プラグインの再読込がショートカットでできたり、デベロッパーコンソールもとてもいい機能

AdobeXDは Sketchと比べるとまだまだ機能が少ないが、少ないからこそシンプルだったり、軽かったり、プロトタイピング機能が便利だったりするので、使うプロジェクトも増えてきていると思う
そこでアホみたいな手作業をプラグインによって自動化できれば、楽しい未来しか見えてこないのではないかと感じた