HTML+JSでアズールレーンおえかき機能のシミュレーターを作ってみた



![エルドリッジかわいいよエルドリッジ](https://qiita-image-store.s3.amazonaws.com/0/190979/6d704a24-4993-ec46-4362-c282c11b2604.png)


初投稿です。よろしくお願いします。

そのコードはこうしたほうがいいよ、フロントエンド業界でその書き方はご法度だよ等々、
ご指摘ご助言があればコメントいただけますと幸いです。[^1]


# TL;DR

- アズレン一周年イベント・上のおえかきシミュレーターを作りました。
- GitHubPagesで公開しています。
→ https://akinosora.github.io/azudot/
- アズールレーン一周年おめでとうございます。


# 概要

普段は主にJava+フレームワークでWEBアプリを開発しているエンジニアです。
メインはJavaですが、htmlやJavaScript、cssなどについてもそれなりに触れる機会があります。

また、JavaScript/TypeScriptについてはAngularJS/Angular2でも少々触れたことがあります。

とはいえ、それらは偉大な先人の残した技術資産の流用や微修正、
フレームワークに依ったコーディングなどが主[^2]となるため、
Javaはともかく純粋なhtml/JavaScript/cssの記法や作法については、
自分はあまりにも無知であるという悩みがありました。

そのためいつか勉強を兼ねて、フレームワークを一切使わず、
html+JavaScript+cssのみで1からなにかを作ってみたいと漠然と考えていました。

さて、話は全く変わりますが、私の主な趣味はゲームです。
特に最近はアズールレーンというものにはまっております

こちらは現在ちょうど一周年イベントを開催中[^3]で、
この中にはインクを集めてドット絵を描く、という企画があるのですが、
これは現段階では塗り絵のようなもので、まだ自由に絵を描くことができません...[^4]。 

このインクで自由に絵を描けると楽しそうだなぁ...自由に描きたいなぁ...そう思ったとき、
htmlやJavaScriptで何か作ってみたいと考えていたことを思い出し、これを題材とすることにしました。

以上のような背景により、このたびアズールレーンおえかきシミュレーターなるものを作ってみた次第です[^5]。

今回は以下を心掛けました。

- html/JavaScript/cssのみ利用可とする。
- フレームワークを利用しないこと。
- バックエンドとの通信を行わず、フロントエンドのみの動作で完結させること。
- JavaScriptでのDOM操作を試してみること。
- 最初から欲張りすぎず、ちょっとした機能でまずは満足すること。


# 成果物

GitHubPagesで公開しています。
https://akinosora.github.io/azudot/

![ハートマークいっぱい](https://qiita-image-store.s3.amazonaws.com/0/190979/38b59646-1cf9-985a-b6ec-e1e0d7a4cf27.png)



## 操作方法

キャンバスを左クリックすることで描画します。
キャンバスを右クリックすることでデフォルトの背景色に戻ります。

キャンバスの下にあるパレットの各色をクリックすることで、色を取ることができます。

現在選択中の色は表示されません。申し訳ありません。
絵を完成させても、保存方法がないため画面リロード等で消えます。ご了承ください。


## ざっくりと実装内容

キャンバス/パレットのグリッドはテーブルで表現しています。
セルのそれぞれにonclickイベントを設定しており、
クリックされたセルにJavaScriptで背景色のスタイルを設定しています[^6]。

今回DOMの構築の大半をJavaScriptに任せてみました。


## コード

```html:index.html
<!DOCTYPE html>
<html>
<head>
<script src="src/script/main.js"></script>
<link rel="stylesheet" href="src/css/main.css">
<meta charset="utf-8" />
<title>おえかきシミュレーター</title>
</head>
<body>
<h1>おえかきシミュレーター</h1>
<my-canvas>
    <!-- draw with JavaScript -->
</my-canvas>
</body>
</html>
```

```css:src/css/main.css
body {
    background: #313131;
    color: #ffffff;
}
table {
    border-collapse: collapse;
    margin-bottom: 16px;
}
td {
    border: 1px solid gray;
    width: 16px;
    line-height: 16px;
    cursor: pointer;
}
```

```javascript:src/script/main.js
"use strict";

(function(){
    // 37x22
    const HEIGHT = 22;
    const WIDTH = 37;
    
    const myCanvas = document.createElement("div");
    const myPalette = document.createElement("div");

    const colorSet = {
        _: "#bdaead", // default
        A: "#ffffff", // A
        B: "#312829", // B
        C: "#ffd373", // C
        D: "#73ffad", // D
        E: "#7382ff", // E
        F: "#ff7194", // F
        G: "#ffd7c6", // G
    };
    let currentColor = colorSet.A;

    const createCanvasTbody = () => {
        const tbody = document.createElement("tbody");
        for(let i=0; i<HEIGHT; i++){
            const tr = document.createElement("tr");
            for(let j=0; j<WIDTH; j++){
                const td = document.createElement("td");
                td.innerHTML="&nbsp;";
                td.style.backgroundColor = colorSet._;
                td.onclick = (e) => {
                    e.target.style.backgroundColor = currentColor;
                };
                td.oncontextmenu = (e) => {
                    e.target.style.backgroundColor = colorSet._;
                    return false;
                };
                tr.appendChild(td);
            }
            tbody.appendChild(tr);
        }
        return tbody;
    }
    const createPaletteTbody = () => {
        const tbody = document.createElement("tbody");
        const tr = document.createElement("tr");
        for(const key in colorSet){
            if (colorSet[key] === colorSet._) continue;
            const td = document.createElement("td");
            td.innerHTML="&nbsp;";
            td.style.backgroundColor = colorSet[key];
            const getColor = (e) => (currentColor = colorSet[key]) && false;
            td.onclick = getColor;
            td.oncontextmenu = getColor;
            tr.appendChild(td);
        }
        tbody.appendChild(tr);
        return tbody;
    }

    document.addEventListener('DOMContentLoaded', function() {
        Array.from(document.getElementsByTagName("my-canvas"), elem => {
            const canvasHint = document.createElement("div");
            canvasHint.innerText = "左クリックで描画、右クリックで消しゴムだよ！";
            elem.appendChild(canvasHint);
            // draw canvas
            const canvasTable = document.createElement("table");
            canvasTable.style.width = 16 * WIDTH + "px";
            canvasTable.style["table-layout"] = "fixed";
            canvasTable.onselectstart = () => false;
            elem.appendChild(myCanvas)
                .appendChild(canvasTable)
                .appendChild(createCanvasTbody());
            // draw palette
            const paletteTable = document.createElement("table");
            paletteTable.onselectstart = () => false;
            elem.appendChild(myPalette)
                .appendChild(paletteTable)
                .appendChild(createPaletteTbody());
            const paletteHint = document.createElement("div");
            paletteHint.innerText = "パレットを右/左クリックで色選択だよ！";
            myPalette.appendChild(paletteHint);
        });
    });
    
})();
```


# 感想

プライベートでなにかを作るのは何気に初めてでしたが、試行錯誤の後になんとか動いてくれたときは嬉しかったです。

ただ、cssをほとんど使っていない、スタイルの指定がcssとjsに分散しているなど、いまいちに思う点もありました。
スタイル指定はcssのみが担当しclass毎にデザイン変更、jsではclassの付け替えのみ担当、というのもできそうでしょうか・・・

学習を兼ねてといいつつ、今回ごく一部しか使えていないので、htmlやcssの勉強を続けていきたいです。。。

また、`let`/`const`忘れにより変数がグローバル変数になり、挙動が思ったのと違うものになってプチはまりしました。

```javascript:修正前
        for(key in colorSet){ // keyはグローバル変数
            ...
            // パレット内の全てのセルがcolorSet.G("#ffd7c6")扱いになる
            // 関数実行時のkeyの値(ループの最終値)を参照してしまう
            td.onclick = (e) => currentColor = colorSet[key];
            tr.appendChild(td);
        }
```

```javascript:修正後
        for(const key in colorSet){ // keyはforスコープの変数
            ...
            td.onclick = (e) => currentColor = colorSet[key];
            tr.appendChild(td);
        }
```

`"use strict";` で厳格モードにしていませんでした...
次はLinterも入れておきましょうか...

<details>
<summary>蛇足</summary>
おえかきシミュレーター作ってたらアズレンの演習やり忘れました...
</details>


# 機能追加するとしたら

- セーブ&ロード
  - テーブル<->JSON変換からのJSON出力/JSON読込あたりか
- カラーパレット追加
  - 以前開催されたおえかきイベントの色を追加
- 現在選択中の色の表示
- 全消し機能
  - 現在の初期化処理を関数化すれば比較的容易に実装できそう？
- デザインの変更
  - もっとパレットらしい見た目に
  - もっとかわいく。cssいっぱい使いたい

# 参考サイト

[Qiita: ライブラリを使わない素のJavaScriptでDOM操作](https://qiita.com/kouh/items/dfc14d25ccb4e50afe89)

[Qiita: 【JavaScript】記述方法別の実行タイミングについて](https://qiita.com/tomcky/items/07190a3a2d0d993d1b56)

[Qiita: 要素の取得方法まとめ](https://qiita.com/amamamaou/items/25e8b4e1b41c8d3211f4)

[Qiita: Markdown記法 チートシート](https://qiita.com/Qiita/items/c686397e4a0f4f11683d)

[Git の Commit Author と Commiter を変更する](https://qiita.com/sea_mountain/items/d70216a5bc16a88ed932)
gitこそ入れていましたが、プログラミングには初めて使うPCで、ユーザ設定を忘れたままコミットしたのに後になって気づきました...
一括変更のコマンドがとても助かりました。


[^1]: ただし心は豆腐なので、恐縮ですが優しめの内容ですととてもありがたいです。。。

[^2]: 具体的には、テンプレートエンジンの構文で書かれたhtml、bootstrapにデザインを頼ったcss、申し訳程度に利用するjqueryなど。

[^3]: 一周年おめでとうございます。

[^4]: 以前にあった類似のイベントでは、お題の塗り絵がすべて終わった後は自由な絵を描くことができたので、恐らく今回もそのような流れになるものと思われます。

[^5]: とはいえこの長い前置きに見合うほど凝ったものではありません...処女作なので大目に見てくださ...\_::(´ཀ`」 ∠)::\_

[^6]: cssさんの霊圧が...消えた...?

[^7]: `key` の変化に追従しないという意味