一発芸！SVGでHTMLを画像化する

## お詫びと訂正（2016年12月5日追記）

記事初出時に React v15.3.2 では `foreignObject`要素がサポートされてない、と記しましたがこちらは誤りでした。
[React v15.0.0 のリリースノート](https://github.com/facebook/react/releases/tag/v15.0.0)にてSVGの全ての要素をサポートした、とあるのですが、このタイミングではまだ[公式ドキュメント](https://facebook.github.io/react/docs/hello-world.html)が更新されていませんでした。
また v15.2.x では一部の属性が使用不可と判定されてしまって、コンポーネントのレンダリング自体は意図通りになるものの使用には不安を感じさせる挙動となっていました。

![capture.png](https://qiita-image-store.s3.amazonaws.com/0/36521/011a3b3c-ff97-593a-96ce-2042f1c97190.png)
React V15.2.x では`use`要素や`foreignObject`要素の子要素に必要な`xmlns:*`属性が使用不可であると警告される

しかし、最近になって公式ドキュメントのリライトが始まって、もともと存在した「Reactで使用できる要素と属性の一覧」ページが思いっきり削除されました（[当該コミット](https://github.com/facebook/react/commit/455d2d1b48e5cdaeac5d0b4fd92b29b4d52bcaec#diff-7de5a2c6a6ac0642ff9129ad05eed631)）。
そこで気になって現時点で最新バージョンである v15.4.0 で試したところ、`use`要素や`foreignObject`要素が前述の警告もなく普通に使用できたので自分の勘違いに気付いた次第です。

手元でバージョンをさかのぼって試したところ、v15.3.0 以上ならば問題なく動作するようです。
したがって記事初出時の記載は完全に誤りです。
ここに訂正しお詫びいたします。
大変失礼しました。

---
## このスライドについて

2016年10月19日（水）
~~SVGおじさん~~ 松田さん 主催
「まぼろしのSVG勉強会 その2」 発表資料  
いまさらなネタかもしれないけど許せ  

※ 「まぼろし」というのは[弊社](https://maboroshi.biz/)のことです

---
## これはHTMLから作った画像です

![capture.png](https://qiita-image-store.s3.amazonaws.com/0/36521/9b54f216-0905-a6b2-dd94-a878ba4837d1.png)

しかもフロントだけで実現可能！

---
## デモ作った

[react-dom-imager-demo](https://github.com/haribote/react-dom-imager-demo)

```bash
git clone git@github.com:haribote/react-dom-imager-demo.git
```

```bash
cd react-dom-imager-demo && npm install
```

```bash
npm start
```

### 

---
## いきなり種明かし

### HTMLを画像化する手順
1. 画像化したいHTMLを作る
2. (1)のHTMLをSVGの`foreignObject`要素で囲む
3. (2)のSVGを`canvas`要素に描画する

### MDNに全部書いてある
[DOM オブジェクトを Canvas に描画する - HTML | MDN](https://developer.mozilla.org/ja/docs/Web/HTML/Canvas/Drawing_DOM_objects_into_a_canvas)

---
## コード例

```html
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 1280" width="960" height="1280">
  <style>
    h1 {
      color: red;
    }
  </style>
  <g>
    <foreignObject x="0" y="0" width="100%" height="100%">
      <div xmlns="http://www.w3.org/1999/xhtml">
        <h1>Hello, world!</h1>
      </div>
    </foreignObject>
  </g>
</svg>
```

---
## SVGをCanvasに描画する
```JavaScript
// canvas要素とコンテキスト、SVGを読み込むimage要素を用意
const canvasEl = document.getElementById('canvas');
const context  = canvasEl.getContext('2d');
const imageEl  = new Image();

// SVGコードをBlobにしてURLを発行する
const staticSvgBlob = new Blob(['<svg xmlns...'], { type: 'image/svg+xml' });
const url           = URL.createObjectURL(staticSvgBlob);

// SVGが読み込まれたらcanvas要素に描画する
imageEl.onload = () => {
  context.clearRect(0, 0, canvasEl.width, canvasEl.height);
  context.drawImage(imageEl, 0, 0);
  URL.revokeObjectURL(url); // URL解放
};

// CORS設定をしてSVGを画像として読み込む
imageEl.crossOrigin = 'Anonymous';
imageEl.src = url;
```

---
## 最重要ポイント
Canvasに描画するSVGは完全にスタンドアロンでなくてはならない

つまり描画に必要な情報はすべてぶっ込むべし！

---
## BlobにするときのMIME

```JavaScript
new Blob(['<svg xmlns...'], { type: 'image/svg+xml' });
```

文字コードは不要

MDNに書かれている通りに
`image/svg+xml;charset=utf-8`
とすると一部のブラウザでSVGがぶっ壊れる

（なんでだろう？）

---
## ルートのSVGに必要な属性
- `xmlns`
- `viewBox`
- `width`
- `height`

---
## CSSの埋め込み
`style`要素にCSSを文字列展開して直接ぶっ込む！

```html
<style>
  h1 {
    color: red;
  }
</style>
```


---
## 画像の埋め込み
Base64エンコードしてDataURLでぶっ込む！

```html
<img src="data:image/png;base64,iVBOR..." width="400" height="382" alt="" />
```

---
## フォントとか
画像同様にDataURLにする必要がある

- データの再頒布にあたるので著作権とか注意
- アイコンはフォントじゃなくてSVGでおｋ

---
## 残念なお知らせ
- IEってなんですか？ :sleepy:
- `foreignObject`要素の含まれたSVGをCanvasに描き出すと`tainted`とされて`toDataURL`などが使えなくなる
    - Chrome, Safari だけ？
    - ちゃんと調べてない :stuck_out_tongue_winking_eye:
    - chromiumのissueは[こちら](https://bugs.chromium.org/p/chromium/issues/detail?id=294129)
    - Canvasのtaint（汚染）については[こちら](https://developer.mozilla.org/ja/docs/Web/HTML/CORS_enabled_image)
- ~~Reactだと`foreignObject`要素がサポートされてないから`dangerouslySetInnerHTML`でぶっ込まざるをえない :name_badge:~~
    - ~~v15.3.2 時点~~
    - ~~将来的にはサポートされるはず~~

※1ページ目の訂正をお読みください。

---
## ありがとうございました :bow: