JavaScriptでキャンバスを使った描画アプリを作る方法

テーマ

こんにちは!!naoto555です。
今回は、JavaScriptを使ってキャンバスを扱う方法について解説していきます。
キャンバスとは、Webページ上で自由な図形を描画するための領域です。JavaScriptを使うことで、マウスやタッチ操作で自由な線や図形を描くアプリケーションを実装することができます。例えば、手書きのメモアプリやペイントアプリなどがあります。
この記事では、キャンバスの基本的な操作方法から、直線や円、矩形など様々な図形の描画方法、文字や画像の扱い方まで、幅広く解説していきます。
さらに、キャンバスを使って作成できるアプリケーションや、実際に手を動かして学ぶための演習問題もご紹介します。
この知識を身につけることで、自分でアイデアを出してアプリケーションを作ることができますし、Webページ上で自由な描画を行うことができるようになります。ぜひ、最後までご覧いただき、キャンバスの魅力に触れてみてください!(2024.2記載)

描画手順

キャンバスへの描画は以下、①~⑤のような手順で進めていきます。まずはHMTLで、canvas領域(500px × 500px)を定義していきます。
①HTMLの作成

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <canvas id="canvas" width="500" height="500" style="background-color:lightgray;"></canvas>

    <script type="text/javascript">
        //ここにJavaScriptを記載
    </script>
</body>
</html>

次にJavaScriptで以下の処理を行っていきます。

②JavaScriptでcanvas要素への参照先を取得

//②JavaScriptでcanvas要素への参照を取得
const canvas = document.getElementById("canvas");

③canvas要素の参照からコンテキストを使用
※JavaScriptでcanvasを操作するためには、’getContext’メソッドを使ってコンテキストオブジェクトを取得する必要があります。キャンバス要素には、描画に必要な情報(線の色、太さ等)を持つコンテキスト(Context)が存在します。コンテキストは、キャンバスの描画やエフェクトの作成に必要な機能を提供します。簡単に言うと、コンテキストはキャンバス上に描画するための道具箱のようなものです。

//③canvas要素の参照からコンテキストを取得 
const ctx = canvas.getContext("2d");

④コンテキストに色や線、太さなどを設定

//④コンテキストに色や先、太さなどを設定
ctx.strokeStyle = "red";         // 線の色を設定             ->赤
ctx.fillStyle = "lithtblue";     // 塗りの色を設定           ->ライトブルー
ctx.lineCap = "round";           // 線の端点のスタイルを設定  ->球形
ctx.shadowColor = "black";       // 影の色を設定             ->黒
ctx.shadowBlur = 20;             // 影のブラーの度合いを設定  ->20px

 ⑤コンテキストに対して線や矩形などを描画
・線の描画

//線の描画
ctx.beginPath();
ctx.moveTo(100, 150);
ctx.lineTo(200, 300);
ctx.stroke();

・塗りつぶし矩形の描画

//塗りつぶし矩形の描画
ctx.fillRect(250,100,150,200);

・線で矩形を描画

//線で矩形を描画
ctx.strokeRect(250,100,150,200);

様々な図形の描画の解説

①直線、多角形
直線や多角形を描画するためにメソッド,プロパティは以下の通りです。パスを初期化していないパスを閉じていないため思った描画にならないことはよくあるミスなので線を描画するときは、必ずbeginPath()を実行するように注意してください。

メソッド・プロパティ 解説
bebinPath() パスを初期化する
moveTo(x, y) (x,y)座標に筆をおろす
lineTo(x, y) (x,y)座標に筆を動かす
closePath() パスをクリアする閉じる(筆を始点にあわせる)
stroke() 筆が動いた軌跡を線として描く
fill() 筆が動いた軌跡の範囲を塗りつぶす
storokeStyle 線を描画する色を指定する
fillStyle 塗りつぶしの色を指定する
lineWidth 線の幅を指定する
lineCap 線の終端の形状を指定する butt, round, square
shadowColor 影の色
shadowBlur 影に適用するボカシの範囲

上記のメソッド・プロパティを使い、パスを閉じた場合、閉じなかった場合、それぞれを塗りつぶし、軌跡で書いた4パターンの三角形を描画してみます。

<body>
    <canvas id="canvas" width="500" height="150"></canvas>
    <script type="text/javascript">
        "use strict"
        let ctx = null;

        window.onload = function(){
            const canvas = document.getElementById("canvas");
            ctx = canvas.getContext("2d");
           
            ctx.strokeStyle = "red";
            ctx.fillStyle = "blue";
            ctx.lineWidth = 3;

            drawTriangle(100, 25, false, false);
            drawTriangle(200, 25, true, false);
            drawTriangle(300, 25, false, true);
            drawTriangle(400, 25, true, true);
        }

        function drawTriangle(x, y, close, fill){
            ctx.beginPath();
            ctx.moveTo(x, y);
            ctx.lineTo(x+40, y+100);
            ctx.lineTo(x-40, y+100);

            if(close){
                ctx.closePath();
            }else{
                //do Nothing
            }

            if(fill){
                ctx.fill();
            }else{
                ctx.stroke();
            }
        }
    </script>
</body>

描画した三角形


②矩形
矩形を描画するためにメソッドは以下の通りです。矩形ではパスを使用しないので,beginPath(), closePath()は不要です。

メソッド 解説
strokeRect(x, y, width, height) (x, y)を左上隅として、幅w,高さhの矩形を線で描く
fillRect(x, y, width, height) (x, y)を左上隅として、幅w,高さhの塗りつぶし矩形を描く
clearRect(x, y, width, height) (x, y)を左上隅として、幅w,高さhの矩形エリアを削除する

上記のメソッド・プロパティを使い、線で書いた矩形,塗りつぶした矩形,塗りつぶした矩形をclearRect()でくり抜いた3つの矩形を描いてみます。

<body>
    <canvas id="canvas" width="500" height="150"></canvas>
    <script type="text/javascript">
        "use strict"
        let ctx = null;

        window.onload = function(){
            const canvas = document.getElementById("canvas");
            ctx = canvas.getContext("2d");
           
            ctx.strokeStyle = "red";
            ctx.fillStyle = "blue";
            ctx.lineWidth = 3;

            drawRectangle(100, 25, false);
            drawRectangle(200, 25, true);
            drawRectangle(300, 25, true);
            clearRectangle(310, 50);
        }

        function drawRectangle(x, y, fill){
            if(fill){
                ctx.fillRect(x,y,80,100);
            }else{
                ctx.strokeRect(x,y,80,100);
            }
        }

        function clearRectangle(x,y){
            ctx.clearRect(x,y,50,50);
        }
    </script>
</body>

描画した矩形

③円・弧
円弧を描画するメソッドは以下は以下の通りです。
arcはPathを書くメソッドなので、beginPath()でのPathの初期化が必要になります。また、パスを閉じた後、最後にstroke()かfill()で描画する必要があります。

arc(x, y, "半径(px)", "開始する角度(rad)", "終了する角度(rad)", "回転方向(true:反時計回り, false:時計回り)")

※arcメソッドの第6引数は、デフォルトfalse(時計回り)となっていて何も書かないとデフォルトのfalseが採用されます。

上記のメソッドを使い、実際にarc()を使って円弧をいくつかを描いてみます。

<body>
    <canvas id="canvas" width="1000" height="150"></canvas>
    <script type="text/javascript">
        "use strict"
        let ctx = null;

        window.onload = function(){
            const canvas = document.getElementById("canvas");
            ctx = canvas.getContext("2d");
           
            ctx.strokeStyle = "red";
            ctx.fillStyle = "blue";
            ctx.lineWidth = 3;

            //①円
            ctx.beginPath();
            ctx.arc(100, 75, 50, 0, 2*Math.PI);
            ctx.closePath();
            ctx.stroke();

            //②扇形
            ctx.beginPath();
            ctx.moveTo(250, 75);
            ctx.arc(250, 75, 50, -5/6*Math.PI, -1/6*Math.PI);
            ctx.closePath();
            ctx.fill();

            //④半時計回り
            ctx.beginPath();
            ctx.moveTo(400, 75);
            ctx.arc(400, 75, 50, -5/6*Math.PI, -1/6*Math.PI, true);
            ctx.closePath();
            ctx.fill();

            //⑤円弧のみ描画
            ctx.beginPath();
            ctx.arc(550, 75, 50, -3/4*Math.PI, 0);
            ctx.stroke();
        }
    </script>
</body>

描画した円弧

④文字
文字を描画するメソッドは以下は以下の通りです。

メソッド 解説
strokeText(text, x, y) (x, y)をテキストの左下隅として、textの輪郭を描く
fillText(text, x, y) (x, y)をテキストの左下隅として、塗りつぶしtextを描く

上記メソッドを使い、実際にキャンバスに文字を描いてみます。

<body>
    <canvas id="canvas" width="500" height="250" style="background-color: lightgray;"></canvas>

    <script type="text/javascript">
        "use strict"
        let ctx = null;

        window.onload = function(){
            const canvas = document.getElementById("canvas");
            ctx = canvas.getContext("2d");
           
            //文字サイスとフォントの設定
            ctx.font = "40px 'Century'";

            //文字輪郭のカラー
            ctx.strokeStyle = "blue";

            //strokeTextでの文字描画
            ctx.strokeText("Hello World!", 30, 100);

            //文字塗りつぶしのカラー
            ctx.fillStyle = "red";

            //fillTextでの文字描画
            ctx.fillText("Vanishment this world!", 30, 150);
        }
    </script>
</body>

描画した文字

⑤画像
画像を描画するメソッドは以下は以下の通りです。

メソッド 解説
drawImage(image, x, y, w, h) を左上隅として、w,hのサイズでimageを描画する
※w,hを省略した場合、元サイズで表示される

上記メソッドを使い、実際にキャンバスに画像を描いてみます。
(使用した画像 100px*100px)

<body>
    <canvas id="canvas" width="500" height="500" style="background-color: lightgray;"></canvas>
    <img id="user1" src="img/user1.png" style="display:none;"/>
    <img id="user2" src="img/user2.png" style="display:none;"/>
    <img id="user3" src="img/user3.png" style="display:none;"/>

    <script type="text/javascript">
        "use strict"
        let ctx = null;

        window.onload = function(){
            const user1 = document.getElementById("user1");
            const user2 = document.getElementById("user2");
            const user3 = document.getElementById("user3");

            const canvas = document.getElementById("canvas");
            ctx = canvas.getContext("2d");
           
            ctx.drawImage(user1,30,30);
            ctx.drawImage(user2,150,30,70,100);
            ctx.drawImage(user3,30,150,330,200);
        }
    </script>
</body>

描画した画像

座標系の設定

キャンバスには、座標系を変更するという機能も準備されています。これによって例えば時計のような回転する図形も容易に描くことができます。
例えば、以下のような図形を容易に描くことができます。

コードは以下のようなものになります。

<body>
    <canvas id="canvas" width="500" height="500" style="background-color: lightgray;"></canvas>
 
    <script type="text/javascript">
        "use strict"
        let ctx = null;

        window.onload = function(){
            const canvas = document.getElementById("canvas");
            ctx = canvas.getContext("2d");

            // コンテキストのプロパティとして、色と太さを設定
            ctx.strokeStyle = "red";
            ctx.lineWidth = 5;
           
            for(let i=0; i<=6; i++){
                let angle = Math.PI / 6 * i;

                //コンテキストの状態を保存する
                ctx.save();

                // (250, 250)の位置に移動し、そこをを原点に設定
                ctx.translate(250,250);

                // コンテキストをangle(rad)回転
                ctx.rotate(angle);

                //描画
                ctx.beginPath();
                ctx.moveTo(0,-200);
                ctx.lineTo(0, -180);
                ctx.stroke();

                //ctx.save()で保存したコンテキストの状態を復元する
                ctx.restore();
            }
        }
    </script>
</body>

上記は、以下のような手順でfor文で徐々に回転させながら描画を行っています。

実際に簡単なアプリをつくってみよう

上記に紹介した手法を使って、簡単なアプリを作ってみました。JavaScriptに自信のある方は演習問題のつもりでコード見ずに作ってみましょう。

①以下のようなアナログ時計を作ってみよう

TIME

サンプルコード

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AnalogWatch</title>
</head>
<body>
    <canvas id="canvas" width="500" height="500" style="background-color: lightgray;"></canvas>
    <p id="time"></p>
 
    <script type="text/javascript">
        "use strict"
        let ctx = null;
        const canvas = document.getElementById("canvas");
        let timerId = NaN;
        const time = document.getElementById("time");

        window.onload = function(){
            drawScale();
            timerId = setInterval(tick,1000);
        }

        function drawScale(){
            //コンテキストの定義
            ctx = canvas.getContext("2d");
           
            //目盛りの描画
            for(let i=0; i<=59; i++){
                let angle = Math.PI / 30 * i;

                // コンテキストのプロパティとして、色と太さを設定
                ctx.strokeStyle = "black";

                // ctx.save()は、コンテキストの状態を保存する
                ctx.save();

                if(i%5 == 0){//5分目盛り                    
                    //中心座標の移動
                    ctx.translate(250,250);

                    //座標の回転
                    ctx.rotate(angle);

                    //描画
                    ctx.lineCap = "squire";
                    ctx.lineWidth = 5;
                    ctx.beginPath();
                    ctx.moveTo(0,-200);
                    ctx.lineTo(0, -180);
                    ctx.stroke();

                    //ctx.save()で保存したコンテキストの状態を復元する
                    ctx.restore();
                }else{//1分目盛り
                    //中心座標の移動
                    ctx.translate(250,250);

                    //座標の回転
                    ctx.rotate(angle);

                    //描画
                    ctx.lineCap = "squire";
                    ctx.lineWidth = 3;
                    ctx.beginPath();
                    ctx.moveTo(0,-200);
                    ctx.lineTo(0, -195);
                    ctx.stroke();

                    //ctx.save()で保存したコンテキストの状態を復元する
                    ctx.restore();
                }
            }
        }

        function tick(){        
            //時間の取得
            let now = new Date();
            let hours = now.getHours() % 12;
            let minutes = now.getMinutes();
            let seconds = now.getSeconds();
            time.textContent = now;

            //コンテキストの定義
            ctx = canvas.getContext("2d");

            //描画領域のクリア
            ctx.fillStyle = "lightgray";
            ctx.fillRect(0,0,500,500);

            //目盛りの描画
            drawScale();

            //長針の描画設定
            ctx.save();
            ctx.translate(250,250);
            ctx.rotate((minutes * Math.PI/30)+(Math.PI/30 * seconds/60));
            ctx.strokeStyle = "black";
            ctx.lineWidth = 5;
            ctx.lineCap = "round";

            //長針の描画
            ctx.beginPath();
            ctx.moveTo(0,20);
            ctx.lineTo(0,-190);
            ctx.stroke();
            ctx.restore();

            //短針の描画設定
            ctx.save();
            ctx.translate(250,250);
            ctx.rotate((hours * Math.PI/6) + (Math.PI/6 * minutes/60));
            ctx.strokeStyle = "black";
            ctx.lineWidth = 8;
            ctx.lineCap = "round";

            //短針の描画
            ctx.beginPath();
            ctx.moveTo(0,10);
            ctx.lineTo(0,-170);
            ctx.stroke();
            ctx.restore();

            //秒針の描画設定
            ctx.save();
            ctx.translate(250,250);
            ctx.rotate(seconds * Math.PI/30);
            ctx.strokeStyle = "red";
            ctx.lineWidth = 3;

            //秒針の描画
            ctx.beginPath();
            ctx.moveTo(0,30);
            ctx.lineTo(0,-180);
            ctx.stroke();
            ctx.restore();
        }
    </script>
</body>
</html>

②以下のようなお絵描きできるCanvasを作成してみよう
(マウスをドラック&ムーブするとお絵描きできます)


 

サンプルコード

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Paint</title>
</head>
<body>
    <canvas id="myCanvas" width="500" height="500" style="background-color: lightgray;"></canvas>
    <button onclick="clearCanvas()">Clear</button>
    <script type="text/javascript">
        "use strict"

        //canvas要素を取得
        const myCanvas = document.getElementById("myCanvas");

        //2Dコンテキストを取得
        let context = myCanvas.getContext("2d");

        //描画スタイルを設定
        context.strokeStyle = "black";
        context.lineWidth = 5;

        //描画を開始
        let isDrawing = false;
        let lastX = 0;
        let lastY = 0;

        myCanvas.addEventListener('mousedown', function(e){
            isDrawing = true;
            lastX = e.offsetX;
            lastY = e.offsetY;
        });

        myCanvas.addEventListener('mousemove', function(e){
            if(!isDrawing){
                return;
            }
            context.beginPath();
            context.moveTo(lastX, lastY);
            context.lineTo(e.offsetX, e.offsetY);
            context.stroke();
            lastX = e.offsetX;
            lastY = e.offsetY;
        });

        myCanvas.addEventListener('mouseup', function(){
            isDrawing = false;
        });

        function clearCanvas(){
            context.fillStyle = "lightgray";
            context.fillRect(0,0,500,500);
        }
    </script>
</body>
</html>

まとめ

以上、JavaScriptを使ってキャンバスを扱う方法についてまとめて解説してみました。ここまで、読んでくださった方のWebアプリケーションの作成のお役にたてたら嬉しいです。