SE_BOKUのまとめノート的ブログ

SE_BOKUが知ってること・勉強したこと・考えたことetc

CanvasAPIチュートリアル(3)/HTML5&JavaScript

f:id:arakan_no_boku:20210106233420p:plain

 MDNのCanvasAPIのチュートリアルを参考に、自分なりにちょっとアレンジして遊んでみた・・の3回目です。

developer.mozilla.org 

目次

前回と前々回のリンク

前回、前々回のリンクは以下です。

arakan-pgm-ai.hatenablog.com

arakan-pgm-ai.hatenablog.com

サンプル画面の結果イメージ 

今回は、(1)(2)で使った以下のフレームを流用して、チュートリアル説明コメントとJavaScript部分だけ入れ替えて、別のチュートリアルをやってみます。

最終的にはこんな感じになりました。

f:id:arakan_no_boku:20210114003200p:plain

ほぼほぼ、チュートリアルに掲載されているものをそのまま使って、ちょこっとだけ変えてみる・・感じでやってます。 

JavaScript:画像を切り取り額縁にはめる 

このチュートリアルでは、こんなサイの画像と。

f:id:arakan_no_boku:20210115003354p:plain

こんな額縁の画像を使います。

f:id:arakan_no_boku:20210115003524p:plain

で・・、サイの画像の顔の部分を額縁の内側にはまるサイズで切り取って、合成して、額縁におさめられたサイの顔の写真的な画像を表示します。。

f:id:arakan_no_boku:20210115003755p:plain

Canvasで画像ファイルを使う一番簡単なのは、HTML側で、こんな感じにDisplay:noneでimgタグに画像を指定して、IDで指定する方法です。

<div class="content">
    <div style="display:none;">
        <img id="source" src="https://mdn.mozillademos.org/files/5397/rhino.jpg" width="300" height="227">
        <img id="frame" src="https://mdn.mozillademos.org/files/242/Canvas_picture_frame.png" width="132" height="150">
    </div>
    <canvas id="c01"></canvas>
</div>

IDを指定してあるので、JavaScript側ではこれで簡単に画像イメージが取得できます。

document.getElementById('source')

あとは、それを、そのままCanvasAPIの「drawImage」メソッドの引数に渡すだけ。

なので、上記を行うJavaScriptはこうなります。

var c01 = document.getElementById('c01');
if (c01.getContext){
    var ctx = c01.getContext("2d")
    // Draw slice
    ctx.drawImage(document.getElementById('source'), 33, 71, 104, 124, 21, 20, 87, 104);
    // Draw frame
    ctx.drawImage(document.getElementById('frame'), 0, 0);  
}else{
    console.log('canvasオブジェクトがNullです。');
}

drawImageの引数の説明は以下にあります。

developer.mozilla.org

上記の「 ctx.drawImage(document.getElementById('source'), 33, 71, 104, 124, 21, 20, 87, 104);」は、サイの画像を「X座標:33、Y座標71 から幅:104、高さ:124」だけ切り取って、Canvasの「X座標:21、Y座標:20」に「幅:87、高さ104」で描画する・・という風に読んでいくと「なるほどな・・」って感じです。

JavaScript:折れ線グラフを描く 

 簡単な折れ線グラフをCanvasAPIで描いてます。

といっても、

こんな感じの背景画像の上に座標をあわせて折れ線を描くだけですが。

f:id:arakan_no_boku:20210113013530p:plain

背景画像はHTMLで指定します。

<div class="content">
   <div style="display:none;">
       <img id="back" src="https://mdn.mozillademos.org/files/5395/backdrop.png" />
   </div>
   <canvas id="c02"></canvas>
</div>

対応するJavaScriptの該当部分です。

var c02 = document.getElementById('c02');
if (c02.getContext){
    var ctx = c02.getContext("2d")
    ctx.beginPath();
    ctx.drawImage(document.getElementById('back'), 0, 0);    
    ctx.moveTo(30, 96);
    ctx.lineTo(70, 66);
    ctx.lineTo(103, 76);
    ctx.lineTo(170, 15);
    ctx.stroke();    
}else{
    console.log('canvasオブジェクトがNullです。');
}

Pathの中で背景画像と、線を描き、stroke()で表示する。

ほぼ、それだけです。 

JavaScript:座標変換・回転・サイズ変更 

このページにある「座標の変換」「回転」「サイズ変更」をひとつにまとめてやってみました。

developer.mozilla.org

 結果はこの表示になります。

f:id:arakan_no_boku:20210117203852p:plain

このケースだと、HTML部分はCanvasの指定だけです。

<div class="content">
   <canvas id="c03"></canvas>
</div>

対応するJavaScriptの該当部分です。

var c03 = document.getElementById('c03');
if (c03.getContext){
    var ctx = c03.getContext("2d")
    for (var i = 0; i < 3; i++) {
        for (var j = 0; j < 3; j++) {
            ctx.save();
            ctx.fillStyle = 'rgb(' + (51 * i) + ', ' + (255 - 51 * i) + ', 255)';
            ctx.translate(10 + j * 50, 10 + i * 50);
            if(j >= 2){
                ctx.rotate((Math.PI / 180) * 25); // rotate
                ctx.scale(1.3,1.3);
            }  
            ctx.fillRect(0, 0, 25, 25);
            ctx.restore();
        }
     }
 
}else{
    console.log('canvasオブジェクトがNullです。');
}

以下の3つの操作をしながら塗りつぶし四角を描いてます。

  • 原点(0,0)を指定の位置に移動する「translate(x, y)」
  • 原点を中心にキャンバスを回転する「rotate(angle)」
  • X方向にx倍、Y方向にy倍拡大する「scale(x, y)」
JavaScriptクリッピングされた描画

 最後はこんな感じの画像を描きます。

f:id:arakan_no_boku:20210117210830p:plain

黒で塗りつぶした四角形をえがいて、その上に円を置いて、その中で星を描画しているのですが、それが円からはみ出さないように「クリッピング」されてますよ・・というものです。

CanvasAPIでは、クリッピングが実にスマートに実現できるようになっていて、上記だと、Pathに図形を描画して、最後にclip()を使うだけです。

 ctx.beginPath();
 ctx.arc(0, 0, 60, 0, Math.PI * 2, true);
 ctx.clip();

実にスマートかつシンプルです。

チュートリアルでは、白い星を描いています。

function drawStar(ctx, r) {
    ctx.save();
    ctx.beginPath();
    ctx.moveTo(r, 0);
    for (var i = 0; i < 9; i++) {
      ctx.rotate(Math.PI / 5);
      if (i % 2 === 0) {
        //ctx.lineTo((r / 0.525731) * 0.200811, 0);
        ctx.lineTo((r / 0.0525731) * 0.350811, 0);
    } else {
        ctx.lineTo(r, 0);
      }
    }
    ctx.closePath();
    ctx.fill();
    ctx.restore();
  }

本家のチュートリアルと同じですが、サイズだけ少し大きくしてクリップされた感じをわかりやすくしてます。

四角を描いて、円のクリッピング領域を描いて、座標を移動しながら星を描く。

そんな処理は以下のようになります。

var c04 = document.getElementById('c04');
if (c04.getContext){
    var ctx = c04.getContext("2d")
    ctx.fillRect(0, 0, 150, 150);
    ctx.translate(75, 75);

    // Create a circular clipping path
    ctx.beginPath();
    ctx.arc(0, 0, 60, 0, Math.PI * 2, true);
    ctx.clip();

    // draw stars
    for (var j = 1; j < 50; j++) {
        ctx.save();
        ctx.fillStyle = '#fff';
        ctx.translate(75 - Math.floor(Math.random() * 150),
                      75 - Math.floor(Math.random() * 150));
        drawStar(ctx, Math.floor(Math.random() * 4) + 2);
        ctx.restore();
    }
 
}else{
    console.log('canvasオブジェクトがNullです。');
}

これを動かした結果です。 

f:id:arakan_no_boku:20210123002404p:plain

黒一色で、最初にのせた画像と円の中の色が違いますね。

ここに円内の背景色をグラデーションにするには。

var lingrad = ctx.createLinearGradient(0, -75, 0, 75);
lingrad.addColorStop(0, '#232256');
lingrad.addColorStop(1, '#143778');

ctx.fillStyle = lingrad;
ctx.fillRect(-75, -75, 150, 150);

こういうのをclip()の後続に追加してやります。

そうすると、グラデーションで描いた背景もクリッピングされて円の背景みたいにみえるみたいです。 

JavaScript全体 

CanvasAPIは、インタフェースが直観的でわかりやすいし、いいですね。

最後に今回のJavaScript部分全体です。

demo_p02,js

var c01 = document.getElementById('c01');
if (c01.getContext){
    var ctx = c01.getContext("2d")
    // Draw slice
    ctx.drawImage(document.getElementById('source'), 33, 71, 104, 124, 21, 20, 87, 104);
    // Draw frame
    ctx.drawImage(document.getElementById('frame'), 0, 0);  
}else{
    console.log('canvasオブジェクトがNullです。');
}

var c02 = document.getElementById('c02');
if (c02.getContext){
    var ctx = c02.getContext("2d")
    ctx.beginPath();
    ctx.drawImage(document.getElementById('back'), 0, 0);    
    ctx.moveTo(30, 96);
    ctx.lineTo(70, 66);
    ctx.lineTo(103, 76);
    ctx.lineTo(170, 15);
    ctx.stroke();    
}else{
    console.log('canvasオブジェクトがNullです。');
}

var c03 = document.getElementById('c03');
if (c03.getContext){
    var ctx = c03.getContext("2d")
    for (var i = 0; i < 3; i++) {
        for (var j = 0; j < 3; j++) {
            ctx.save();
            ctx.fillStyle = 'rgb(' + (51 * i) + ', ' + (255 - 51 * i) + ', 255)';
            ctx.translate(10 + j * 50, 10 + i * 50);
            if(j >= 2){
                ctx.rotate((Math.PI / 180) * 25); // rotate
                ctx.scale(1.3,1.3);
            }  
            ctx.fillRect(0, 0, 25, 25);
            ctx.restore();
        }
     }
 
}else{
    console.log('canvasオブジェクトがNullです。');
}

var c04 = document.getElementById('c04');
if (c04.getContext){
    var ctx = c04.getContext("2d")
    ctx.fillRect(0, 0, 150, 150);
    ctx.translate(75, 75);

    // Create a circular clipping path
    ctx.beginPath();
    ctx.arc(0, 0, 60, 0, Math.PI * 2, true);
    ctx.clip();

    // draw background
    var lingrad = ctx.createLinearGradient(0, -75, 0, 75);
    lingrad.addColorStop(0, '#232256');
    lingrad.addColorStop(1, '#143778');

    ctx.fillStyle = lingrad;
    ctx.fillRect(-75, -75, 150, 150);

    // draw stars
    for (var j = 1; j < 50; j++) {
        ctx.save();
        ctx.fillStyle = '#fff';
        ctx.translate(75 - Math.floor(Math.random() * 150),
                      75 - Math.floor(Math.random() * 150));
        drawStar(ctx, Math.floor(Math.random() * 4) + 2);
        ctx.restore();
    }
 
}else{
    console.log('canvasオブジェクトがNullです。');
}

function drawStar(ctx, r) {
    ctx.save();
    ctx.beginPath();
    ctx.moveTo(r, 0);
    for (var i = 0; i < 9; i++) {
      ctx.rotate(Math.PI / 5);
      if (i % 2 === 0) {
        //ctx.lineTo((r / 0.525731) * 0.200811, 0);
        ctx.lineTo((r / 0.0525731) * 0.350811, 0);
    } else {
        ctx.lineTo(r, 0);
      }
    }
    ctx.closePath();
    ctx.fill();
    ctx.restore();
  }

あと、HTML部分も。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>BULMAとCanvasチュートリアル</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="https://use.fontawesome.com/releases/v5.3.1/js/all.js" defer ></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.8.0/css/bulma.min.css" />
  </head>
  <body>
   <header>
        <div class="hero is-info is-bold">
            <div class="hero-body">
                <div class="container">
                    <h1 class="title">BulmaとCanvasを使ったチュートリアル</h1>
                    <h2 class="subtitle">@BOKU</h2>
                </div>
            </div>
        </div>
    </header>
    <div class="columns">
        <div class="column is-half">
            <article class="box media">
                <figure class="media-left">
                    <p class="image is-48x48">
                        <img src="images/s01.JPG">
                    </p>
                </figure>
                <div class="media-content">
                    <div class="content">
                        <p><strong>チュートリアル01</strong><br>
                           額縁の中にサイの顔をはめこんでみる <br>
                        </p>
                    </div>
                    <div class="content">
                        <div style="display:none;">
                            <img id="source" src="https://mdn.mozillademos.org/files/5397/rhino.jpg" width="300" height="227">
                            <img id="frame" src="https://mdn.mozillademos.org/files/242/Canvas_picture_frame.png" width="132" height="150">
                        </div>
                        <canvas id="c01"></canvas>
                    </div>
                </div>
            </article>
      </div>
        <div class="column is-half">
            <article class="box media">
                <figure class="media-left">
                    <p class="image is-48x48">
                        <img src="images/s02.JPG">
                    </p>
                </figure>
                <div class="media-content">
                    <div class="content">
                        <p><strong>チュートリアル02</strong><br>
                            簡単な折れ線グラフを描いてみる<br>
                        </p>
                    </div>
                    <div class="content">
                        <div style="display:none;">
                            <img id="back" src="https://mdn.mozillademos.org/files/5395/backdrop.png" />
                        </div>
                        <canvas id="c02"></canvas>
                    </div>
                </div>
            </article>
      </div>
    </div>
     <div class="columns">
        <div class="column is-half">
            <article class="box media">
                <figure class="media-left">
                    <p class="image is-48x48">
                        <img src="images/s03.JPG">
                    </p>
                </figure>
                <div class="media-content">
                    <div class="content">
                        <p><strong>チュートリアル03</strong><br>
                            位置変換・回転・サイジング<br>
                        </p>
                    </div>
                    <div class="content">
                        <canvas id="c03"></canvas>
                    </div>
                </div>
            </article>
      </div>
        <div class="column is-half">
            <article class="box media">
                <figure class="media-left">
                    <p class="image is-48x48">
                        <img src="images/s04.JPG">
                    </p>
                </figure>
                <div class="media-content">
                    <div class="content">
                        <p><strong>チュートリアル04</strong><br>
                            クリッピングを使ってみる<br>
                        </p>
                    </div>
                    <div class="content">
                        <canvas id="c04"></canvas>
                    </div>
                </div>
            </article>
      </div>
    </div>
    <footer class="footer">
        <div class="container">
            <div class="content has-text-centered">
                2021 asakan-boku
            </div>
        </div>
    </footer>
    <script src="js/demo_p02.js"></script>
  </body>
</html>

今回はこんなところで。

ではでは。

#JS