3. JavaScriptによる描画

3.0 目的

ここでは、サンプルプログラムは 1.3 サンプルコード に従って、 準備してあるものとする。 VS Code では chap03 フォルダーを開くとサンプルプログラムが実行できる。

実行中のブラウザをいったんすべて終了してから、 sample03_01 を実行してみよう。 ブラウザが開き、以下のようなページを表示する。

_images/sample03_01.png

この中央の赤い四角はプログラムで描画したものである。 ここではこの例のように、 web ページ上に図形を描画するプログラムを学ぶ。

すでに 2. Javascript の基本 についてはよく理解しているものとする。

3.1 canvasタグ

このような描画には canvas タグを用いる。 canvas タグは HTML5 で描画のために導入された。 canvas タグの使い方を見てみよう。

先に示したページは以下の3個のファイルでできている。

sample03_01/index.html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <link rel="stylesheet" type="text/css" href="stylesheet.css">
        <script src="script.js">    </script>       
        <title>sample03_01</title>
    </head>
    <body onload='draw_canvas()'>
        <div>
            <div id='div1'>
                Javascript programing for Web - sample03_01 <br/>
                600px by 600px canvas
            </div>
            <div id='div2'></div>
            <canvas id="canvas_tag_1" width="600" height="600"></canvas>
        </div>
    
    </body>
</html>
sample03_01/stylesheet.css
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#div1 {
    float: top;
    padding-left: 10px;
    width:590px;
    height: 50px;
    border-radius: 5px;
    border-style: solid;
    border-width: 1px;
    border-color: #aaa;
    color: #0000ff;
    box-shadow: 8px 8px 5px #888;
  }
#div2 {
    float: top;
    height: 10px;
  }
#canvas_tag_1 {
    float: top;
    border-color: #aaa;
    border-style: solid;
    border-width: 1px;
    box-shadow: 8px 8px 5px #888;
  }
sample03_01/script.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// draw_canvas 関数の定義
function draw_canvas() {
  // 2次元描画用のコンテキストを作成し c1 に代入する。
  var canvas1 = document.getElementById('canvas_tag_1');
  if ( ! canvas1 || ! canvas1.getContext ) { return false; }
  var c1 = canvas1.getContext('2d');

  // 描画の開始
  c1.beginPath();
  // x=200, y=200, width=100, height=100 の矩形を定義
  c1.rect(250,250,100,100);
  // r=255, g=0, b=0 (赤色) で図形を塗りつぶす
  c1.fillStyle = 'rgb(255,0,0)';
  c1.fill();
}

この講座は JavaScript のプログラミングを学ぶことが目的だが、 canvas タグと JavaScript のプログラムの関係を理解するために、 HTML と CSS についても最小限の説明をする。

index.htmlを見てみよう。

行5~行6で stylesheet.css と script.js を参照している。

stylesheet.css ではウエブページのレイアウト、 枠線、装飾などを設定していて、たとえば描画領域をわかりやすくする「影」も設定している。 たとえば行22を、

box-shadow: 8px 8px 5px #f00;

とすると影は赤くなる。

sample03_01/index.html
5
6
        <link rel="stylesheet" type="text/css" href="stylesheet.css">
        <script src="script.js">    </script>       

行9で onload 属性を記述している。

sample03_01/index.html
9
    <body onload='draw_canvas()'>

onload 属性により、 ブラウザは index.html の中の body 要素が読み込んだ後、 JavaScript プログラム中の draw_canvas 関数を実行する。

canvas タグは Web ページに JavaScript で描画可能な領域を確保する。 行16の canvas タグでは、600px x 600px の描画領域を確保している。 px はピクセルを意味する。 Web ページ上では文字が表示されている枠より下の、 もう一つの影付きの枠が、canvas タグで確保された領域である。

sample03_01/index.html
16
            <canvas id="canvas_tag_1" width="600" height="600"></canvas>

3.2 scriptタグ

行6で script.js を参照している。

sample03_01/index.html
6
        <script src="script.js">    </script>       

このように script タグでパスを指定し、 JavaScript のプログラムが入ったファイルを読み込む。

一般的には以下のように、 JavaScript のプログラムが格納されたファイルのパスを指定する。

<script src='パス'> </script>

それ以外に以下のようにして直接 JavaScript の命令を記述することもできる。

<script> var x=0; </script>

これらの scriptタグは、 HTML ファイル中の HTML タグが記載できる場所であれば、 どこにでも、何か所にでも記載できる。

script タグが HTML ファイルの中の何か所かに分散していたり、 src 属性によって JavaScript のプログラムが他のファイルから読み込まれても、 効果としては、連続した一つのファイルになっている場合と同様である。

index.html と stylesheet.css についての説明は、 この後の JavaScript のプログラムの内容を理解するためであれば、 以上で十分だろう。

しかし、index.html, stylesheet.css はシンプルなものにとどめたから、 調べることは容易なので、 ここで説明しなかった部分についても、 興味があれば調べておくとよいだろう。

3.3 canvas への描画

3.3.1 描画の基本

ここからは、script.js について見ていく。

行3-6で canvas に描画を始めるための準備を行っている。 その結果 c1 が canvas への描画を行うための変数として設定される。 canvas とはこの Web ページ中の描画できるエリアのことだ。 以下では c1 に対する操作で各種の描画が行われる。

c1 を使ってどのように描画を行うかを理解するため、 行3-6の部分は後で見ることにして、 まず行8~行14を読んでいこう。

sample03_01/script.js
 8
 9
10
11
12
13
14
  // 描画の開始
  c1.beginPath();
  // x=200, y=200, width=100, height=100 の矩形を定義
  c1.rect(250,250,100,100);
  // r=255, g=0, b=0 (赤色) で図形を塗りつぶす
  c1.fillStyle = 'rgb(255,0,0)';
  c1.fill();

beginPathは、描画の開始をあらわす。

c1.beginPath();

rect は長方形の輪郭を設定する関数である。

c1.rect(x座標, y座標, , 高さ);

数値はいずれもピクセルで指定する。

fillStyle 属性は塗りつぶし色を指定する。

c1.fillStyle = 'rgb(Redの輝度,Greenの輝度,Blueの輝度)';

fillはで塗りつぶしを行う。

c1.fill();

ためしに、

c1.rect(0,0,100,100);

としてみよう。描画領域の左上に100 x 100の正方形が描画される。また、

c1.fillStyle = 'rgb(255,192,0);

とすると、オレンジ色の正方形が描画される。

3.3.2 2Dコンテキストの取得

次に先ほど説明をあと回しにした、 行1~行6 を簡単に説明する。 ここでは 2D コンテキストを取得する。2D とは 2次元座標(2-dimensional coordinate)、 つまり x,y 座標による平面的な描画を意味する。 ここで取得した 2D コンテキストを c1 に設定し、 この c1 を介して 2D の描画を行うことができる。

sample03_01/script.js
1
2
// draw_canvas 関数の定義
function draw_canvas() {

ここでは関数 draw_canvas を定義している。 onload すなわちここではbody要素の読み込みが終わると、 この draw_canvas 関数が呼び出される。

sample03_01/script.js
3
4
5
6
  // 2次元描画用のコンテキストを作成し c1 に代入する。
  var canvas1 = document.getElementById('canvas_tag_1');
  if ( ! canvas1 || ! canvas1.getContext ) { return false; }
  var c1 = canvas1.getContext('2d');

getElementById は HTML ドキュメント中の id canvas_tag_1 によって canvas タグを参照し、 そのidを取得する。

getContext は canvas タグ から 2Dコンテキストを作成し、 結果を c1 に代入する。

この後は c1 に対するメソッドを使い 2D 描画を行うことができる。

これらの処理は、2D 描画を行う場合常に必要になる。 以後のサンプルプログラムではこの部分の処理は同じプログラムを用いている。 変数 c1 にはつねに同じようにして 2D コンテキストを準備する。

3.4 より複雑な描画

より複雑な図形や描画を行ってみよう。

sample03_04/script.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// draw_canvas 関数の定義
function draw_canvas() {
  // 2次元描画用のコンテキストを作成し c1 に代入する。
  var canvas1 = document.getElementById('canvas_tag_1');
  if ( ! canvas1 || ! canvas1.getContext ) { return false; }
  var c1 = canvas1.getContext('2d');

  // 赤く塗りつぶされた正方形
  // x=200, y=200, width=100, height=100
  c1.beginPath();
  c1.rect(200,200,150,150);
  c1.fillStyle = 'rgb(255,0,0)';
  c1.fill();

  // 水色の正方形の枠
  // x=250, y=250, width=100, height=100
  c1.beginPath();
  c1.rect(260,260,150,150);
  c1.strokeStyle = 'rgb(0,255,255)';
  c1.lineWidth = 5;
  c1.stroke();

  // 黄色い半円
  // x=250, y=250, radius=10
  c1.beginPath();
  c1.arc(250,250,40,Math.PI*0.75,Math.PI * 1.75,false);
  c1.fillStyle = 'rgb(255,255,0)';
  c1.fill();
  
}

このプログラムはこのような図形を描画する。

_images/sample03_04.png

赤い塗りつぶされた正方形は先のプログラムと同じなので説明を省略する。

sample03_04/script.js
 8
 9
10
11
12
13
  // 赤く塗りつぶされた正方形
  // x=200, y=200, width=100, height=100
  c1.beginPath();
  c1.rect(200,200,150,150);
  c1.fillStyle = 'rgb(255,0,0)';
  c1.fill();

次に水色で正方形の輪郭のみが描画される。

sample03_04/script.js
15
16
17
18
19
20
21
  // 水色の正方形の枠
  // x=250, y=250, width=100, height=100
  c1.beginPath();
  c1.rect(260,260,150,150);
  c1.strokeStyle = 'rgb(0,255,255)';
  c1.lineWidth = 5;
  c1.stroke();

rect は同じように使用する。 rect が指定するのは形状だけで、 輪郭や塗りつぶしはあとから指定する。

c1.strokeStyle = 'rgb(0,255,255)';

ここでは、輪郭線の色として、水色を指定している。

c1.lineWidth = 5;

ここでは、輪郭線の線幅を5pxとしている。

c1.stroke();

ここで、輪郭線を描画している。

c1.beginPath();
... 複数の描画命令 ...
c1.stroke();

のように beginPath の後で複数の描画命令を実行してから、c1.stroke()を実行する。 これらの複数の描画命令で描画したすべての図形が、 c1.stroke() で描画される。

行23~28 では黄色の半円形を描画している。

sample03_04/script.js
23
24
25
26
27
28
  // 黄色い半円
  // x=250, y=250, radius=10
  c1.beginPath();
  c1.arc(250,250,40,Math.PI*0.75,Math.PI * 1.75,false);
  c1.fillStyle = 'rgb(255,255,0)';
  c1.fill();

arc は円弧を描くメソッドである。

c1.arc(x,y,r,a1,a2,ccw)

x,yは円の中心点の座標を指定する。 rは半径を指定する。単位はピクセルである。 半径5の場合、中心ドットを含め5ドットの円の円弧が描かれる。 a1,a2は開始角、終了角を指定する。 ccw は反時計周りを指定する。 true ならa1,a2は反時計周り, false なら時計周りと解釈される。 0は右方向である。

もし受講者が複素平面になじみがあれば、 canvas上に中心点を0とする複素平面を重ねた場合、 ccwがtrueの場合、a1、a2が指定する角度は、 exp(i*a1), exp(i*a1) の方向と同じである。

3.5 for を使った描画

次に for ステートメントを使ったプログラムを見てみよう。

sample03_05を実行しすると、 以下のような画面を表示する。

_images/sample03_05.png

このような画面を手作業で作るのは難しいが、 これから学ぶ for ステートメントをつかえば、プログラムはたった23行だ。

プログラムを見てみよう。

sample03_05/script.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function draw_canvas() {
  // 2次元描画用のコンテキストを作成し c1 に代入する。
  var canvas1 = document.getElementById('canvas_tag_1');
  if ( ! canvas1 || ! canvas1.getContext ) { return false; }
  var c1 = canvas1.getContext('2d');

  // 変数 i,jを定義する
  var i,j;
  // x 方向にi=0~14まで15マスを描画する
  for (i = 0; i < 15; i++) {
    // y 方向にy=0~14まで15マスを描画する
    for (j = 0; j < 15; j++) {
          c1.beginPath();
          // i の値によって r(赤)の輝度を変化させる
          // toString(10)で、文字列に変換
          var red = (i * 18).toString(10); 
          c1.fillStyle = 'rgb(' + red + ',0,0)';
          // i,j を座標に変換
          c1.rect(i * 40, j * 40, 38, 38);
          c1.fill(); // 色を塗る
      }
  }
}

3.5.1 for ステートメント

for ステートメントを見てみよう。

sample03_05/script.js
 7
 8
 9
10
11
12
  // 変数 i,jを定義する
  var i,j;
  // x 方向にi=0~14まで15マスを描画する
  for (i = 0; i < 15; i++) {
    // y 方向にy=0~14まで15マスを描画する
    for (j = 0; j < 15; j++) {

変数 i, j を定義し、for 分で i は 0~15, jも 0~15 となるように二重ループを作る。

sample03_05/script.js
15
16
          // toString(10)で、文字列に変換
          var red = (i * 18).toString(10); 

行12~行21は、まずi=0で実行され、次にi=1、次にi=2とくりかえし実行される。 またその中で、行13~行20は、まずj=0、次にj=1と異なるjの値について繰り返される。 結果として、行13~行20は、以下のように実行される。

i=0,j=0
i=0,j=1
i=0,j=2
...
i=0,j=14
i=1,j=0
i=1,j=1
...
i=14,j=14

15×15回で、計225回、異なるiとjの組み合わせで実行される。

このiとjで、描画するマス目の位置とマス目の色を変化させる。

3.5.2 色の指定

色の指定は fillStyleで行う。

c1.fillStyle = 'rgb(64,128,255)';

この場合、red:64、greee:128、blue:255の色を指定する。 各数値は0~255(最大)で輝度をあらわしており、 赤約25%, 緑約50%輝度、青100%の輝度の色を表す。

マス目の色をマスの位置に従って変化させるために、 以下のように、赤の輝度を i * 18 にすればよい。

i=0  ,  rgb(0,0,0);
i=1  ,  rgb(18,0,0);
i=2  ,  rgb(36,0,0);
...
i=14  ,  rgb(252,0,0);

3.5.3 文字列中の式

しかし、

c1.fillStyle='rgb(i * 18, 0, 0)'

という代入では、i = 1 の場合に

c1.fillStyle='rgb(18, 0, 0)'

という結果にはならない。

'i * 18'という部分も文字列なので、 JavaScript ではこれがそのまま計算されることはなく、

'rgb(i * 18, 0, 0)'

という文字列がそのまま fillStyle にわたってしまう。

なぜそれでは問題なのだろうか。 プログラミング初心者にはわかりにくいかもしれないから、 少し補足する。

変数名や式を解釈したり実行するのは、 JavaScript 処理系である。 つまり、コンパイラ、インタプリタなどの、 プログラミング言語の処理系でなければ、 計算式や変数の文字列を解釈して計算することはできない。

fillStyle は JavaScript 処理系の一部ではなく、 ライブラリの関数にすぎないので、 あらかじめ定義された形式の文字列を処理できるだけで、 文字列として i * 18 を受け取っても、 それを JavaScript の処理系のように計算式として処理することができない。

fillStyle には、

c1.fillStyle='rgb(18, 0, 0)'

という、 すでに具体的な数値による色指定の文字列を与えないと、 色指定ができないのである。

3.5.4 数値からrgbへ

そこで toString を用いる。 数値に対し toString(10) を使うと、 その数値を10進法であらわした文字列に変換することができる。

red = (i * 18).toString(10)

という式で、i * 18 の結果である18という数値を、 '18' という文字列に変換できる。

こうして得られた red という変数にある文字列 '18' を用いて、

'rgb(' + red + ',0,0)'

という式を使うと、

'rgb('  '18'  ',0,0)'

という3つの文字例が結合され、

'rgb(18, 0, 0)'

という文字列が完成する。

このようにして、マス目により異なる色指定を行って、 i の値によって赤の濃淡を変化させているのである。

3.6 if を使った描画

次に for ステートメントの中に if を使ったプログラムを書いてみる。

sample03_06/script.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function draw_canvas() {
  // 2次元描画用のコンテキストを作成し c1 に代入する。
  var canvas1 = document.getElementById('canvas_tag_1');
  if ( ! canvas1 || ! canvas1.getContext ) { return false; }
  var c1 = canvas1.getContext('2d');

  // 変数 i,jを定義する
  var i,j;
  // x 方向にi=0~14まで15マスを描画する
  for (i = 0; i < 15; i++) {
    // y 方向にy=0~14まで15マスを描画する
    for (j = 0; j < 15; j++) {
      c1.beginPath();
      // i と j の値の大小によって色を変える
      if (i == j) {
        c1.fillStyle = 'rgb(0,32,128)';    // 紺
      }
      else {
        c1.fillStyle = 'rgb(128,192,255)'; // 水色
      }
      // i,j を座標に変換
      c1.rect(i * 40, j * 40, 38, 38);
      c1.fill(); // 色を塗る
    }
  }
}

このプログラムは以下のような画面を表示する。

_images/sample03_06.png

このプログラムはfillstyleを決める部分で if が使われている。

sample03_06/script.js
14
15
16
17
18
19
20
      // i と j の値の大小によって色を変える
      if (i == j) {
        c1.fillStyle = 'rgb(0,32,128)';    // 紺
      }
      else {
        c1.fillStyle = 'rgb(128,192,255)'; // 水色
      }

for ループの中ではi と j の値により、 左から i 番目, 右から j 番目の正方形が描画される。 その際に、

if (i == j) {

により i と j が等しいか否かを判定している。 もし等しければ紺を、異なれば水色を設定している。 したがって斜めの対角線上のマス目は i == j であるから紺色で、 それ以外は水色で描画される。

3.7 練習問題

3.7.1 出題

それでは、少し if 文の条件式を考えてみよう。 この後いくつかの描画画面を示すが、これらはすべて if 文の条件式を変えるだけで描画できる。

条件式の指定にどのような演算子が使えるかを、まず確認しよう。iii.3 比較演算iii.演算子 については Quick Reference を参照しよう。

これらの演算子を使って、以下にあげる描画画面を描画できる。 どのような条件式にすればよいかを考えてみよう。

この問題は初心者には少し難しい問題なので、あせらずに時間をかけてほしい。 自分で考えるのが難しい場合は、問題の後のヒントをみるとよい。 それでも難しいようなら、実際にヒントにある条件式を使ってプログラムを動かしてみてから、どうしてそのような動作になるか考えてみてもよい。 結果を納得できるようになるまで、いろいろ試してみよう。

Q.0

_images/sample03_07-0.png

Q.1

_images/sample03_07-1.png

Q.2

_images/sample03_07-2.png

Q.3

_images/sample03_07-3.png

Q.4

_images/sample03_07-4.png

Q.5

_images/sample03_07-5.png

Q.6

_images/sample03_07-6.png

Q.7

_images/sample03_07-7.png

Q.8

_images/sample03_07-8.png

Q.9

_images/sample03_07-9.png

3.7.2 ヒント

以下の条件式のどれかである。ただし、番号は問題の番号とは対応していない。 iii.3 比較演算iii.演算子 については Quick Reference を参照しよう。

条件式

番号

(i == 7 || i == j)

1

((15 + i - j) % 3 == 0 )

2

((15 + i - j) % 5 < 3 )

3

((i % 3) * (j % 3) == 0))

4

(i > 9 && j < 5 || i < 5 && j > 9)

5

((i < 8) == (j % 2 == 0))

6

(i == 7 || j == 7)

7

(((15 + i - j) % 3) * ((i + j) % 3) == 0)

8

((i < 8) == (j < 8))

9

((i % 5) > (j % 5))

10

3.8 まとめ

  1. HTML5 では canvas を使って図形を描画できる。

  2. rect で x座標 , y座標、w:幅, h:高さ、を指定できる。

  3. 様々な描画コマンドを用いて様々な形状を描画できる。

  4. 枠のみ、塗りつぶし、塗りつぶしの色、影など、様々な描画方法を指定できる。