Vue.jsでSVGの生成とPNG化をするメモ

Vue.jsでSVGの生成とPNG化をするメモ（備忘録）

SVGで図形や文字をはめ込みPNG化する仕様を実装したのでメモとして残す。

# やりたいこと
SVGタグの中に動的要素を配置してそれを画像化したい
```Html
<svg width="50" height="50" viewBox="0,0,50,50" xmlns="http://www.w3.org/2000/svg">
    <!-- ここを動的に生成したい -->
    <rect x="10" y="10" width="10" height="10" fill="#ccc"/>
</svg>
```
# SVGタグを生成してブラウザで表示

## 要素を管理するオブジェクトを用意

今回は四角形とテキストを使う

```typescript
interface State {
  svgItem: (SvgItemRect | SvgItemText)[];
}
//SVGの四角形用
interface SvgItemRect {
  type: 'rect';
  x: number;
  y: number;
  width: number;
  height: number;
  rx: number;
  ry: number;
  fill: string;
}

//SVGのテキスト用
interface SvgItemText {
  type: 'text';
  x: number;
  y: number;
  fontSize: number;
  fill: string;
  text: string;
}

const state = reactive<State>({
  svgItem: [
    //初期値入れてみた
    {
      type: 'rect',
      x: 20,
      y: 20,
      width: 100,
      height: 100,
      rx: 0,
      ry: 0,
      fill: '#849ff0',
    },
    {
      type: 'text',
      x: 100,
      y: 20,
      fontSize: 20,
      fill: '#e74c3c',
      text: 'sample',
    },
  ],
});
```

## テンプレート側
```vue
<svg
    width="400"
    height="400"
    viewBox="0, 0, 400, 400"
    xmlns="http://www.w3.org/2000/svg">
    <template v-for="(row, index) in state.svgItem" :key="index">
        <template v-if="row.type === 'rect'">
            <rect
            :x="row.x"
            :y="row.y"
            :width="row.width"
            :height="row.height"
            :rx="row.rx"
            :ry="row.ry"
            :fill="row.fill"/>
        </template>
        <template v-if="row.type === 'text'">
            <text
            :x="row.x"
            :y="row.y"
            :font-size="row.fontSize"
            :fill="row.fill"
            font-family="serif">
            {{ row.text }}
            </text>
        </template>
    </template>
</svg>
```
## 結果
ブラウザで確認できる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3270885/c11c5a59-79a2-bb3d-8828-aeffbe44cfee.png)


# SVGタグからDataURLを生成

## svgタグを格納するrefを追加
```typescript
const svgElement = ref();
```

## svgタグにrefをセット
```diff:
<svg
    width="400"
    height="400"
    viewBox="0, 0, 400, 400"
    xmlns="http://www.w3.org/2000/svg"
+   ref="svgElement"
>
```

## SVGタグからSVGのDataURLを生成する関数
※btoaやunescapeが非推奨になっている・・・・

```typescript
import { Buffer } from 'buffer';
const ToBase64 = (elm: HTMLElement): string | null => {
    const svgData = new XMLSerializer()
      .serializeToString(elm)
      .replace(/<!--[\s\S]*?-->/g, '');//一応<!--v-if-->がいらないから消す
    const dataUrl =
      'data:image/svg+xml;charset=utf-8;base64,' +
      // btoa(unescape(encodeURIComponent(svgData))) //昔はこれで良かった
      Buffer.from(svgData).toString('base64');//今の方法は？
    return dataUrl;
};
```

## SVGのDataURLをImgオブジェクトに変換する関数
```typescript
const ToImage = (base64img: string): Promise<HTMLImageElement | null> => {
  return new Promise((resolve) => {
    const img = new Image();
    img.onload = () => resolve(img);
    img.onerror = (e) => resolve(null);
    img.crossOrigin = 'anonymous';
    img.src = base64img;
  });
};
```


## Canvasにimgを描画してPNGデータを取得

上の関数を使いPNGデータを生成する
```typescript
const createPngImg = async () => {
  let canvas: HTMLCanvasElement | null = null;
  let ctx: CanvasRenderingContext2D | null = null;
  try {
    const svgDataUrl = ToBase64(svgElement.value);
    if (svgDataUrl === null) return null;
    const img = await ToImage(svgDataUrl);
    canvas = document.createElement('canvas');
    canvas.width = 400;
    canvas.height = 400;
    ctx = canvas.getContext('2d');
    if (img === null || ctx === null) return null;
    ctx.drawImage(img, 0, 0);
    return canvas.toDataURL('image/png');
  } catch (error) {
    console.error(error);
    return null;
  } finally {
    canvas = null;
    ctx = null;
  }
};
```


# 最終的に 
あとは最初に用意したリアクティブな要素を操作できるようにすれば幸せになれるはず・・・・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3270885/73e4561b-42ab-7a45-892a-fcfe69d463ad.png)

# 最後に
SVGはタグですからもちろん・・・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3270885/4d50474d-3147-43ad-8b75-12ec636f227d.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3270885/5a579882-9879-3881-ac2a-650c13f83324.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3270885/3f24ab3a-bd6b-8267-d368-a2a4859fe4e8.png)

**イベントが使えます。**
ということは、ドラッグ操作やクリック、マウスホイールなどで自在にSVGが操れますね（幸せ）


# GitHubにサンプル上げてます
https://github.com/hare-systems-ryo/vue-svg-sample

# ついでにGitHubにデモページも・・・
https://hare-systems-ryo.github.io/vue-svg-sample/