Canvas2Dについてさっくり復習してみた


# Canvas2Dについてさっくり復習してみた

## 概要

ryokio0129です。

JavaScript Advent Calendar 2020 6日目になります。
初参加です。よろしくお願いいたします。

canvasへの描画に関して、昨今色々なフレームワークやライブラリがあり、そちらを使うことが多いと思います。
かくいう私も、仕事では [Phaser](https://phaser.io/) というHTML5ゲームフレームワークを使っていることが多いです。

そんな中、たまたま素のCanavs2Dを触らないといけない状況が発生したので、さっくり復習していきます！

## Canvas2Dをさっくり復習するどん

### Canvas2Dのコンテキスト取得

WebGLにしてもCanvas2Dにしても、`HTMLCanvasElement`からコンテキストを取得しないと描画できないです。
Canvas2Dに関しては`CanvasRenderingContext2D`を取得します。

<p class="codepen" data-height="265" data-theme-id="dark" data-default-tab="js,result" data-user="ryokio" data-slug-hash="qBaZpPo" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Canvas2Dのコンテキストを取得">
  <span>See the Pen <a href="https://codepen.io/ryokio/pen/qBaZpPo">
  Canvas2Dのコンテキストを取得</a> by ryokio (<a href="https://codepen.io/ryokio">@ryokio</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

### 矩形・テキストの描画

矩形の描画は `context.fillRect(x, y, width, height)`。
テキストの描画は `context.fillText(text, x, y)`。

さくっと描画できますが、現状だとどちらも黒で何がなんだかわからないですね……。

<p class="codepen" data-height="265" data-theme-id="dark" data-default-tab="js,result" data-user="ryokio" data-slug-hash="QWKNaxy" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="矩形・テキストの描画">
  <span>See the Pen <a href="https://codepen.io/ryokio/pen/QWKNaxy">
  矩形・テキストの描画</a> by ryokio (<a href="https://codepen.io/ryokio">@ryokio</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

### 色を指定する

色……特に塗りつぶすための色を指定するのは `context.fillStyle` を使います。
`context.fillStyle` はCSSの指定そのまま使えるので、例えば `context.fillStyle = rgba(255, 0, 0, 0.1)` のようにすると、不透明度も反映することが可能です。

色は変わりましたが、これだとどちらも同じ色になってしまいます。
これだと困りますね……。

<p class="codepen" data-height="265" data-theme-id="dark" data-default-tab="js,result" data-user="ryokio" data-slug-hash="vYXGprb" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="色を指定する">
  <span>See the Pen <a href="https://codepen.io/ryokio/pen/vYXGprb">
  色を指定する</a> by ryokio (<a href="https://codepen.io/ryokio">@ryokio</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

### 描画状況の保存・復元

`context.save()` で今の描画状況を保存します。
`context.restore()` で保存した状況を復元します。

これで矩形のみが`rgba(255, 0, 0, 0.1)`になりました。
また、

```js
context.save();

  // この時点での描画を保存
  context.save();
  context.fillStyle = '#00FF00';
  context.restore();

context.restore();
```

のように入れ子も出来ますが、`context.save()` や `context.restore()` が抜けると大変なことになりますので、ご注意を。
（今までで `context.restore()` の記載が抜けてることの方が多かったです）

<p class="codepen" data-height="265" data-theme-id="dark" data-default-tab="js,result" data-user="ryokio" data-slug-hash="qBaZpMK" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="描画状況の保存・復元">
  <span>See the Pen <a href="https://codepen.io/ryokio/pen/qBaZpMK">
  描画状況の保存・復元</a> by ryokio (<a href="https://codepen.io/ryokio">@ryokio</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>


### 様々な図形

円弧を描く場合は `context.arc()` を使えば描けます。
`context.beginPath()` でパスを使いたい宣言をします。その後に、`context.arc()` を使って描画します。

<p class="codepen" data-height="265" data-theme-id="dark" data-default-tab="js,result" data-user="ryokio" data-slug-hash="xxEVpBz" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="円弧を描く">
  <span>See the Pen <a href="https://codepen.io/ryokio/pen/xxEVpBz">
  円弧を描く</a> by ryokio (<a href="https://codepen.io/ryokio">@ryokio</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

また `context.moveTo()` でパスの始点を変更することができます。
それを使って、様々な図形を描くことが可能です。

* canvas に図形を描く - 開発者ガイド | MDN<br>
https://developer.mozilla.org/ja/docs/Web/Guide/HTML/Canvas_tutorial/Drawing_shapes

### 画像の描画

画像の描画は `context.drawImage()` で行います。
ですが、画像はまず画像を読み込み終えていることが前提ですので、画像が読み込み終わっているかのチェックが必要になります。

<p class="codepen" data-height="265" data-theme-id="dark" data-default-tab="js,result" data-user="ryokio" data-slug-hash="BaLKYyj" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="画像の描画">
  <span>See the Pen <a href="https://codepen.io/ryokio/pen/BaLKYyj">
  画像の描画</a> by ryokio (<a href="https://codepen.io/ryokio">@ryokio</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

`context.drawImage()` は画像以外にも色々指定することができます。

* CanvasRenderingContext2D.drawImage() - Web API | MDN<br>
https://developer.mozilla.org/ja/docs/Web/API/CanvasRenderingContext2D/drawImage

### 変形

図形や画像などは変形することが可能です。

拡縮に関しては `context.scale()`。
移動に関しては `context.translate()`。
回転に関しては `context.rotate()`。


複雑な変形を行う場合は `context.transform()` もしくは `context.setTransform()` で可能です。
`context.transform()` は現状のマトリクスからの変形。
`context.setTransform()` は現状のマトリクスを破棄して変形（上書き）です。
どちらにしても2Dの行列計算が必要です。~~調べれば出てくるとはいえ、私は苦手です……。~~

<p class="codepen" data-height="265" data-theme-id="dark" data-default-tab="js,result" data-user="ryokio" data-slug-hash="yLaOvaP" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="変形">
  <span>See the Pen <a href="https://codepen.io/ryokio/pen/yLaOvaP">
  変形</a> by ryokio (<a href="https://codepen.io/ryokio">@ryokio</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

### 合成

直前の描画との合成をする際は、`context.globalCompositeOperation` を設定します。
私は黒の矩形を画像で抜いて、シルエット画像にする。ということが多いでしょうか。
（これによって、わざわざシルエット画像を用意してもらう必要がなくなる）

<p class="codepen" data-height="265" data-theme-id="dark" data-default-tab="js,result" data-user="ryokio" data-slug-hash="MWjyQmQ" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="合成">
  <span>See the Pen <a href="https://codepen.io/ryokio/pen/MWjyQmQ">
  合成</a> by ryokio (<a href="https://codepen.io/ryokio">@ryokio</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

### オフスクリーンレンダリング

[合成](#合成) の際に少しやっていますが、新しいcanvas要素を作り、そこにひとまず描画を行い、
その結果を、実際のcanvasに描画して反映させています。
いわゆるオフスクリーンレンダリングです。

専用の OffscreeCanvas というものもあるようですが、当面はこの手法でやるしかないのかなと思っています。

OffscreenCanvas - Web APIs | MDN
https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas

## 終わりに

アドベントカレンダー初参加だ！
と思いつつも、仕事が忙しい（言い訳）ために、今急いで書いた次第であります。
だが、参加することに意義がある！　……と信じつつ。

根っこのCanvas2Dを触れようとしている方の一助になれば幸いです。
ありがとうございました。