canvasでクリスマスツリーをチカチカさせた習作

## はじめに
クリスマスだし、なんとなくそれっぽいことをしたい気持ちです。
ずっと仲良くなりたいと思っていたcanvasを使って、クリスマスツリーをチカチカさせてみようかと思います。

- [ソース](https://github.com/ken1flan/xmas_tree)
- [デモ](https://ken1flan.github.io/xmas_tree/)

![xmas_tree.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14380/0c976bca-48a0-e49b-8307-6a875ae400a2.gif)

## 詳細

### [index.html](https://github.com/ken1flan/xmas_tree/blob/main/index.html#L24-L30)
canvasを設置して、bodyのonloadに仕掛けた初期化用の関数を実行するだけです。

```html:index.html
<html>
  <head>
    <!--  省略 -->
    <script src="main.js"></script>
  </head>
  <body onload='initialize();'>
    <p>
      <canvas id='tutorial' width='320' height='320'></canvas>
    </p>
  </body>
<html>
```

### [初期化](https://github.com/ken1flan/xmas_tree/blob/main/main.js#L17-L21)
bodyのonloadで実行される部分です。
`setInterval`で一定間隔で再描画しています。

```javascript:main.js
// 省略
const interval = 500;  // 明滅間隔(ミリ秒)
// 省略
function initialize() {
  treeImage.src = 'tree.png'

  window.setInterval(draw, interval);
}
// 省略
```

### [描画](https://github.com/ken1flan/xmas_tree/blob/main/main.js#L23-L38)

一度白紙に戻してから、木の絵を書いて、明かりを描画しています。
明かりの位置は…テキトーに描いた絵だったので、機械的に決められず…座標を保持しています。
明かりは60%の確率でついているようにしています。

本当は見えないcanvasに一通り書き終わってから、見えるcanvasにコピーするなどしたほうがちらつきが抑えられるかもしれませんが、平気そうだったのでやってません。

```javascript:main.js
// 省略
const lightPositions = [  // 明かりの位置
  [128, 76], [158, 221], [150, 94], [174, 156], [177, 100],
  [203, 161], [193, 224], [116, 140], [105, 195], [225, 224],
  [157, 75], [164, 130], [155, 191], [186, 202], [208, 201],
  [94, 229], [124, 238], [162, 250], [215, 247]
];
const lightRate = 0.6;  // 明かりが光る割合
// 省略
function draw() {
  var ctx = document.getElementById('tutorial').getContext('2d');

  ctx.clearRect(0, 0, 320, 320);

  ctx.drawImage(treeImage, 0, 0, 320, 320);

  for(var i = 0; i < lightPositions.length; i++) {
    if(Math.random() > lightRate) { continue; }

    var [x, y] = lightPositions[i];
    drawLight(ctx, x, y);
  }
}
// 省略
```

### [明かりの描画](https://github.com/ken1flan/xmas_tree/blob/main/main.js#L40-L56)
渡されたコンテキストに、指定の位置を中心に、決まった色で円を描いています。
少し色気を持たせたかったので、透明度を変えて円を描いています。

ここでハマったのは`beginPath()`です。これを最初適宜挟まなかったために、透明度が利かなかったり、円と円の間に線が書かれてしまったりしてました。

```javascript
// 省略
const lightRadius = 3;                               // 明かりの半径
const aroundLightRadiusMagnification = 1.7;          // 明かりの近い周辺
const outerLightRadiusMagnification = 3.0;           // 明かりの遠い周辺
const lightColor = 'rgb(255, 255, 255)';             // 明かりの色
const aroundLightColor = 'rgba(255, 255, 255, 0.6)'; // 明かりの近い周辺の色
const outerLightColor = 'rgba(255, 255, 255, 0.2)';  // 明かりの遠い周辺の色
// 省略
function drawLight(ctx, x, y) {
  ctx.beginPath();
  ctx.fillStyle = outerLightColor;
  ctx.strokeStyle = outerLightColor;
  ctx.arc(x, y, lightRadius * outerLightRadiusMagnification, 0, 2 * Math.PI, false);
  ctx.fill();
  ctx.beginPath();
  ctx.fillStyle = aroundLightColor;
  ctx.strokeStyle = aroundLightColor;
  ctx.arc(x, y, lightRadius * aroundLightRadiusMagnification, 0, 2 * Math.PI, false);
  ctx.fill();
  ctx.beginPath();
  ctx.fillStyle = lightColor;
  ctx.strokeStyle = lightColor;
  ctx.arc(x, y, lightRadius, 0, 2 * Math.PI, false);
  ctx.fill();
}
```

## 終わりに
単純しか使わなかったですが、canvas、結構楽しいですね。
ちゃんとやれば、オブジェクトごとに描画、みたいな進化もさせられそう、などと思ったり思わなかったり…。
ゲームを作っているひとが結構いますが、たしかにわかる…。作りたくなってきました。
canvas、楽しい！

## 参考
- [MDN Canvasチュートリアル Basic animations](https://developer.mozilla.org/ja/docs/Web/Guide/HTML/Canvas_tutorial/Basic_animations)