Canvas を使って画面描画をしてみよう!
今回は、Canvas API を使って画面描画をしてみます。
まずはindex.html
をいい感じにまっさらにして、Canvas 要素を追加しましょう。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="/src/styles.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tauri App</title>
<script type="module" src="/src/main.ts" defer></script>
</head>
<body>
<canvas id="test-canvas" width="300" height="300"></canvas>
</body>
</html>
次に、main.ts
をもっとまっさらにして、Canvas の DOM を取得してみましょう。
window.addEventListener("DOMContentLoaded", () => {
const canvas = document.getElementById("test-canvas");
});
前回の記事で吹っ飛ばした気がしますが、window.addEventListener("DOMContentLoaded", () => { ... });
は、DOM の読み込みが完了した時に実行される関数です。
んでたまに何度も出てきてる DOM っていうのが何なのかといいますと、HTML の要素 (<div>...</div>
とか<p>...</p>
とか) のことを、TypeScript では DOM と呼びます。
すなわち、「DOM の読み込み完了」とは、HTML の要素が全て読み込まれ(画面に描画され)た時を指します。
更に、document.getElementById("test-canvas")
は、HTML におけるid
がtest-canvas
である要素を取得する関数です。
さっきのindex.html
では、<canvas id="test-canvas" width="300" height="300"></canvas>
という要素がありますので、これを取得しています。
という訳で、まずは Canvas の説明です。
Canvas とは
Canvas というものは HTML5 で導入された、画面描画を行うためのやつです。
画面描画というと、画像や図形、文字なんかを描画することが出来ます。
つまりこれ、これまで CSS でなんかめっちゃ頑張って図形作成とかやってたような奴が、TypeScript から簡単に操作できるって話なんですよ。
まずはものは試しです。
Canvas に何かを描画するにはコンテキストというものを取得する必要があります。
window.addEventListener("DOMContentLoaded", () => {
const canvas = document.getElementById("test-canvas");
const ctx = canvas.getContext("2d");
});
はい、まず Canvas 要素を取得して、それにgetContext
というのを呼び出して 2D の Context を取得して...
あれ?
なんかエラー...?
あーあ、あなた TypeScript に怒られてますね。
TypeScript で DOM をいじいじするときのお話
上記コードは JavaScript では、エラーもなく動作します。
が、TypeScript はそうはいきません。
まず、const canvas = document.getElementById("test-canvas");
から見ていきましょう。
先ほどの画像を見ても分かるように、: HTMLElement | null
と書いてあります。
これは、canvas
がHTMLElement
型かnull
型のどちらかであることを示しています。
なぜならば、test-canvas
という id を持つ要素が存在しないかもしれないからです。
これではいけません。まさに Null Pointer Exception です。ガッ...
null
を消費しましょう。
window.addEventListener("DOMContentLoaded", () => {
const canvas = document.getElementById("test-canvas");
if (!canvas) return;
const ctx = canvas.getContext("2d");
});
if 文を使いましょう。
TypeScript での if 文は、if (条件) { ... }
と書くことが出来ます。
(条件が真の時の処理が 1 行ですむ場合は、波かっこを省略することが出来ます)
!canvas
というのですが、まず!
は否定を表す演算子 (NOT) です。
!true
はfalse
、!false
はtrue
です。
そして、以下の記事にもあるように、TypeScript の条件式の中で、null
はfalse
として扱われます。
(というより、null
じゃないような 1 とか存在する値はtrue
として扱われます)
つまり、!canvas
はcanvas
がnull
である場合にtrue
を返します。
これで、canvas
がnull
である場合は、return
で関数を終わらせ、null
じゃない場合は次の処理に進むようになりました。
よし! これでエラーは出なくな...あれ?
getContext
がエラーを吐いていますね。
これは、canvas
にはgetContext
メソッドが存在しないからです。
いや待て、さっきお前「JavaScript なら正常に動く」言ってたやんかと。
ってことはgetContext
だかなんだかもあるんやろ?と。
原因は、変数canvas
がHTMLElement
だからです。
HTMLElement にはgetContext
メソッドが存在しません。
なので、canvas
が<canvas></canvas>
であることを TypeScript に伝える必要があります。
<canvas></canvas>
...すなわちHTMLCanvasElement
にはgetContext
メソッドが存在します。
そして、その TypeScript に伝える方法というのが、型アサーションです。
window.addEventListener("DOMContentLoaded", () => {
const canvas = document.getElementById("test-canvas") as HTMLCanvasElement | null;
if (!canvas) return;
const ctx = canvas.getContext("2d");
});
document.getElementById("test-canvas")
の後ろにas HTMLCanvasElement | null
と書いています。
これで、TypeScript はcanvas
がHTMLCanvasElement
かnull
のどちらかであることを理解しました。
この型アサーションというのはとても強力なもので、型を強制的に変換することが出来ます。
今回、HTMLElement
からHTMLCanvasElement
への変換のため、互いの型が近いため正常に動きます。
しかし、as HTMLCanvasElement | null
というのをas number | null
とでも書いてみましょうか。
現在の私のエディタではエラーが発生しません。
しかし、人間の目で見る限り、document.getElementById("test-canvas")
はnumber
を返すことはないでしょう。
...というように、型アサーションは便利ながらとても強力なものなので、使いすぎには注意しましょう。
ちなみに、as HTMLCanvasElement | null
をas number
としてみると、私のエディタでもエラーを吐いてくれました。
型 'HTMLElement | null' から型 'number' への変換は、互いに十分に重複できないため間違っている可能性があります。意図的にそうする場合は、まず式を 'unknown' に変換してください。
型 'HTMLElement' は型 'number' と比較できません。ts(2352)
最後に、ctx
にも同様にnull
消費を行いましょう。
window.addEventListener("DOMContentLoaded", () => {
const canvas = document.getElementById("test-canvas") as HTMLCanvasElement | null;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
});
描画だ!
さて、ここまでで Canvas の Context を取得することが出来ました。
描画するにはその Context を使います。
試しに、 Canvas に四角形を描画してみましょう。
window.addEventListener("DOMContentLoaded", () => {
const canvas = document.getElementById("test-canvas") as HTMLCanvasElement | null;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
ctx.fillStyle = "red";
ctx.fillRect(10, 10, 100, 100);
});
おっ!?
赤い四角形が描画されました!
ctx.fillStyle = "red";
で色 (特に fill
するための色) を指定し、ctx.fillRect(10, 10, 100, 100);
で四角形 (Rectangular
) を左から 10px、上から 10px のところに 100x100 で描画しています。
はは...rectangular
...四角形...AutoCAD...思い出しちゃったネ...
私は AutoCAD で「四角形」が
Rectang
であると学んだよ!
更に画像も描画しちゃいましょう!
ベーコンの画像でも描画してみますか。
const img = new Image();
img.src = "https://baconmockup.com/200/200/";
img.onload = () => {
ctx.drawImage(img, 50, 50);
};
(先ほどのctx.fillRect(...);
の下)
飯テロですか!?!?!?
と、このように画像も出すことが出来ちゃいます。
const img = new Image();
で画像を作成しています。
これは、HTML の<img>
要素と同じものです。
img.src = "https://baconmockup.com/200/200/";
で画像の URL を指定しています。
そして、img.onload = () => { ctx.drawImage(img, 50, 50); };
で画像の読み込みが完了した時にコンテキストに画像を、上から 50px、左から 50px の位置に描画しています。
画像の読み込みが終わる前にctx.drawImage(img, 50, 50);
を実行してしまうと、正常に描画されません。
似たような方法でimg.decode()
という方法もありますが、今回は省略します。
数日の間だけ頭に入れておいてください。
まとめ
はい、もうこれであなたは何でも描画できるようになりましたね。
え?なに?ベーコンが邪魔だから動かしてほしい?
分かりましたよ...動かす...動かす...
...ということで、次回は Canvas でのアニメーションについてお話しようと思います。