LogoMark.png

JavaScript/Canvas の変更点


#author("2020-02-02T17:39:34+09:00;2019-12-25T13:19:54+09:00","default:inoue.ko","inoue.ko")
*JavaScript|Canvas
~
//RIGHT:
//[[WebDesign/JavaScript]]
//#clear

HTML + CSS + JavaScriptによるページのサンプルソースを紹介しています。
HTML5で実装された<CANVAS>を利用しています。
~

***事例1 縦横に矩形を並べて描きます。 [[→DEMO>https://design.kyusan-u.ac.jp/SampleSite/JS_Canvas/]]

CANVASを用いたインタラクティブなグラフィックスのページを作るための、基本的なサンプルです。ウインドウが読み込まれた時点で、まず画面のサイズを計測して、CANVASが画面中央に位置するように計算しています。
~

''index.html'' | ページの文書構造

 <!DOCTYPE html>
 <html lang="ja">
     <head>
         <title>Basic Sample</title>
         <script type="text/javascript" src="sample.js"></script>
         <link rel="stylesheet" type="text/css" href="sample.css">
     </head>
 
     <body>
         <canvas id="myCanvas" width="480" height="480"></canvas>
         <input type="button"  id="myButton" value="draw">
     </body>
 </html>

~

''sample.css'' | ページのビジュアル

 #myCanvas {
     position:absolute;
 }
 
 #myButton {
     position:absolute;
 }
~

''sample.js'' | ページのふるまい

 window.onload = function(){
     var mh = window.innerHeight/2 - 10;
     var mw = window.innerWidth/2;
     document.getElementById("myCanvas").style.top = String( (window.innerHeight - 480)/2 ) +'px';
     document.getElementById("myCanvas").style.left = String( (window.innerWidth - 480)/2 ) +'px';
     document.getElementById("myCanvas").style.width = String(480)+'px';
     document.getElementById("myCanvas").style.height = String(480)+'px';
     document.getElementById("myCanvas").style.background = "silver";
     document.getElementById("myButton").style.top = String( window.innerHeight/2 + 280 ) +'px';
     document.getElementById("myButton").style.left = String( window.innerWidth/2 - 20 ) +'px';
     document.getElementById("myButton").addEventListener("click", Draw, false);
 }
 
 function Draw(){
     var cnvs = document.getElementById("myCanvas");
     var dc = cnvs.getContext("2d");
     var x,y;
     for( y=0; y<480; y+=40 ){
         for( x=0; x<480; x+=40 ){
              dc.fillStyle = "#"+ Math.floor(Math.random() * 0xFFFFFF).toString(16);
              dc.fillRect( x+8, y+8, 24, 24 );		
         }
     }
 }

~
~

***事例2 Canvas上でのアニメーション [[→DEMO>https://design.kyusan-u.ac.jp/SampleSite/JS_Animate/]]

アニメーションの基本は「古い絵を消して、新しい場所に描く」を目に止まらない速さで繰り返す・・・ということです。
//[[ダブルバッファリング>Google:HTML5 canvas ダブルバッファリング]]という技法を使えば、さらに画面のチラツキがなくなるようですが、ここでは基本を理解する目的で、シンプルな記述をしています。

''index.html''
 <!DOCTYPE html>
 <html lang="ja">
         <head>
                 <meta charset="UTF-8">
                 <link rel="stylesheet" type="text/css"  href="style.css">
                 <script type="text/javascript" src="sample.js"></script>
                 <title>Sample Page</title>
         </head>
         <body>
                 <h1>Sample Graphics</h1>
                 <canvas id="myCanvas" width="640" height="480" ></canvas>
                 <p>JavaScriptによるCANVASへの描画</p>
         </body>
 </html>
~

''style.css''
 body {
     background-color: silver;
     color: white;
     text-align:center;
 }
 h1{
     font-size: 24pt;
 }
 canvas{
     background-color:black;
 }
~


''sample.js''
 var cnvs,dc;
 var X=Y=200;
 var vx=vy=10.0;
 
 window.onload = function(){
     cnvs = document.getElementById("myCanvas");
     dc = cnvs.getContext("2d");
     Draw();
 }
 
 function Draw(){
 
     requestAnimationFrame( Draw );
 
     dc.fillStyle = "black";
     dc.fillRect(0, 0, 640, 480); 
 
     X += vx;
     Y += vy;
 
     if(X < 0 || X > 640){
          vx *= -1;
     }
     if(Y < 0 || Y > 480){
          vy *= -1;
     }
 
     dc.beginPath();
     dc.arc(X, Y, 10, 0, 2 * Math.PI, false);
     dc.fillStyle = 'red';
     dc.fill();
 }

ボールが画面内で反射しながら動きます。
Draw()全体で1フレーム分、これを毎秒約60回(60fps)実行します。
黒でcanvas全体を消して、座標()を更新して、円を描く・・
という極めて単純な作業の繰り返しです。

Draw関数の中に書かれた以下の記述が、Draw自体を繰り返し実行させます。
 requestAnimationFrame( Draw );

動かす=描画する座標を更新していく ということで、
これは、以下の記述で実現しています。
 X += vx; (Y方向も同様)  
これは X = X+vx; と同じ意味で、
現在のX座標にvxを足した値を、新たな座標Xに設定します。
例えば、vxが1だったとすると、この式を実行する度に、Xは1ずつ増える・・・
ということになります。

壁面(境界)での方向転換は、以下の式で実現しています。
 vx *= -1; (Y方向も同様)
これは vx = vx*(-1); と同じ意味で、現在が+ならーへ、現在がーなら+へと
反転します。

''補足''
アニメーションをストップさせるには、cancelAnimationFrame()。
例えば以下のような書き方をすれば、100フレーム目でストップします。
 count = count+1;
 if(count<100){
     requestAnimationFrame( Draw );
 }else{
     cancelAnimationFrame( Draw );
 }
~
~

***事例3 マウスイベント [[→DEMO>https://design.kyusan-u.ac.jp/SampleSite/JS_MouseEvent]]

HTMLとCSSは、事例2と同じなので省略します。
JavaScriptの部分だけ、以下のものに差し替えてみて下さい。
canvas上でクリックする都度、画面上にランダムな色と大きさの
ボールが追加されます。

''sample.js''
 var cnvs,dc;
 var N = 0;
 var R = new Array(1000);
 var C = new Array(1000);
 var X = new Array(1000);
 var Y = new Array(1000);
 var vx= new Array(1000);
 var vy= new Array(1000);
 
 window.onload = function(){
     cnvs = document.getElementById("myCanvas");
     dc = cnvs.getContext("2d");
     cnvs.addEventListener("click", AddObject, false);
     Draw();
 }
 
 function AddObject(event){
 
     if( N >= 999 ) return;
  
     var rect = event.target.getBoundingClientRect();
  
     X[N] = event.clientX - rect.left;
     Y[N] = event.clientY - rect.top;
     vx[N] = Math.floor ( Math.random()*15 - 7 );
     vy[N] = Math.floor ( Math.random()*15 - 7 );
  	
     R[N] = Math.floor ( Math.random()*15 + 5 );
 
     var r = Math.floor ( Math.random()*256 );
     var g = Math.floor ( Math.random()*256 );
     var b = Math.floor ( Math.random()*256 );
     C[N] = 'rgb('+ r + ',' + g + ','+ b +')';
 
  N++;
 }
 
 function Draw(){
 
     requestAnimationFrame( Draw );
 
     dc.fillStyle = "black";
     dc.fillRect(0, 0, 640, 480); 
     
     for(i=0; i<N; i++){
  
         X[i] += vx[i];
         Y[i] += vy[i];
     
         if(X[i] < 0 || X[i] > 640){
             vx[i] *= -1;
         }
 
         if(Y[i] < 0 || Y[i] > 480){
             vy[i] *= -1;
         }
     
         dc.beginPath();
         dc.arc(X[i], Y[i], R[i], 0, 2 * Math.PI, false);
         dc.fillStyle = C[i];
         dc.fill();
     }
 }
ここでは、複数のボールを制御するために、配列変数を使っています。
 var X = new Array(1000);
と書くことで、X[0] 〜 X[999]までの配列が確保されます。

X[i] : i 番目のボールのX座標
C[i] : i 番目のボールの色(r,g,bの3つの値で定義されています)
R[i] : i 番目のボールの大きさ(半径)  ・・・といった感じです。

window.onload時点で、canvas上でのクリックイベントに対して、
AddObjectという関数を実行するように指示しています。
以下のような記述です。クリックイベントに対する「御用聞き」という意味で
イベント・リスナーと呼ばれます。
 cnvs.addEventListener("click", AddObject, false);
~
~

***事例4 キーボードイベント [[→DEMO>https://design.kyusan-u.ac.jp/SampleSite/JS_KeyEvent]]

HTMLとCSSは事例2のままでOKですが、HTMLに記載されたキャンバスのサイズは縦長に変更する方がいいでしょう。 
 <canvas id="myCanvas" width="480" height="640" ></canvas>
JavaScriptの部分だけ、以下のものに差し替えてみて下さい。
Enterキーでボールが出ます。左右カーソルでラケットが動きます。
キーボードでラケットが動きにくい場合は、マウスで([[DEMO2>https://design.kyusan-u.ac.jp/SampleSite/JS_KeyEvent2]])

''sample.js''
 var cnvs,dc;
 var N = 0;
 var BX = new Array(1000);
 var BY = new Array(1000);
 var vx= new Array(1000);
 var vy= new Array(1000);
 var R = new Array(1000);
 var C = new Array(1000);
 var RX,RY,RWIDTH,RHEIGHT;
 var message = "Game Over";
 var audioHit = new Audio("hit.wav");
 var audioMiss = new Audio("miss.wav");
 
 window.onload = function(){
     cnvs = document.getElementById("myCanvas");
     dc = cnvs.getContext("2d");
     RWIDTH = cnvs.width/5;
     RHEIGHT = cnvs.height/32;
     RX = cnvs.width/2 - RWIDTH/2;
     RY = cnvs.height - 80;
     window.addEventListener('keydown',keyEventFunc,true);
     Draw();
 } 
 
 function keyEventFunc(event){
     if( event.keyCode == 13 ) AddObject();
     if( event.keyCode == 37 ) MoveRacket(-20);
     if( event.keyCode == 39 ) MoveRacket(+20);
 }
 
 function AddObject(){
     if( N >= 999 ) return;
     message = ""; // Game Start 
     BX[N] = cnvs.width/2; 
     BY[N] = cnvs.height/5;
     vx[N] = Math.floor ( Math.random()*13 - 6 );
     vy[N] = Math.floor ( Math.random()*5 + 5 ) * (-1);
     R[N] = cnvs.width/64;
     var r = Math.floor ( Math.random()*256 );
     var g = Math.floor ( Math.random()*256 );
     var b = Math.floor ( Math.random()*256 );
     C[N] = 'rgb('+ r + ',' + g + ','+ b +')';
     N++;
 }
 
 function MoveRacket( v ){
     if( v < 0 && 0 < RX ) RX += v ;
     if( 0 < v && RX+RWIDTH < cnvs.width ) RX += v;
 }
 
 function Draw(){
 
     requestAnimationFrame( Draw );
 
     dc.fillStyle = "black";
     dc.fillRect(0, 0, cnvs.width, cnvs.height ); 
     
     for(i=0; i<N; i++){
          BX[i] += vx[i];
          BY[i] += vy[i];
          if(BX[i] < 0 || BX[i] > cnvs.width){ //wall
               vx[i] *= -1;
          }
          if(BY[i] < 0){ //roof
               vy[i] *= -1;
          }
          if( BY[i] > cnvs.height ) { //miss
               N = 0;
               message = "Game Over";
               audioMiss.play();
               break;
          }    
          if( ( RX < BX[i]  &&  BX[i] < RX+RWIDTH  )
               && ( RY < BY[i]+R[i]  &&  BY[i]-R[i] < RY ) ) { // racket
               vy[i] *= -1;
               audioHit.play();
          }
          dc.beginPath();
          dc.arc(BX[i], BY[i], R[i], 0, 2 * Math.PI, false);
          dc.fillStyle = C[i];
          dc.fill();
     }
    
     dc.fillStyle = "white";
     dc.fillRect(RX, RY, RWIDTH, RHEIGHT);
 
     dc.fillStyle = "white";
     dc.font = "20pt Arial";
     dc.fillText( message, cnvs.width/2 - 70, cnvs.height*0.45); 
 
 }
~
~