.. _xx_3_draw: ======================= 3. JavaScriptによる描画 ======================= 3.0 目的 -------- ここでは、サンプルプログラムは :ref:`sample_code` に従って、 準備してあるものとする。 VS Code では chap03 フォルダーを開くとサンプルプログラムが実行できる。 実行中のブラウザをいったんすべて終了してから、 sample03_01 を実行してみよう。 ブラウザが開き、以下のようなページを表示する。 .. image:: ../images/chap03/sample03_01.png この中央の赤い四角はプログラムで描画したものである。 ここではこの例のように、 web ページ上に図形を描画するプログラムを学ぶ。 すでに :ref:`xx_2_jsbasic` についてはよく理解しているものとする。 3.1 canvasタグ -------------- このような描画には canvas タグを用いる。 canvas タグは HTML5 で描画のために導入された。 canvas タグの使い方を見てみよう。 先に示したページは以下の3個のファイルでできている。 .. literalinclude:: ../program/chap03/sample03_01/index.html :caption: sample03_01/index.html :language: html :linenos: .. literalinclude:: ../program/chap03/sample03_01/stylesheet.css :caption: sample03_01/stylesheet.css :language: css :linenos: .. literalinclude:: ../program/chap03/sample03_01/script.js :caption: sample03_01/script.js :language: JavaScript :linenos: この講座は JavaScript のプログラミングを学ぶことが目的だが、 canvas タグと JavaScript のプログラムの関係を理解するために、 HTML と CSS についても最小限の説明をする。 index.htmlを見てみよう。 行5~行6で stylesheet.css と script.js を参照している。 stylesheet.css ではウエブページのレイアウト、 枠線、装飾などを設定していて、たとえば描画領域をわかりやすくする「影」も設定している。 たとえば行22を、 :: box-shadow: 8px 8px 5px #f00; とすると影は赤くなる。 .. literalinclude:: ../program/chap03/sample03_01/index.html :caption: sample03_01/index.html :language: html :lines: 5-6 :lineno-start: 5 .. _xx_onload: 行9で onload 属性を記述している。 .. literalinclude:: ../program/chap03/sample03_01/index.html :caption: sample03_01/index.html :language: html :lines: 9 :lineno-start: 9 onload 属性により、 ブラウザは index.html の中の body 要素が読み込んだ後、 JavaScript プログラム中の draw_canvas 関数を実行する。 canvas タグは Web ページに JavaScript で描画可能な領域を確保する。 行16の canvas タグでは、600px x 600px の描画領域を確保している。 px はピクセルを意味する。 Web ページ上では文字が表示されている枠より下の、 もう一つの影付きの枠が、canvas タグで確保された領域である。 .. literalinclude:: ../program/chap03/sample03_01/index.html :caption: sample03_01/index.html :language: html :lines: 16 :lineno-start: 16 3.2 scriptタグ -------------- 行6で script.js を参照している。 .. literalinclude:: ../program/chap03/sample03_01/index.html :caption: sample03_01/index.html :language: html :lines: 6 :lineno-start: 6 このように script タグでパスを指定し、 JavaScript のプログラムが入ったファイルを読み込む。 一般的には以下のように、 JavaScript のプログラムが格納されたファイルのパスを指定する。 :: それ以外に以下のようにして直接 JavaScript の命令を記述することもできる。 :: これらの 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を読んでいこう。 .. literalinclude:: ../program/chap03/sample03_01/script.js :caption: sample03_01/script.js :language: JavaScript :lines: 8-14 :lineno-start: 8 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); とすると、オレンジ色の正方形が描画される。 .. _xx_2dcontext: 3.3.2 2Dコンテキストの取得 ^^^^^^^^^^^^^^^^^^^^^^^^^^ 次に先ほど説明をあと回しにした、 行1~行6 を簡単に説明する。 ここでは 2D コンテキストを取得する。2D とは 2次元座標(2-dimensional coordinate)、 つまり x,y 座標による平面的な描画を意味する。 ここで取得した 2D コンテキストを c1 に設定し、 この c1 を介して 2D の描画を行うことができる。 .. literalinclude:: ../program/chap03/sample03_01/script.js :caption: sample03_01/script.js :language: JavaScript :lines: 1-2 :lineno-start: 1 ここでは関数 draw_canvas を定義している。 :ref:`onload` すなわち\ここではbody要素の読み込みが終わると、 この draw_canvas 関数が呼び出される。 .. literalinclude:: ../program/chap03/sample03_01/script.js :caption: sample03_01/script.js :language: JavaScript :lines: 3-6 :lineno-start: 3 getElementById は HTML ドキュメント中の id canvas_tag_1 によって canvas タグを参照し、 そのidを取得する。 getContext は canvas タグ から 2Dコンテキストを作成し、 結果を c1 に代入する。 この後は c1 に対するメソッドを使い 2D 描画を行うことができる。 これらの処理は、2D 描画を行う場合常に必要になる。 以後のサンプルプログラムではこの部分の処理は同じプログラムを用いている。 変数 c1 にはつねに同じようにして 2D コンテキストを準備する。 3.4 より複雑な描画 ------------------ より複雑な図形や描画を行ってみよう。 .. literalinclude:: ../program/chap03/sample03_04/script.js :caption: sample03_04/script.js :language: JavaScript :linenos: このプログラムはこのような図形を描画する。 .. image:: ../images/chap03/sample03_04.png 赤い塗りつぶされた正方形は先のプログラムと同じなので説明を省略する。 .. literalinclude:: ../program/chap03/sample03_04/script.js :caption: sample03_04/script.js :language: JavaScript :lines: 8-13 :lineno-start: 8 次に水色で正方形の輪郭のみが描画される。 .. literalinclude:: ../program/chap03/sample03_04/script.js :caption: sample03_04/script.js :language: JavaScript :lines: 15-21 :lineno-start: 15 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 では黄色の半円形を描画している。 .. literalinclude:: ../program/chap03/sample03_04/script.js :caption: sample03_04/script.js :language: JavaScript :lines: 23-28 :lineno-start: 23 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を実行しすると、 以下のような画面を表示する。 .. image:: ../images/chap03/sample03_05.png このような画面を手作業で作るのは難しいが、 これから学ぶ for ステートメントをつかえば、プログラムはたった23行だ。 プログラムを見てみよう。 .. literalinclude:: ../program/chap03/sample03_05/script.js :caption: sample03_05/script.js :language: JavaScript :linenos: 3.5.1 for ステートメント ^^^^^^^^^^^^^^^^^^^^^^^^ for ステートメントを見てみよう。 .. literalinclude:: ../program/chap03/sample03_05/script.js :caption: sample03_05/script.js :language: JavaScript :lines: 7-12 :lineno-start: 7 変数 i, j を定義し、for 分で i は 0~15, jも 0~15 となるように二重ループを作る。 .. literalinclude:: ../program/chap03/sample03_05/script.js :caption: sample03_05/script.js :language: JavaScript :lines: 15-16 :lineno-start: 15 行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)' という、 すでに具体的な数値による色指定の文字列を与えないと、 色指定ができないのである。 .. _xx_numtorgb: 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 を使ったプログラムを書いてみる。 .. literalinclude:: ../program/chap03/sample03_06/script.js :caption: sample03_06/script.js :language: JavaScript :linenos: このプログラムは以下のような画面を表示する。 .. image:: ../images/chap03/sample03_06.png このプログラムはfillstyleを決める部分で if が使われている。 .. literalinclude:: ../program/chap03/sample03_06/script.js :caption: sample03_06/script.js :language: JavaScript :lines: 14-20 :lineno-start: 14 for ループの中ではi と j の値により、 左から i 番目, 右から j 番目の正方形が描画される。 その際に、 :: if (i == j) { により i と j が等しいか否かを判定している。 もし等しければ紺を、異なれば水色を設定している。 したがって斜めの対角線上のマス目は i == j であるから紺色で、 それ以外は水色で描画される。 3.7 練習問題 ------------ 3.7.1 出題 ^^^^^^^^^^ それでは、少し if 文の条件式を考えてみよう。 この後いくつかの描画画面を示すが、これらはすべて if 文の条件式を変えるだけで描画できる。 条件式の指定にどのような演算子が使えるかを、まず確認しよう。:ref:`sx_cond` と :ref:`sx_operator` については Quick Reference を参照しよう。 これらの演算子を使って、以下にあげる描画画面を描画できる。 どのような条件式にすればよいかを考えてみよう。 この問題は初心者には少し難しい問題なので、あせらずに時間をかけてほしい。 自分で考えるのが難しい場合は、問題の後のヒントをみるとよい。 それでも難しいようなら、実際にヒントにある条件式を使ってプログラムを動かしてみてから、どうしてそのような動作になるか考えてみてもよい。 結果を納得できるようになるまで、いろいろ試してみよう。 Q.0 ^^^ .. image:: ../images/chap03/sample03_07-0.png Q.1 ^^^ .. image:: ../images/chap03/sample03_07-1.png Q.2 ^^^ .. image:: ../images/chap03/sample03_07-2.png Q.3 ^^^ .. image:: ../images/chap03/sample03_07-3.png Q.4 ^^^ .. image:: ../images/chap03/sample03_07-4.png Q.5 ^^^ .. image:: ../images/chap03/sample03_07-5.png Q.6 ^^^ .. image:: ../images/chap03/sample03_07-6.png Q.7 ^^^ .. image:: ../images/chap03/sample03_07-7.png Q.8 ^^^ .. image:: ../images/chap03/sample03_07-8.png Q.9 ^^^ .. image:: ../images/chap03/sample03_07-9.png 3.7.2 ヒント ^^^^^^^^^^^^ 以下の条件式のどれかである。ただし、番号は問題の番号とは対応していない。 :ref:`sx_cond` と :ref:`sx_operator` については 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) 枠のみ、塗りつぶし、塗りつぶしの色、影など、様々な描画方法を指定できる。