Canvas



## Canvasの特徴
### デフォルトサイズが決まっている
canvasはwidthとheightが指定されなかった場合、幅300px、高さ150pxになります。

### CSSが効かない
CSSは、「Canvas要素」までしかアクセスできません。
元の画像の縦横比は考慮されないので、指定の仕方によっては画像が歪んで表示される場合があります。


### SVGとの比較

#### Canvas
- SVGに比べるとシンプルなので学習が比較的容易

#### SVG
- 表示内容をDOMで表現する
- テキストが抽出できる
- リンクのフォーカス制御はブラウザが処理
- ユーザーにとって使いやすいものになる可能性が高い

#### Canvasが適す場合
Canvas APIでないと実現できないか、効率が良い場合のみCanvasを使います。
- 多数のオブジェクトがインタラクティブに動作するコンテンツ(ゲームなど)
- 描画結果をPNG形式などにエクスポートする
- JavaScriptで生成した画像やを表示する
- SVGが使えない環境

#### SVGとの組み合わせ
toDataURL()やforeignObjectを使って、Canvas APIの描画結果をSVGに含めることができます。





## 描画

|メソッド|内容|
|---|---|
|canvas.getContext('2d')|キャンバスを取得|

### 四角

|メソッド|内容|
|---|---|
|fillRect(x,y,幅,高さ)|四角を塗る|
|strokeRect(x,y,幅,高さ)|四角の境界線を書く|


|プロパティ|内容|
|---|---|
|fillStyle|塗りの色|
|strokeStyle|線や輪郭の色|
|lineWidth|線の太さ|
|lineCap|線の終端の形(butt/round/square)|
|lineJoin|線の接続の形(miter/round/bevel) ※miterは、bevelに切り変わる値をmiterLimit属性で指定可能|
|shadowColor|影の色|
|shadowBlur|影のぼかし|
|shadowColor|影の色を設定|
|shadowOffsetX|影をX方向にずらすピクセル数|


```:html
<canvas id="js-canvas-test">
</canvas>
```

```:js
//canvas要素のid取得
const canvas = document.getElementById("js-canvas-test");
 
//コンテキストの取得
const context = canvas.getContext("2d");

//塗りの色
context.fillStyle = "#2980b9";

//線の幅
context.lineWidth = 4;

//線の色
context.strokeStyle = "#34495e";

//塗りの描画
context.fillRect(10, 10, 100, 100);

//線の描画
context.strokeRect(8, 8, 102, 102)
```

### 線

|メソッド|内容|
|---|---|
|moveTo(x,y)|次の図形の開始位置|
|beginPath()|パスのリセット|
|lineTo(x,y)|パスを書く|
|quadraticCurveTo(cpx,cpy,x,y)|2次曲線|
|bezierCurveTo(cp1x,cp1y,cp2x,cp2y,x,y)|ベジェ曲線|
|arcTo(x1,y1,x2,y2,半径)|折れ線(角丸)|
|arc(x,y,半径,開始角度,終了角度,回転方向)|円弧|
|closePath()|パスを閉じる(閉じると塗りができるようになる)|

```:html
<canvas id="js-canvas-test">
</canvas>
```

```:js
//canvas要素のid取得
const canvas = document.getElementById("js-canvas-test");

//コンテキストの取得
const context = canvas.getContext("2d");

//線の幅
context.lineWidth = 4;

//線の色
context.strokeStyle = "#34495e";

//塗りの色
context.fillStyle = "#2980b9";

//パス
context.beginPath();
context.moveTo(20, 60);
context.lineTo(180, 80);
context.lineTo(100, 20);
context.closePath();

//線と塗りの描画
context.stroke();
context.fill();
```


### 文字

|メソッド|内容|
|---|---|
|fillText(文字列,x,y[,最大幅])|文字(塗り)|
|strokeText(文字列,x,y[,最大幅])|文字(線)|
|measureText()|文字列の幅取得|


|プロパティ|内容|
|---|---|
|font|文字のサイズやフォントの指定|
|textAlign|水平方向の寄せ(left/center/right/start(初期値)/end)|
|textBaseline|垂直方向の寄せ(top/hanging/middle/alphabetic(初期値)/ideographic/bottom)|



```:html
<canvas id="js-canvas-test">
</canvas>
```

```:js
//canvas要素のid取得
const canvas = document.getElementById("js-canvas-test");

//コンテキストの取得
const context = canvas.getContext("2d");

//フォントの指定
context.font = "48px serif";

//文字の描画(塗り)
context.fillText("sample text", 0, 48, 300);

//文字の描画(線)
context.strokeText("sample text", 0, 96, 300);
```

### グラデーション
table:グラデーションのプロパティ
 addColorStop(位置、色)	位置は0～1


#### 線形グラデーション

|メソッド|内容|
|---|---|
|context.createLinearGradient(x1,y2,x2,y2)|線形グラデーションの作成|

```:html
<canvas id="js-canvas-test">
</canvas>
```

```:js
//canvas要素のid取得
const canvas = document.getElementById("js-canvas-test");

//コンテキストの取得
const context = canvas.getContext("2d");

//線形グラデーションの作成
const grad = context.createLinearGradient(0, 0, 0, 150);
grad.addColorStop(0.0, "#3498db");
grad.addColorStop(0.5, "#2980b9");
grad.addColorStop(1.0, "#34495e");

//グラデーションの描画
context.fillStyle = grad;
context.fillRect(0, 0, 300, 150);
```

#### 円形グラデーション

|メソッド|内容|
|---|---|
|createRadialGradient(x0,y0,r0,x1,y1,r1)|円形グラデーションの作成|

```:html
<canvas id="js-canvas-test">
</canvas>
```

```:js
//canvas要素のid取得
const canvas = document.getElementById("js-canvas-test");

//コンテキストの取得
const context = canvas.getContext("2d");

//円形グラデーションの作成
const grad = context.createRadialGradient(150, 75, 1, 150, 75, 100);
grad.addColorStop(0.0, "#3498db");
grad.addColorStop(0.5, "#2980b9");
grad.addColorStop(1.0, "#34495e");

//グラデーションの描画
context.fillStyle = grad;
context.fillRect(0, 0, 300, 150);
``` 

### パターン

|メソッド|内容|
|---|---|
|createPattern(元画像, "繰り返し方")|繰り返し方(repeat/repeat-x/repeat-y/no-repeat)|

```:html
<canvas id="js-canvas-test">
</canvas>
```


```:js
//canvas要素のid取得
const canvas = document.getElementById("js-canvas-test");

//コンテキストの取得
const context = canvas.getContext("2d");

//画像の新規作成
const image = new Image();
 
//画像の読み込みが完了したらパターンを作る
image.addEventListener("load", () => {
  const pattern = context.createPattern(image, "repeat");
  context.fillStyle = pattern;
  context.fillRect(0, 0, 300, 150);
});

//imageのsrc属性に画像を入れる
image.src = "sample.jpg";
```

### 半透明
```:html
<canvas id="js-canvas-test">
</canvas>
```

```:js
//canvas要素のid取得
const canvas = document.getElementById("js-canvas-test");

//コンテキストの取得
const context = canvas.getContext("2d");

//画像の新規作成
const image = new Image();
 
//画像の読み込みが完了したら半透明にする
image.addEventListener("load", () => {
  context.globalAlpha = 0.5;
  context.drawImage(image, 0, 0);
});

//imageのsrc属性に画像を入れる
image.src = "sample.jpg";
```

### saveとrestore

|メソッド|内容|
|---|---|
|save()|保存|
|restore()|復元|

|save()で保存される、されない|項目|
|---|---|
|〇|現在の変換マトリックス|
|〇|現在の切り抜き領域|
|×|現在のパス|
|×|現在のビットマップ|
|〇|strokeStyle|
|〇|fillStyle|
|〇|globalAlpha|
|〇|lineWidth|
|〇|lineCap|
|〇|lineJoin|
|〇|miterLimit|
|〇|shadowOffsetX|
|〇|shadowOffsetY|
|〇|shadowBlur|
|〇|shadowColor|
|〇|globalCompositeOperation|
|〇|font|
|〇|textAlign|
|〇|textBaseline|



```:html
<canvas id="js-canvas-test">
</canvas>
```

```:js
//canvas要素のid取得
const canvas = document.getElementById("js-canvas-test");

//コンテキストの取得
const context = canvas.getContext("2d");

//色指定
context.fillStyle = "#27ae60";

context.save();

//指定
context.fillStyle = "#e74c3c";

context.restore();

//四角を書く
context.fillRect(0, 0, 300, 150);
```

### 座標変換
|メソッド|内容|
|---|---|
|translate(x,y)|移動|
|rotate(角度)|回転|
|scale(x,y)|拡大縮小|
|transform(a,b,c,d,e,f)|行列で変換|
|setTransform(a,b,c,d,e,f)|座標変換をリセットする|


```:html
 <canvas id="js-canvas-test">
 </canvas>
```

```:js
//canvas要素のid取得
const canvas = document.getElementById("js-canvas-test");

//コンテキストの取得
const context = canvas.getContext("2d");

//色
context.fillStyle = "#27ae60";

//移動
context.translate(50, 100);

//回転
context.rotate(30);

//四角を書く
context.fillRect(0, 0, 100, 100);
```


### 合成
|プロパティ|内容|
|---|---|
|globalCompositeOperation|図形や画像を描画領域の既存のビットマップに合成する方法を変更可能|


|globalCompositeOperarionの値|内容|
|---|---|
|source-atop|元画像と背景画像の両方が不透明の領域に元画像を描画　それ以外には背景画像を描画|
|source-in|元画像と背景画像の両方が不透明の領域に元画像を描画　それ以外は透明色|
|source-out|背景画像が透明の領域に元画像を描画　それ以外は透明色|
|source-over|単純に元画像を背景画像の上に重ねる(初期値)|
|destination-atop|source-atopの元画像と背景画像を入れ替えたもの|
|destination-in|source-inの元画像と背景画像を入れ替えたもの|
|destination-out|source-inの元画像と背景画像を入れ替えたもの|
|destination-over|source-overの元画像と背景画像を入れ替えたもの|
|lighter|元画像と背景画像を加算合成|
|copy|背景画像を無視し、単純に元画像を描画|
|xor|元画像と背景画像のどちらか一方のみが不透明な領域にそれぞれの画像を描画　それ以外の領域は透明|


```:html
<canvas id="js-canvas-test">
</canvas>
```

```:js
//canvas要素のid取得
const canvas = document.getElementById("js-canvas-test");
 
//コンテキストの取得
const context = canvas.getContext("2d");
 
//四角1を書く
context.fillStyle = "#e74c3c";
context.fillRect(0, 0, 150, 150);
 
//合成の形式を指定する
context.globalCompositeOperation = "lighter";
 
//四角2を描く
context.fillStyle = "#2980b9";
context.fillRect(75, 0, 150, 150);
```  



### クリッピング

|メソッド|内容|
|---|---|
|clip()|切り抜き|


```:html
<canvas id="js-canvas-test">
</canvas>
```

```:js
//canvas要素のid取得
const canvas = document.getElementById("js-canvas-test");

//コンテキストの取得
const context = canvas.getContext("2d");

//画像作成
const image = new Image();

//画像が読み込まれたらクリッピングする
image.addEventListener("load", () => {
  context.arc(150, 75, 75, 0, Math.PI * 2, false);
  context.clip();
  context.drawImage(image, 0, 0);
});

//画像のsrc属性に画像を入れる
image.src = "sample.jpg";
```














## ビットマップの操作

|メソッド|内容|
|---|---|
|drawImage(image,dx,dy)|画像を表示|
|drawImage(image,dx,dy,dw,dh)|画像を表示(リサイズ)|
|drawImage(image,sx,sy,sw,sh,dx,dy,dw,dh)|画像を表示(トリミング)　sが元になる画像|
|createImageData(sw,sh)	空の画像を作成|空の画像を作成|
|createImageData(image)|画像のコピー|
|getImageData(sx,sy,sw,sh)|現在のビットマップを取得|
|putImageData(image,dx,dy,dirtyX,dirtyY,dirtyW,dirtyH)|ビットマップを描画領域にコピー|



```:html
<canvas id="js-canvas-test">
</canvas>
```

```:js
//canvas要素のid取得
const canvas = document.getElementById("js-canvas-test");

//コンテキストの取得
const context = canvas.getContext("2d");

//imageの作成
const image = new Image();

//imageの読み込みが終了したら画像を描画
image.addEventListener("load", () => {
    context.drawImage(image, 0, 0);
});

//画像の指定
image.src = "sample.jpg";
```





## ピクセルごとの色の取得

```:html
<canvas id="js-canvas-test">
</canvas>
```

```:js
//canvas要素のid取得
const canvas = document.getElementById("js-canvas-test");
 
//コンテキストの取得
const context = canvas.getContext("2d");
 
//塗りの色
context.fillStyle = "#2980b9";
 
//線の幅
context.lineWidth = 4;
 
//線の色
context.lineStyle = "#34495e";
 
 
//塗りの描画
context.fillRect(10, 10, 100, 100);
 
//線の描画
context.strokeRect(8, 8, 102, 102);
 
//ピクセルごとの色の取得
const imageData = context.getImageData(8, 8, 102, 102);
 
//ピクセルごとの色の表示
console.log(imageData.data);
``` 








### base64化

|メソッド|内容|
|---|---|
|canvas.toDataURL|DataURL形式で出力|


```:html
<canvas id="js-canvas-test">
</canvas>

<img id="js-img" />
```

```:js
//canvas要素のid取得
const canvas = document.getElementById("js-canvas-test");

//コンテキストの取得
const context = canvas.getContext("2d");

//塗りの色
context.fillStyle = "#2980b9";

//線の幅
context.lineWidth = 4;

//線の色
context.lineStyle = "#34495e";

//塗りの描画
context.fillRect(10, 10, 100, 100);

//線の描画
context.strokeRect(8, 8, 102, 102);

//dataURL化
const canvasPicture = canvas.toDataURL();

//img要素のsrc属性に入れる
const img = document.getElementById("js-img");
img.src = canvasPicture; //""や''で囲まないように注意
```
 
img属性の画像は、ブラウザにより名前が異なる
別ドメインの画像を描画したcanvasでtoDataURL()メソッドを呼び出すと例外が発生する(セキュリティ上の理由)


|ブラウザ|生成した画像の名前|
|---|---|
|chrome|ダウンロード.png|
|Firefox|index.png|
|Edge|無題.png|

png以外の形式にすることもできる(非対応ブラウザあり)

```:js
//jpgを指定する場合(jpgは透過情報を持てないので、透明部分は黒くなります)
const canvasPicture = canvas.toDataURL("image/jpeg");

//webpを指定する場合
const canvasPicture = canvas.toDataURL("image/webp");

//明示的にpngを指定する場合
const canvasPicture = canvas.toDataURL("image/png");
```
















## 高解像度デバイス対応

```:html
<p>デバイスピクセル比：<span id="js-deviceDpr"></span></p>
 
<canvas id="js-canvas-test">
</canvas>
```

```:js
//デバイスピクセル比を取得
const dpr = window.devicePixelRatio;

//デバイスピクセル比を出力(動作確認用)
const deviceDpr = document.getElementById("js-deviceDpr");
deviceDpr.innerHTML = dpr;

//canvas要素のid取得
const canvas = document.getElementById("js-canvas-test");

//canvas要素の幅を取得
const canvasWidth = canvas.clientWidth;

//canvas要素の高さを取得
const canvasHeight = canvas.clientHeight;


//密度の調整
canvas.width = canvasWidth * dpr;
canvas.height = canvasHeight * dpr;

//表示上の大きさの調整
canvas.style.width = canvasWidth + "px";
canvas.style.height = canvasHeight + "px";

//コンテキストの取得
const context = canvas.getContext("2d");

//コンテキストの密度の調整
context.scale(dpr, dpr);

//色指定
context.fillStyle = "#27ae60";

//円の定義(中心点(75,75)、半径75、開始角度0、終了角度0、時計回り)
context.arc(75, 75, 75, 0 * Math.PI / 180, 360 * Math.PI / 180, false);

//描画
context.fill();
```







## 加工
|メソッド|内容|
|---|---|
|context.putImageData(画像)|画像を入力する|



```:html
<canvas id="js-canvas-test1">
</canvas>
 
<canvas id="js-canvas-test2">
</canvas>
```

```:js
//canvas要素1のid取得
const canvasOriginal = document.getElementById("js-canvas-test1");
 
//コンテキスト1の取得
const contextOriginal = canvasOriginal.getContext("2d");
 
//canvas要素2のid取得
const canvasEffected = document.getElementById("js-canvas-test2");
 
//コンテキスト2の取得
const contextEffected = canvasEffected.getContext("2d");

 
//imageの作成
const image = new Image();
 
//imageの読み込みが終了したら画像を描画
image.addEventListener("load", () => {
 
  //オリジナル画像の描画
  contextOriginal.drawImage(image, 0, 0);
 
  //オリジナル画像の取得
  const imageOriginal = contextOriginal.getImageData(0, 0, 300, 150);
 
  //オリジナル画像のピクセル情報を取得
  const pixelColorOriginal = imageOriginal.data;
 
  //加工用の画像を作成
  const imageEffected = new ImageData(300, 150);
 
  //加工用画像のピクセルの色情報を取得
  const pixelColorEffected = imageEffected.data;
 
  for (let i = 0; i < pixelColorOriginal.length / 4; i += 1) {
 
    //オリジナル画像の赤を取得
    const red = pixelColorOriginal[i * 4];
 
    //オリジナル画像の緑を取得
    const green = pixelColorOriginal[i * 4 + 1];
 
    //オリジナル画像の青を取得
    const blue = pixelColorOriginal[i * 4 + 2];
 
    //オリジナル画像のアルファを取得
    const alpha = pixelColorOriginal[i * 4 + 3];
 
 
    //加工画像の赤に、オリジナル画像の緑を代入
    pixelColorEffected[i * 4] = green;
 
    //加工画像の緑に、オリジナル画像の青を代入
    pixelColorEffected[i * 4 + 1] = blue;
 
    //加工画像の青に、オリジナル画像の赤を代入
    pixelColorEffected[i * 4 + 2] = red;
 
    //加工画像のアルファに、オリジナル画像のアルファを代入
    pixelColorEffected[i * 4 + 3] = alpha;
 
  }
 
  //canvas2要素2に加工した画像を入れる
  contextEffected.putImageData(imageEffected, 0, 0)
 
});
 
//画像の指定
image.src = "sample.jpg";
``` 









## クリックイベント

```:html
<canvas id="js-canvas-test">
</canvas>
 
<p id="js-clicked"></p>
```

```:js
//canvas要素のid取得
const canvas = document.getElementById("js-canvas-test");
//コンテキストの取得
const context = canvas.getContext("2d");
 
//クリックメッセージ位置のid取得
const clicked = document.getElementById("js-clicked");
 
//クリック判定
canvas.addEventListener("click", () => {
 
  //クリック座標取得
  const clickPositionX = event.clientX + window.pageXOffset - canvas.offsetLeft;
  const clickPositionY = event.clientY + window.pageYOffset - canvas.offsetTop;
 
  //図形の範囲内か判定
  if (context.isPointInPath(clickPositionX, clickPositionY)) {
    clicked.innerHTML = "クリックされた";
  }
})
 
//四角の定義
context.rect(100, 25, 100, 100);
 
//色の定義
context.fillStyle = '#27ae60';
 
//描画
context.fill();
```





## アニメーション

```:html
<canvas id="js-canvas-test">
</canvas>
```

```:js
//canvas要素のid取得
const canvas = document.getElementById("js-canvas-test");
 
//canvas要素の幅を取得
const canvasWidth = canvas.clientWidth;
 
//canvas要素の高さを取得
const canvasHeight = canvas.clientHeight;
 
//コンテキストの取得
const context = canvas.getContext("2d");
 
//色の定義
context.fillStyle = '#27ae60';
 
//四角の大きさ
const rectSize = 100;
 
//アニメーションの実行回数のカウント用変数
let count = 0;
 
//アニメーション
const animation = () => {
   
  //現在の描画を消去
  context.clearRect(0, 0, canvasWidth, canvasHeight);
   
  //パスのリセット
  context.beginPath();
   
  //x軸方向に1ピクセル移動
  context.translate(1, 0);
   
  //描画
  context.fillRect(0, 0, rectSize, rectSize);

  //canvasの端まで到達するまでanimationを繰り返す
  if (count <= canvasWidth - rectSize) {
    count += 1;
    requestAnimationFrame(animation);
  }

}

requestAnimationFrame(animation);
```