X-HACKさん主催の「Generative Art With JavaScript」に参加してきました!

先日、#xhackさん主催のJavascriptのイベントに参加してきました!

普段私は業務でJavaをメインで使っており、Javascriptも使ったりするのですが、描画系のJavascriptはこれまで使ったことがなく、とても斬新で面白かったです!
また、初心者でもわかりやすく、しっかり理解できる内容でした。

とはいうものの、文章下手な上に一緒に勉強会に参加されていたきわっちょさんがかなりわかりやすくまとめてくださっていたので、私はあまり書くことがないかもしれませんが・・笑(こっそり覗いてしまってすみません)

イベントの詳細はGenerative Art with JavaScriptをご覧ください!

htmlでCanvasを作成する

1. 「index.html」を作成し、以下のコードを書いてみてください。

<!DOCTYPE html>
<html>
  <body>
    <canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;">
    </canvas>
  </body>
</html>

<canvas>タグで描画領域を指定します。
widthが横幅、heightが縦幅、borderが線の太さ、solidが線の色を表します。
上記HTMLを実行すると、長方形の枠が表示されたと思います。
今回の勉強会では、このコードを元にどんどん色付けをしていく形で進めていきました。

2. 「style.css」を作成して「index.html」で読み込みます。
canvasに設定されている<sytle>タグの中身もそちらに移したいと思います。

<!DOCTYPE html>
<html>
  <head>
   <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <canvas id="myCanvas" width="300" height="150">
    </canvas>
  </body>
</html>
#myCanvas {
 border: 1px solid #d3d3d3;
 width: 300px;
 height: 150px;
}

3.Javascriptでキャンバスに四角形を描いてみます。

<!DOCTYPE html>
<html>
 <head>
   <link rel="stylesheet" href="style.css">
 </head>
 <body>
   <canvas id="myCanvas" width="300" height="150">
   </canvas>
 <script>
   let c = document.getElementById("myCanvas");
   let ctx = c.getContext("2d");
   ctx.fillStyle = "#FF0000";
   ctx.fillRect(20, 20, 150, 100);
 </script>
 </body>
</html>

<script>タグ内の説明をしていきます。
1行目:HTMLから「myCanvas」というIDのエレメントを取得します。
2行目:canvasに描画する準備をします。(X-hack松田さん曰くペンのようなもの)
3行目:色を指定します。
4行目:fillRectで、描画領域を3行目で指定した色で塗りつぶします。
    fillRect(x座標, y座標, 横幅, 縦幅)

4. JSファイルを外部ファイルにします。
「main.js」というファイルを作成し、Javascriptをhtmlファイルから追い出してしまいましょう。また、ついでにキャンバスをブラウザいっぱいにしちゃいましょう!

<!DOCTYPE html>
<html>
 <head>
   <link rel="stylesheet" href="style.css">
 </head>
 <body>
   <canvas id="myCanvas" width="300" height="150">
   </canvas>
   <script src="main.js"></script>
 </body>
</html>
let canvas = document.getElementById("myCanvas");

// canvasサイズ指定
let w = window.innerWidth;
let h = window.innerHeight;
canvas.setAttribute('width', w);
canvas.setAttribute('height', h);


let ctx = canvas.getContext("2d");
// 色の指定
ctx.fillStyle = "#FF0000";
// 四角形の作成
ctx.fillRect(20, 20, 150, 100);
body {
 margin: 0px;
 position: fixed;
}

※myCanvasに対してwidth、heightをvh、vwで指定すると画像がなぜかぼけてしまうので、画面サイズを取得して設定

四角形を複数描く

現在四角形が1つだけ描画されている状態だと思いますが、これを複数描画されるようにしたいと思います。

// 図形の作成
ctx.fillStyle = "#C3C3C3";
ctx.fillRect(0, 0, 150, 100);

ctx.fillStyle = "#E3E1C3";
ctx.fillRect(20, 20, 150, 100);

ctx.fillStyle = "#1FC323";
ctx.fillRect(40, 40, 150, 100);

色をランダムに変える

色はRGBで0~255の数値で表現出来るため、ランダムで0~255の数値を取得し、それをRGBにそれぞれ設定します。

// canvas要素の指定
let ctx = canvas.getContext("2d");
ctx.fillStyle = getRandomColor();
ctx.fillRect(0, 0, 150, 100);

ctx.fillStyle = getRandomColor();
ctx.fillRect(20, 20, 150, 100);

ctx.fillStyle = getRandomColor();
ctx.fillRect(40, 40, 150, 100);

/**
* 0~255のランダムな数値を取得する
* 小数点以下は四捨五入
*/
function getRandomNumber() {
   let x = Math.random() * 255;
   return Math.round(x);
}

/**
* ランダムな色を取得する
*/
function getRandomColor() {
   let r = getRandomNumber();
   let g = getRandomNumber();
   let b = getRandomNumber();
   return `rgb(${r}, ${g}, ${b})`;
}

forを使って複数四角形を描画する

四角形に関する情報をプロパティ化し、冗長だった色の指定や描画処理を関数化しました。

let canvas = document.getElementById("myCanvas");

// サイズ指定
let w = window.innerWidth;
let h = window.innerHeight;
canvas.setAttribute('width', w);
canvas.setAttribute('height', h);

// canvas要素の指定
let ctx = canvas.getContext("2d");

// x, y, width, height, colorを持つオブジェクト
// draw関数を呼び出すと、四角形を描画する
let data = {
   x: 0,             // x座標
   y: 0,             // y座標
   width: 0,         // 横幅
   height: 0,        // 高さ
   color: "#FF0000", // 色
   setData: (_x, _y, _width, _height, _color) => {
       this.x = _x;
       this.y = _y;
       this.width = _width;
       this.height = _height;
       this.color = _color;
   },
   // 描画関数
   draw: () => {
       ctx.fillStyle = getRandomColor();
       ctx.fillRect(this.x, this.y, this.width, this.height);
   }
}

// 四角形を指定回数個作成
for (let i = 0; i < 50; i++) {
   let obj1 = new Object(data);
   obj1.setData(10 * i, 10 * i, 50, 50, "#2EF1D5");
   obj1.draw();
}

/**
* 0~255のランダムな数値を取得する
* 小数点以下は四捨五入
*/
function getRandomNumber() {
   let x = Math.random() * 255;
   return Math.round(x);
}

/**
* ランダムな色を取得する
*/
function getRandomColor() {
   let r = getRandomNumber();
   let g = getRandomNumber();
   let b = getRandomNumber();
   return `rgb(${r}, ${g}, ${b})`;
}

円の描画

それでは、上で使ったことを応用してブラウザいっぱいに円が飛び交うものを作成したいと思います!(一部ソースコード変更しちゃってすみません。。)

<!DOCTYPE html>
<html>
 <head>
   <link rel="stylesheet" href="style.css">
 </head>
 <body>
   <div class="wrapper">
     <canvas id="myCanvas">
     </canvas>
     <script src="./circle.js"></script>
   </div>
 </body>
</html>
body {
  margin: 0px;
  position: fixed;
}
const NUM = 500;
const WIDTH = window.innerWidth;
const HEIGHT = window.innerHeight;
const PI_2 = Math.PI * 2;
let speedX = new Array(NUM);
let speedY = new Array(NUM);
let locX = new Array(NUM);
let locY = new Array(NUM);
let radius = new Array(NUM);
let r = new Array(NUM);
let g = new Array(NUM);
let b = new Array(NUM);
let ctx;

function main() {
   let canvas = document.getElementById('myCanvas');
   // canvasのサイズ指定
   canvas.setAttribute('width', WIDTH);
   canvas.setAttribute('height', HEIGHT);

   if (canvas.getContext) {
       ctx = canvas.getContext('2d');
       for (let i = 0; i < NUM; i++) {
           // 速度の指定
           speedX[i] = Math.random() * 8.0 - 4.0;
           speedY[i] = Math.random() * 8.0 - 4.0;
           // 開始地点(中央)
           locX[i] = WIDTH / 2;
           locY[i] = HEIGHT / 2;
           // 半径を取得
           radius[i] = Math.random() * 45.0 + 1.0;
           // 色を取得
           r[i] = Math.floor(Math.random() * 96);
           g[i] = Math.floor(Math.random() * 96);
           b[i] = Math.floor(Math.random() * 96);
       }
       // 33ミリ秒ごとに円を描画していく
       setInterval(draw, 33);
   }
}

/**
* 描画処理
*/
function draw() {
   ctx.globalCompositeOperation = "source-over";
   // 背景色の設定
   ctx.fillStyle = "rgba(8, 8, 12, .2)";
   ctx.fillRect(0, 0, WIDTH, HEIGHT);
   ctx.globalCompositeOperation = "lighter";

   for (let i = 0; i < NUM; i++) {
       //位置を更新
       locX[i] += speedX[i];
       locY[i] += speedY[i];

       // 円が左端、もしくは右端に到達した場合は向きを反転させる
       if (locX[i] < 0 || locX[i] > WIDTH) {
           speedX[i] *= -1.0;
       }

       // 円が上端、もしくは下端に到達した場合は向きを反転させる
       if (locY[i] < 0 || locY[i] > HEIGHT) {
           speedY[i] *= -1.0;
       }

       //更新した座標で円を描く
       ctx.beginPath();
       // 円の色を指定
       ctx.fillStyle = `rgb(${r[i]}, ${g[i]}, ${b[i]})`;
       ctx.arc(locX[i], locY[i], radius[i], 0, PI_2, true);
       ctx.fill();
   }
}

main();

ブラウザいっぱいに広がっててものすごく綺麗です!!
しかもコードがすごく短い・・

最後にこれをクリックイベントで座標を取得し、そこをスタート地点になるようにしたいと思います!

const NUM = 500;
const WIDTH = window.innerWidth;
const HEIGHT = window.innerHeight;
const PI_2 = Math.PI * 2;
let speedX = new Array(NUM);
let speedY = new Array(NUM);;
let locX = new Array(NUM);
let locY = new Array(NUM);
let radius = new Array(NUM);
let r = new Array(NUM);
let g = new Array(NUM);
let b = new Array(NUM);
let ctx;

let canvas = document.getElementById('myCanvas');
// canvasのサイズ指定
canvas.setAttribute('width', WIDTH);
canvas.setAttribute('height', HEIGHT);

function main(_locX, _locY) {
   if (canvas.getContext) {
       ctx = canvas.getContext('2d');
       for (let i = 0; i < NUM; i++) {
           // 速度の指定
           speedX[i] = Math.random() * 8.0 - 4.0;
           speedY[i] = Math.random() * 8.0 - 4.0;
           // 開始地点(中央)
           locX[i] = _locX;
           locY[i] = _locY;
           // 半径を取得
           radius[i] = Math.random() * 45.0 + 1.0;
           // 色を取得
           r[i] = Math.floor(Math.random() * 96);
           g[i] = Math.floor(Math.random() * 96);
           b[i] = Math.floor(Math.random() * 96);
       }
       // 33ミリ秒ごとに円を描画していく
       setInterval(draw, 33);
   }
}

/**
* 描画処理
*/
function draw() {
   ctx.globalCompositeOperation = "source-over";
   // 背景色の設定
   ctx.fillStyle = "rgba(8, 8, 12, .2)";
   // 描画枠のサイズの設定
   ctx.fillRect(0, 0, WIDTH, HEIGHT);
   ctx.globalCompositeOperation = "lighter";

   for (let i = 0; i < NUM; i++) {
       //位置を更新
       locX[i] += speedX[i];
       locY[i] += speedY[i];

       // 円が左端、もしくは右端に到達した場合は向きを反転させる
       if (locX[i] < 0 || locX[i] > WIDTH) {
           speedX[i] *= -1.0;
       }

       // 円が上端、もしくは下端に到達した場合は向きを反転させる
       if (locY[i] < 0 || locY[i] > HEIGHT) {
           speedY[i] *= -1.0;
       }

       //更新した座標で円を描く
       ctx.beginPath();
       // 円の色を指定
       ctx.fillStyle = `rgb(${r[i]}, ${g[i]}, ${b[i]})`;
       ctx.arc(locX[i], locY[i], radius[i], 0, PI_2, true);
       ctx.fill();
   }
}

window.onload = () => {
   canvas.onclick = (event) => {
       // クリック座標を取得
       let x = event.pageX;
       let y = event.pageY;

       if (typeof ctx !== "undefined") {
           ctx.clearRect(0, 0, WIDTH, HEIGHT);
       }
       main(x, y);
   }
}

こんな感じで作成しました!
でも残念なことになぜか、クリックするたびに玉の速度が速くなってしまう問題が・・
speexX、speedY、locX、locYをクリック時に初期化してもダメでした。
誰か分かる方いたら教えていただけると助かります・・!

他にもいろいろ出来そうなので、また試してみて記事にしたいと思います!

そういえば、6月21日のイケシンさんのトークショーにも参加してきました!
イケシンさんと松田さんとのトークとても楽しかったです。
ただ人数の多さに圧倒されて話しかけれなかったのが残念。
次回開催があったらまた楽しみにしてます!
それと、7月5日のTypeScriptの勉強会にも参加します!!
もし参加される方がいらっしゃったらよろしくお願いしますー!!

この記事が気に入ったらサポートをしてみませんか?