あけおめ企画 – 画像を一切使用せず HTML5 Canvas で鏡餅年賀状を描いてみた

phiary に引っ越しました. 毎日プログラミングやWebに関する情報を発信しています! RSS 登録してたまに覗いたり, tweet やハテブして拡散してもらえると幸いです.

Pocket

元旦に投稿したエントリー『アケオメー!! 画像を使わずに HTML5 Canvas, CSS, SVG それぞれで年賀状を作ってみた.』の個別解説です.

今回解説するのは HTML5 Canvas Version についてです.

nengazyou

Sample

今回解説するサンプルはこちら.

鏡餅の絵から, テキストまですべて Canvas で描いています.

Code

今回のサンプルの全体コードです.

<!DOCTYPE html>

<html>
    
    <head>
        <meta charset="UTF-8">
        <title>HTML5 Canvas で鏡餅ハガキ</title>
        <style>
            * {
                -webkit-box-sizing: border-box;
                -moz-box-sizing: border-box;
                -o-box-sizing: border-box;
                box-sizing: border-box;
            }
            
            body {
                font-family: "Meiryo", Arial, Helvetica, sans-serif;
            }
            /**
             *     14.8cm、横10.0cm
             *     740 500
             */
            .canvas {
                position: absolute;
                box-shadow: 2px 2px 8px 0px black;
                margin: auto;
                left: 0px;
                right: 0px;
                top: 0px;
                bottom: 0px;
            }
        </style>
        
        <script>
            
            window.addEventListener("load", function(){
                var canvas = document.getElementsByClassName("canvas")[0];
                drawKagamiMochi(canvas);
                
                /*
                setTimeout(function(){
                    location.href = location.href;
                }, 1000);
                /**/
            }, false);
            
            var fillCircle = function(context, x, y, radius)
            {
                context.beginPath();
                context.arc(x, y, radius, 0, Math.PI*2, false);
                context.fill();
                context.closePath();
            };
            
            var fillTriangle = function(context, x0, y0, x1, y1, x2, y2)
            {
                context.beginPath();
                context.moveTo(x0, y0);
                context.lineTo(x1, y1);
                context.lineTo(x2, y2);
                context.fill();
                context.closePath();
            };
            
            var drawKagamiMochi = function(canvas)
            {
                var context = canvas.getContext("2d");
                
                context.save();
                context.translate(0, 190);
                
                // 下のお餅を描画
                context.save();
                context.fillStyle   = "#ffc";
                context.shadowColor = "black";
                context.shadowOffsetX = 1;
                context.shadowOffsetY = 1;
                context.shadowBlur  = 4;
                context.translate(250, 235);
                context.scale(1, 0.5);
                fillCircle(context, 0, 0, 120);
                context.restore();
                // 真ん中のお餅を描画
                context.save();
                context.fillStyle   = "#ffc";
                context.shadowColor = "black";
                context.shadowOffsetX = 1;
                context.shadowOffsetY = 1;
                context.shadowBlur  = 4;
                context.translate(250, 160);
                context.scale(1, 0.5);
                fillCircle(context, 0, 0, 80);
                context.restore();
                // みかん
                context.save();
                context.fillStyle   = "orange";
                context.shadowColor = "black";
                context.shadowOffsetX = 1;
                context.shadowOffsetY = 1;
                context.shadowBlur  = 4;
                context.translate(250, 110);
                context.scale(1, 0.9);
                fillCircle(context, 0, 0, 30);
                context.restore();
                // はっぱ
                context.save();
                context.fillStyle   = "green";
                context.shadowColor = "black";
                context.shadowOffsetX = 1;
                context.shadowOffsetY = 1;
                context.shadowBlur  = 4;
                context.translate(250, 75);
                context.beginPath();
                context.moveTo(25, 0);
                context.quadraticCurveTo(0, 0, 0, 20);
                context.lineTo(0, 20);
                context.fill();
                context.beginPath();
                context.moveTo(25, 0);
                context.quadraticCurveTo(25, 20, 0, 20);
                context.lineTo(0, 20);
                context.fill();
                context.restore();
                // 台を描画
                context.save();
                context.shadowColor = "black";
                context.shadowOffsetX = 1;
                context.shadowOffsetY = 1;
                context.shadowBlur  = 4;
                context.fillStyle = "hsl(40, 60%, 60%)";
                context.fillRect(150, 300, 200, 160);
                context.fillRect(100, 250, 300, 50);
                context.restore();
                // アナ
                context.save();
                context.fillStyle = "black";
                fillCircle(context, 250, 380, 50);
                context.restore();
                // 三角巾を描画
                context.save();
                context.fillStyle = "white";
                fillTriangle(context, 130+0, 250, 250, 250+120, 370-0, 250);
                context.fillStyle = "red";
                fillTriangle(context, 130+20, 250, 250, 250+100, 370-20, 250);
                context.fillStyle = "white";
                fillTriangle(context, 130+40, 250, 250, 250+80, 370-40, 250);
                context.restore();
                
                context.restore();
                
                context.save();
                context.font = "100px 標楷體";
                context.textAlign = "center";
                context.fillText("謹賀新年", 250, 100, 500);
                context.restore();
                
                context.save();
                context.textAlign = "right";
                context.font = "20px 標楷體";
                context.fillText("本年もよろしくお願いします", 480, 160, 500);
                context.font = "20px 標楷體";
                context.fillText("平成 二四年", 420, 190, 500);
                context.fillStyle = "red";
                context.fillText("元旦", 480, 190, 500);
                context.restore();
            };
            
            var fillVerticalText = function(text, x, y, size) {
                // TODO: 縦書き処理を作ろうと思ったけど, 改行とか色々面倒なので保留
            };
            
        </script>
    </head>
    <body>
        <h1>HTML5 Canvas で鏡餅ハガキ</h1>
        <p>
        </p>
        <canvas class="canvas" width="500" height="740"></canvas>
    </body>
    
</html>

Tips

ページを自動更新させて確認しながら開発しよう!!

JavaScript でゴニョゴニョやる際のちょっとした小技です.

下記のようなコードを書いておくことで 1000 ミリ秒ごとに画面が自動更新されます. コードを書いて保存すれば自動で画面に反映されるので少し効率が上がります.

setTimeout(function(){
    location.href = location.href;
}, 1000);

ただ 1000 ミリ秒部分を短くしすぎるとウィルス扱いされちゃうので時間の設定と消し忘れにはご注意下さい.

Canvas で円(台の穴), 楕円(おもち)を描画

Canvas 上での円の描画方法は

  • Context の beginPath でパス設定を開始
  • arc で円形にパスを設定し
  • fill で塗りつぶしを行う

という流れになっています. 今回のサンプルでは下記のコードのように関数化してネイティブで用意してある標準API “fillRect” っぽく使えるよう fillCircle として定義しています.

var fillCircle = function(context, x, y, radius)
{
    context.beginPath();
    context.arc(x, y, radius, 0, Math.PI*2, false);
    context.fill();
    context.closePath();
};

楕円は, fillCircle を呼び出す前に, 変換マトリックスの scale 値を変形して楕円を描画しています.

context.scale(1, 0.5);
fillCircle(context, 0, 0, 120);

Canvas で三角形(紙)を描画

Context の beginPath, moveTo, lineTo を使って3点をパスで結び最後に fill を呼ぶことで 塗りつぶし三角形を描画することができます. 今回のサンプルでは下記のように定義して使いやすくしています.

var fillTriangle = function(context, x0, y0, x1, y1, x2, y2)
{
    context.beginPath();
    context.moveTo(x0, y0);
    context.lineTo(x1, y1);
    context.lineTo(x2, y2);
    context.fill();
    context.closePath();
};

Canvas で葉っぱを描画

半分ずつ扇状に描画することで葉っぱを描画しています.

描画には quadraticCurveTo を使って二次ベジェ曲線で描画しています.

context.beginPath();
context.moveTo(25, 0);
context.quadraticCurveTo(0, 0, 0, 20);
context.lineTo(0, 20);
context.fill();

Canvas のベジェ曲線についてはこちらが参考になります.

次回は CSS Version についての解説!!

TRACK BACK URL

POST COMMENT

メールアドレスが公開されることはありません。

COMMENT