CreateJSの画像をHTML input fileからリアルタイムに更新したい

## はじめに
現在、新宿某社に数か月常駐してますフリーランスです。
座席管理システムを新規設計してと言われ、
Tableを使うのも嫌だし、CSSでごにょごにょも違うなと。
あそうだ、Canvasがある。なんだっけあのライブラリ、CreateJSだ！
ということで、数年ぶりに、業務で使用。
調べたんだけど、調べ方が悪いのか、3時間も苦戦してしまったので、記録用として投稿しておきます。

結果だけ見たいひとは　[本番](#本番)　からどうぞ
## やろうと思ったこと
CreateJSでPreLoadJSを使用して、既に表示済みのBitmapオブジェクトを、`<input type="file">`で選択した画像で更新したい

## 条件
* 画像は、Canvasのサイズ全体に表示する。
* サーバーに1度ファイルをアップロードするのはなし。クライアントサイドで完結すること。
* 高解像度ディスプレイ対応になっていること。
* 変更するのは背景なので、変更しても一番後ろに来ること。

### 使用したもの、環境
* CreateJS
    * EselJS 1.0.0
    * PreloadJS 1.0.0
* JQuery 3.3.1
* Chrome 79.0.3945.130（Official Build） （64 ビット）

今回使用する技術のfileAPIは古いブラウザでは動かないらしいので注意

## 未対応のソース
```html:index.html
<canvas id="stage" width="1000" height="600"><canvas>
<script src="./script.js"></script>
<input type="file" name="upimg">
```
```js:script.js
// CreateJsのオブジェクト
var stage; // stage
var imgs; // CreateJS用のImgElementたち
var bg_img;
$(function (){
    // CreateJS開始
    stage = new createjs.Stage("stage");
    item = new createjs.Container();

    // 高解像度対応
    if (window.devicePixelRatio) {
        const canvas = document.getElementById('seat_map');
        canvas.width *= devicePixelRatio;
        canvas.height *= devicePixelRatio;
        canvas.style.width = String(canvas.width / devicePixelRatio) + "px";
        canvas.style.height = String(canvas.height / devicePixelRatio) + "px";
        stage.scaleX = stage.scaleY = window.devicePixelRatio;
    }else{
        // あとあと無いといちいち分岐しなきゃいけないので、ない場合は1倍に設定
        window.devicePixelRatio = 1;
    }

    // 画像をロードする
    var create_js_img_manifest = [
        {"id":"backuround", "src" : "backgroud_img.png"}
        {"id":"item1", "src" : "item1.png"},
        {"id":"item2", "src" : "item2.png"},
        {"id":"item3", "src" : "item3.png"}
    ];
    var queue = new createjs.LoadQueue();
    queue.addEventListener("fileload", fileload);
    queue.addEventListener("complete", complete);
    queue.loadManifest(create_js_img_manifest,true);
    function fileload(event){
        if (event.item.type == "image"){
            imgs[event.item.id] = event.result;
        }
    }
    function complete (event) {
        event.target.removeEventListener("fileload",fileload);
        event.target.removeEventListener("complete",complete);
        render();
    }
}
// 画像を表示
function render(){
    var img = new createjs.Bitmap(imgs['map_img']);
    // スケール（指定した横幅100%の画像サイズに縮小または拡大）
    img.scaleX = canvas_width / img.getBounds().width;
    img.scaleY = img.scaleX;

    // x,yの位置に画像の中央が表示されるように設定
    img.x = canvas_width / 2;
    img.y = canvas_height / 2;
    img.regX = img.getBounds().width / 2;
    img.regY = img.getBounds().height / 2;
    bg_img = img;
    stage.addChild(bg_img);
    stage.addChild(seats);
    
     /////////////////////
    // itemの描画は省略 //
   /////////////////////

    // 描画の更新
    stage.update();
}
```
### 参考資料
すでにいろいろコピペ状態なので、参考資料を一旦書いておきます。
[《CreateJSとか》canvas要素の高解像度デバイスへの対応のしかた。 / Qiita](https://qiita.com/uto-usui/items/5ee0634d5107f3da86e8)
[PreloadJSを使ってらくらく非同期読み込み / Qiita](https://qiita.com/nakajmg/items/65575d54cbed013ff2a5)

## 本番
では、実装したファイルたちです。

```html:index.html(変更なし)
<canvas id="stage" width="1000" height="600"><canvas>
<script src="./script.js"></script>
<input type="file" name="upimg">
```
```js:script.js
// CreateJsのオブジェクト
var stage; // stage
var imgs; // CreateJS用のImgElementたち
var bg_img;
$(function (){
    // CreateJS開始
    stage = new createjs.Stage("stage");
    item = new createjs.Container();

    // 高解像度対応
    if (window.devicePixelRatio) {
        const canvas = document.getElementById('seat_map');
        canvas.width *= devicePixelRatio;
        canvas.height *= devicePixelRatio;
        canvas.style.width = String(canvas.width / devicePixelRatio) + "px";
        canvas.style.height = String(canvas.height / devicePixelRatio) + "px";
        stage.scaleX = stage.scaleY = window.devicePixelRatio;
    }else{
        // あとあと無いといちいち分岐しなきゃいけないので、ない場合は1倍に設定
        window.devicePixelRatio = 1;
    }

    // 画像をロードする
    var create_js_img_manifest = [
        {"id":"backuround", "src" : "backgroud_img.png"}
        {"id":"item1", "src" : "item1.png"},
        {"id":"item2", "src" : "item2.png"},
        {"id":"item3", "src" : "item3.png"}
    ];
    var queue = new createjs.LoadQueue();
    queue.addEventListener("fileload", fileload);
    queue.addEventListener("complete", complete);
    queue.loadManifest(create_js_img_manifest,true);
    function fileload(event){
        if (event.item.type == "image"){
            imgs[event.item.id] = event.result;
        }
    }
    function complete (event) {
        event.target.removeEventListener("fileload",fileload);
        event.target.removeEventListener("complete",complete);
        render();
    }


     // 追加部分 //
     $('input[name=upimg]').on('click',function(){
        event.preventDefault();
        var reader = new FileReader();
        reader.onload = function (event) {
            // DataURLとしてのロード完了
            imgs.backuround.src = event.target.result;
            imgs.backuround.onload = function() {
                // BitmapObjectとしてのロード完了
                // 画像のサイズが変わっちゃったので、一式書き直す。
                bg_img.scaleX = canvas_width / bg_img.getBounds().width;
                bg_img.scaleY = bg_img.scaleX;

                // x,yの位置に画像の中央が表示されるように設定
                bg_img.x = canvas_width / 2;
                bg_img.y = canvas_height / 2;
                bg_img.regX = bg_img.getBounds().width / 2;
                bg_img.regY = bg_img.getBounds().height / 2;
                stage.update();
            };
        }
        reader.readAsDataURL(event.target.files[0]);
    });
    // 追加部分ここまで //


}
// 画像を表示
function render(){
    var img = new createjs.Bitmap(imgs['map_img']);
    // スケール（指定した横幅100%の画像サイズに縮小または拡大）
    img.scaleX = canvas_width / img.getBounds().width;
    img.scaleY = img.scaleX;

    // x,yの位置に画像の中央が表示されるように設定
    img.x = canvas_width / 2;
    img.y = canvas_height / 2;
    img.regX = img.getBounds().width / 2;
    img.regY = img.getBounds().height / 2;
    bg_img = img;
    stage.addChild(bg_img);
    stage.addChild(seats);
    
     /////////////////////
    // itemの描画は省略 //
   /////////////////////

    // 描画の更新
    stage.update();
}
```

### 参考資料
[【JavaScript】 input[type=”file”]で選択した画像をimg要素で表示する / SOFTELメモ Developer's blog
](https://www.softel.co.jp/blogs/tech/archives/5676)
その他、技術的な部分はfileAPIとかBlobとかdataURLとかを調べてください。

### で、何をそんなに悩んだのという話
`imgs.backuround.src`にdataURLを入れたときに、
`imgs.backuround.onload`のイベントがちゃんと発火すると思っていなかったのが原因。
`reader.onload = function (event) {}`内で、srcに代入して、`stage.update`してたら、
fileAPIの変換は終わってるけど、CreateJSの読み込みは終わってなかったということでした。

### 余談
今回散々調べて分かったことは、CreateJSはAdobeのAnimeCCで採用されているので、
細かい話は違うけれど、CerateJSで調べて出てこなかったら、AnimeCCの方も調べると、答えが見つかったりとか。する。かも。
Canvasを使いたがる人って、複雑なオブジェクトを作りたがるのでしょう。
サンプルを公開してくれるのはいいんだけど、余計な機能が多かったり、1部分だけ使うのが難儀なものが多かったような気がします。

### 最後に
サンプルとして投稿しているコードは実際に使ったものを抽象化しているものです。動作は保証しませんが、問題があれば、教えていただければ修正するかもしれません。

itemsの画像たちは、ユーザーがインタラクティブに操作できるものとして実装しています。
話題と関係のない部分を大量に書き込むことになるので、省略しました。


この投稿のコード権利を主張しませんので、適当にコピペして使って大丈夫です。

質問とか、適当こいてんじゃないよみたいなことがあれば、コメントいただければ。