"BOKU"のITな日常

テクノロジー以外にも、日常には、面白そうな”イット”がつまってるんだな

HTML5/CanvasAPIでニコニコマークと実際に動く丸時計を描いてみる(2)

f:id:arakan_no_boku:20210106233420p:plain

Html5のCanvasAPIのチュートリアルを参考にやってみた・・の2回目です。 

前回はこちらです。

arakan-pgm-ai.hatenablog.com

MDNのCanvasAPIのチュートリアル。。

developer.mozilla.org

 

これを参考に、自分なりにCSSにBulmaを使って以下のようなサンプル画面を作ってみました・・というのが前回の話でした。

f:id:arakan_no_boku:20210106235912p:plain

 今回は上記の「チュートリアル03」と「チュートリアル04」についてやります。

前回で書いた内容は再掲しませんので、前回の記事から見てもらう方がわかりやすいとは思います。

 

チュートリアル03

 

ニコニコマークを描いてみました・・てやつです。

サンプルとしてチュートリアルに掲載されていた、そのままに近いです。

f:id:arakan_no_boku:20210111171415p:plain

これを描くソースは以下です。

var c03 = document.getElementById('c03');
if (c03.getContext){
    var ctx = c03.getContext("2d")
    ctx.beginPath();
    ctx.arc(75, 75, 50, 0, Math.PI * 2, true); // Outer circle
    ctx.moveTo(110, 75);
    ctx.arc(75, 75, 35, 0, Math.PI, false);  // Mouth (clockwise)
    ctx.moveTo(65, 65);
    ctx.arc(60, 65, 5, 0, Math.PI * 2, true);  // Left eye
    ctx.moveTo(95, 65);
    ctx.arc(90, 65, 5, 0, Math.PI * 2, true);  // Right eye
    ctx.stroke();
}else{
    console.log('canvasオブジェクトがNullです。');
}

pathとかstrokeとかの意味と使い方は、前回の繰り返しになるので特に説明しませんが、移動して(moveTo)そこに円弧を描く(arc) を繰り返しているだけです。

補足は特にありません。

 

チュートリアル04

 

丸時計を描きます。

f:id:arakan_no_boku:20210111171912p:plain

秒針も動くちゃんとした時計です。

ちょっと、ソースコード的には長めですが、素のCanvasを使って、ちゃんと動く時計をブラウザに表示できるチュートリアルって、なんか、贅沢感があります(笑)

先に時計を描く、ソースコードです。

function clock(){
    var c04 = document.getElementById('c04');
    if (c04.getContext){
        var now = new Date();
        var ctx = c04.getContext("2d")
        ctx.save();
        ctx.clearRect(0, 0, 150, 150);
        ctx.translate(75, 75);
        ctx.scale(0.4, 0.4);
        ctx.rotate(-Math.PI / 2);
        ctx.strokeStyle = 'black';
        ctx.fillStyle = 'white';
        ctx.lineWidth = 8;
        ctx.lineCap = 'round';
  
        // Hour marks
        ctx.save();
        for (var i = 0; i < 12; i++) {
            ctx.beginPath();
            ctx.rotate(Math.PI / 6);
            ctx.moveTo(100, 0);
            ctx.lineTo(120, 0);
            ctx.stroke();
        }
        ctx.restore();
  
        // Minute marks
        ctx.save();
        ctx.lineWidth = 5;
        for (i = 0; i < 60; i++) {
            if (i % 5!= 0) {
                ctx.beginPath();
                ctx.moveTo(117, 0);
                ctx.lineTo(120, 0);
                ctx.stroke();
            }
            ctx.rotate(Math.PI / 30);
        }
        ctx.restore();
  
        var sec = now.getSeconds();
        var min = now.getMinutes();
        var hr  = now.getHours();
        hr = hr >= 12 ? hr - 12 : hr;  
        ctx.fillStyle = 'black';
  
        // write Hours
        ctx.save();
        ctx.rotate(hr * (Math.PI / 6) + (Math.PI / 360) * min + (Math.PI / 21600) *sec);
        ctx.lineWidth = 14;
        ctx.beginPath();
        ctx.moveTo(-20, 0);
        ctx.lineTo(80, 0);
        ctx.stroke();
        ctx.restore();
  
        // write Minutes
        ctx.save();
        ctx.rotate((Math.PI / 30) * min + (Math.PI / 1800) * sec);
        ctx.lineWidth = 10;
        ctx.beginPath();
        ctx.moveTo(-28, 0);
        ctx.lineTo(112, 0);
        ctx.stroke();
        ctx.restore();
  
        // Write seconds
        ctx.save();
        ctx.rotate(sec * Math.PI / 30);
        ctx.strokeStyle = '#D40000';
        ctx.fillStyle = '#D40000';
        ctx.lineWidth = 6;
        ctx.beginPath();
        ctx.moveTo(-30, 0);
        ctx.lineTo(83, 0);
        ctx.stroke();
        ctx.beginPath();
        ctx.arc(0, 0, 10, 0, Math.PI * 2, true);
        ctx.fill();
        ctx.beginPath();
        ctx.arc(95, 0, 10, 0, Math.PI * 2, true);
        ctx.stroke();
        ctx.fillStyle = 'rgba(0, 0, 0, 0)';
        ctx.arc(0, 0, 3, 0, Math.PI * 2, true);
        ctx.fill();
        ctx.restore();
  
        ctx.beginPath();
        ctx.lineWidth = 14;
        ctx.strokeStyle = '#325FA2';
        ctx.arc(0, 0, 142, 0, Math.PI * 2, true);
        ctx.stroke();
        ctx.restore();
        window.requestAnimationFrame(clock);
    }else{
        console.log('canvasオブジェクトがNullです。');
    }
}
window.requestAnimationFrame(clock);

時計を描くclock()メソッドをコールバックとして、window.requestAnimationFrame()で動かしています。

developer.mozilla.org

よく見るとclock()メソッドの中でも「window.requestAnimationFrame()」を再帰的に実行しています。

このテクニックは自分は知らなかったです。

やや長いですが、clock()メソッドの中で文字盤を描いて、針を描いて・・というのを、コツコツやってるだけなので、じっくり見ると、なんとなくわかります。

内部で、save()とrestore()を使ってます。

f:id:arakan_no_boku:20210112230515p:plain

この説明だけ見るとピンときませんが、高度なスタックだと考えればいいみたいです。

つまり。

save()を呼び出すたびに、その時点のすべての設定がスタックにプッシュされます。

restore()を呼び出すたびに、最後に保存されたすべての設定が復元されるわけです。 

このへん・・頭の中だけでは、なかなかイメージしきれません。

自分も上から少しずつ実行して、その時の画面を見て・・というのを地道にやって初めて「ああ、そういうことか!」とわかりました。

やっぱ、ソースはただ眺めるより、動かしてみたほうが速い・・ですね。

 

前回と今回遠しのソースです

 

JavaScript部分だけ、前回と今回をくっつけた全体を再掲しておきます。

demo_p01.js

var c01 = document.getElementById('c01');
if (c01.getContext){
    var ctx = c01.getContext("2d")
    for (var i = 0; i < 6; i++) {
        for (var j = 0; j < 6; j++) {
          ctx.fillStyle = 'rgb(' + Math.floor(255 - 42.5 * i) + ', ' +
                           Math.floor(255 - 42.5 * j) + ', 0)';
          ctx.fillRect(j * 25, i * 25, 25, 25);
        }
    }
    ctx.globalAlpha = 0.2;
    ctx.fillStyle = '#FFF';
       for (var i = 0; i < 7; i++) {
        ctx.beginPath();
        ctx.arc(75, 75, 10 + 10 * i, 0, Math.PI * 2, true);
        ctx.fill();
    }  
}else{
    console.log('canvasオブジェクトがNullです。');
}

var c02 = document.getElementById('c02');
if (c02.getContext){
    var ctx = c02.getContext("2d")
    ctx.font = '48px serif';
    ctx.fillText('Hello world', 10, 50);
    ctx.strokeText('Hello world', 30, 70);
    ctx.strokeStyle = '#F22'
    for (var i = 0; i < 5; i++) {
        ctx.lineWidth = 1 + i;
        ctx.beginPath();
        ctx.moveTo(30,10 * i + 70);
        ctx.lineTo(300,10 * i + 70);
        ctx.stroke();
    }  
 
}else{
    console.log('canvasオブジェクトがNullです。');
}

var c03 = document.getElementById('c03');
if (c03.getContext){
    var ctx = c03.getContext("2d")
    ctx.beginPath();
    ctx.arc(75, 75, 50, 0, Math.PI * 2, true); // Outer circle
    ctx.moveTo(110, 75);
    ctx.arc(75, 75, 35, 0, Math.PI, false);  // Mouth (clockwise)
    ctx.moveTo(65, 65);
    ctx.arc(60, 65, 5, 0, Math.PI * 2, true);  // Left eye
    ctx.moveTo(95, 65);
    ctx.arc(90, 65, 5, 0, Math.PI * 2, true);  // Right eye
    ctx.stroke();
}else{
    console.log('canvasオブジェクトがNullです。');
}

function clock(){
    var c04 = document.getElementById('c04');
    if (c04.getContext){
        var now = new Date();
        var ctx = c04.getContext("2d")
        ctx.save();
        ctx.clearRect(0, 0, 150, 150);
        ctx.translate(75, 75);
        ctx.scale(0.4, 0.4);
        ctx.rotate(-Math.PI / 2);
        ctx.strokeStyle = 'black';
        ctx.fillStyle = 'white';
        ctx.lineWidth = 8;
        ctx.lineCap = 'round';
  
        // Hour marks
        ctx.save();
        for (var i = 0; i < 12; i++) {
            ctx.beginPath();
            ctx.rotate(Math.PI / 6);
            ctx.moveTo(100, 0);
            ctx.lineTo(120, 0);
            ctx.stroke();
        }
        ctx.restore();
  
        // Minute marks
        ctx.save();
        ctx.lineWidth = 5;
        for (i = 0; i < 60; i++) {
            if (i % 5!= 0) {
                ctx.beginPath();
                ctx.moveTo(117, 0);
                ctx.lineTo(120, 0);
                ctx.stroke();
            }
            ctx.rotate(Math.PI / 30);
        }
        ctx.restore();
  
        var sec = now.getSeconds();
        var min = now.getMinutes();
        var hr  = now.getHours();
        hr = hr >= 12 ? hr - 12 : hr;  
        ctx.fillStyle = 'black';
  
        // write Hours
        ctx.save();
        ctx.rotate(hr * (Math.PI / 6) + (Math.PI / 360) * min + (Math.PI / 21600) *sec);
        ctx.lineWidth = 14;
        ctx.beginPath();
        ctx.moveTo(-20, 0);
        ctx.lineTo(80, 0);
        ctx.stroke();
        ctx.restore();
  
        // write Minutes
        ctx.save();
        ctx.rotate((Math.PI / 30) * min + (Math.PI / 1800) * sec);
        ctx.lineWidth = 10;
        ctx.beginPath();
        ctx.moveTo(-28, 0);
        ctx.lineTo(112, 0);
        ctx.stroke();
        ctx.restore();
  
        // Write seconds
        ctx.save();
        ctx.rotate(sec * Math.PI / 30);
        ctx.strokeStyle = '#D40000';
        ctx.fillStyle = '#D40000';
        ctx.lineWidth = 6;
        ctx.beginPath();
        ctx.moveTo(-30, 0);
        ctx.lineTo(83, 0);
        ctx.stroke();
        ctx.beginPath();
        ctx.arc(0, 0, 10, 0, Math.PI * 2, true);
        ctx.fill();
        ctx.beginPath();
        ctx.arc(95, 0, 10, 0, Math.PI * 2, true);
        ctx.stroke();
        ctx.fillStyle = 'rgba(0, 0, 0, 0)';
        ctx.arc(0, 0, 3, 0, Math.PI * 2, true);
        ctx.fill();
        ctx.restore();
  
        ctx.beginPath();
        ctx.lineWidth = 14;
        ctx.strokeStyle = '#325FA2';
        ctx.arc(0, 0, 142, 0, Math.PI * 2, true);
        ctx.stroke();
        ctx.restore();
        window.requestAnimationFrame(clock);
    }else{
        console.log('canvasオブジェクトがNullです。');
    }
}
window.requestAnimationFrame(clock);

HTML 部分は前回の記事を参照ください。

今回はこんなところで。

ではでは。