実践！Animate CCを使ったWebサイト演出

# はじめに

こんにちは。フロントエンドエンジニアの @butchi_y です。

高校生時代にFlashでアニメーションを作り始め、
大学生時代にActionScriptで音声の研究をし、
社会人はFLASHerとして入社しました。

Webでアニメーション演出する際、CSSやJavaScriptのコードを書く場合もしばしばですが、
AnimateでCanvas書き出しができるようになった今、
複雑なアニメーションを行うにあたってこの技術を使わない手はありません。

今回のAdvent Calendarの趣旨からすると、Flash技術を懐かしむ感じかもしれませんが、まだまだ現役で使えるツールとして利用していきましょう！


# 今回やること

Flashアプリケーションのようなものをサイト上で実現するのではなく、あくまでも部品として
Animate CCで書き出したHTMLを基にWebサイトにアニメーションを組み込みます。
Canvas要素にイベントを送るとアニメーションがスタートし、
アニメーション終了時にCanvas要素からイベントが発火する、
という流れを実装します。


# 成果物

このようなアニメーションを動かします。

![kanpai.gif](https://qiita-image-store.s3.amazonaws.com/0/6117/9b50bf8f-3cb3-57cf-0afc-d3e65433a16c.gif)

デモサイトはこちらになります。

https://butchi.github.io/animate-sample/

リポジトリに全部入ってるので、 [コミットの差分](https://github.com/butchi/animate-sample/commits/master) を見ながらだと手順が理解しやすいかと思います。

https://github.com/butchi/animate-sample


# 前提条件

HTMLはPug、JavaScriptはES2015で組みます。今回はCSSは使いません。
組みやすさのため、こちらの [starter kit](https://github.com/kayac/kayac-html5-starter) を利用します。


# 手順

先ほどのデモに示したアニメーションを実装します。

Flaファイルは [こちら](https://github.com/butchi/animate-sample/blob/master/src/fla/kanpai.fla) 。

## アニメーションの作成

アニメーション自体の制作手順は省きますが、
構造としては、

- メインのタイムラインは1フレームのみで、シンボル名「mc」と名付けたムービークリップ（名前はなんでもいい）を配置
- mcの中にアニメーションを実装
- mcの最終フレームに `stop();` のスクリプトを記述

今回メインのタイムラインにベタでアニメーションを配置しなかったのは、イベントを送ってアニメーションを開始する処理を書きやすくするためです。

![image](https://qiita-image-store.s3.amazonaws.com/0/6117/fb9c5813-d795-c51b-ebd9-13029dfc7741.png)

メインのタイムラインでベタにアニメーションを作ってしまった場合は、フレームを全選択して「フレームをコピー」後にmc内のタイムラインに「フレームをペースト」をするとよいでしょう。


## パブリッシュ

パブリッシュは何も難しいことがありません。
用途によって都度詳細設定することになるかもしれませんが、
書き出し先さえわかっていれば大丈夫です。

[差分](https://github.com/butchi/animate-sample/commit/5ecde8f171f078a9b5f03cb65a409e6ecc05b549)


## クラスの定義

1アニメにつき1クラスを作ります。

土台としてはこんな感じです。

```js:module/AnimKanpai.js
export default class AnimKanpai {
  constructor (opts = {}) {
  }
};
```

[差分](https://github.com/butchi/animate-sample/commit/0f01f6d0b38e9559a11050885490875f3d692d6e)


## HTMLからの移植

スクリプトはHTMLにベタ書きされたものと、JSファイルに記述されたものに分かれています。
まずはHTMLにベタ書きされたスクリプトを移植します。

```js
    var canvas, stage, exportRoot;
    function init() {
      canvas = document.getElementById("canvas");
      images = images||{};
      ss = ss||{};
      var loader = new createjs.LoadQueue(false);
      loader.addEventListener("fileload", handleFileLoad);
      loader.addEventListener("complete", handleComplete);
      loader.loadManifest(lib.properties.manifest);
    }
    function handleFileLoad(evt) {  
      if (evt.item.type == "image") { images[evt.item.id] = evt.result; } 
    }
    function handleComplete(evt) {
      //This function is always called, irrespective of the content. You can use the variable "stage" after it is created in token create_stage.
      var queue = evt.target;
      var ssMetadata = lib.ssMetadata;
      for(i=0; i<ssMetadata.length; i++) {
        ss[ssMetadata[i].name] = new createjs.SpriteSheet( {"images": [queue.getResult(ssMetadata[i].name)], "frames": ssMetadata[i].frames} )
      }
      exportRoot = new lib.kanpai();
      stage = new createjs.Stage(canvas);
      stage.addChild(exportRoot); 
      //Registers the "tick" event listener.
      createjs.Ticker.setFPS(lib.properties.fps);
      createjs.Ticker.addEventListener("tick", stage);      
      //Code to support hidpi screens and responsive scaling.
      (function(isResp, respDim, isScale, scaleType) {    
        var lastW, lastH, lastS=1;    
        window.addEventListener('resize', resizeCanvas);    
        resizeCanvas();   
        function resizeCanvas() {     
          var w = lib.properties.width, h = lib.properties.height;      
          var iw = window.innerWidth, ih=window.innerHeight;      
          var pRatio = window.devicePixelRatio, xRatio=iw/w, yRatio=ih/h, sRatio=1;     
          if(isResp) {                
            if((respDim=='width'&&lastW==iw) || (respDim=='height'&&lastH==ih)) {                    
              sRatio = lastS;                
            }       
            else if(!isScale) {         
              if(iw<w || ih<h)            
                sRatio = Math.min(xRatio, yRatio);        
            }       
            else if(scaleType==1) {         
              sRatio = Math.min(xRatio, yRatio);        
            }       
            else if(scaleType==2) {         
              sRatio = Math.max(xRatio, yRatio);        
            }     
          }     
          canvas.width = w*pRatio*sRatio;     
          canvas.height = h*pRatio*sRatio;
          canvas.style.width = w*sRatio+'px';     
          canvas.style.height = h*sRatio+'px';
          stage.scaleX = pRatio*sRatio;     
          stage.scaleY = pRatio*sRatio;     
          lastW = iw; lastH = ih; lastS = sRatio;   
        }
      })(false,'both',false,1); 
    }
```

[差分](https://github.com/butchi/animate-sample/commit/7d12e06925aa0f303fe633c6fe282a238e2b1bb0)


## HTMLから移植したコードの調整

大まかな変更点は以下です。

- `canvas` と `stage` はconstとして定義
- `init`, `handleFileLoad`, `handleComplete` をメソッド化
- jQueryを使う前提で、 `canvas` 要素のjQueryオブジェクトを `$elm` として定義
- コンストラクタから `init` を呼ぶ （個人的な趣向でいったん `initialize` メソッドを介してます）
- `fileload`, `complete` のイベントハンドラを先ほどメソッド化したものに変更
- `exportRoot` は `this` の下に置く

[差分](https://github.com/butchi/animate-sample/commit/6809b618d47f80f4b8898658a1249fd0877306c7)


## JSファイルからの移植

`kanpai.js` から、 `AnimKanpai.js` の末尾へコードを移植します。コードの中身は省略します。

[差分](https://github.com/butchi/animate-sample/commit/fbd6f25b6a9a925e0eae75193d0d615efc897697)


## JSファイルから移植したコードの調整

- グローバルの `createjs` 変数を `window.createjs` に変更
- for文で直接定義されている `i` を `let` で宣言

こちらの修正点は少ないです。
ただ、何故かfor文でいきなり `i` が出てくるので、そこを直さないといけません。

```js
for(let i=0; i<ssMetadata.length; i++) {
  ...
}
```

[差分1](https://github.com/butchi/animate-sample/commit/58d1334124b7f5ba7707dace232234f2446067ee)
[差分2](https://github.com/butchi/animate-sample/commit/eee47b077f304eaddefdfef4af1e5ee7f9971bcb)


## スプライト画像を移植

スプライトはデフォルトでは `kanpai_atlas_.png` のようなファイル名になってます。
ファイル名は変えても変えなくてもいいので、好きな場所に配置しましょう。

[差分](https://github.com/butchi/animate-sample/commit/17c93cfebf4bde3469fee656d4bccb5ecbda000d)


## スプライト画像の指定先修正

先ほどのスプライト画像の移植でファイルのパスが変わった場合、 `manifest` の画像指定を直します。

```js
   manifest: [
    {src:"img/sprite-kanpai.png", id:"kanpai_atlas_"}
```

`id` はそのままでいいです。


## メインのスクリプト準備

`CreateJS` を読み込むと同時に、Canvas要素にクラスを指定し、HTML上に配置します。
複数のアニメーションを配置した際にID被りしないように、 `id="canvas"` の部分には注意しましょう。

```index.pug
  .kanpai.
    <canvas class="anim-kanpai" width="375" height="375" style="display: block; background-color:rgba(255, 255, 255, 1.00)"></canvas>

prepend JavascriptBlock
  script(src="https://code.createjs.com/createjs-2015.11.26.min.js")
```

これで一旦アニメーションが動くはずです。

```js:script.js
import $ from 'jquery';
import AnimKanpai from './module/AnimKanpai';

let animKanpai = new AnimKanpai();
```

[差分](https://github.com/butchi/animate-sample/commit/63eedc4e21a9665c822751998524ffa9770b9943)


## アニメーション開始イベントの設定

アニメーション側で、Canvas要素に `start-anim` というイベントが発火した時にアニメーションを開始するようにします。

```js:AnimKanpai.js
let _this = this;
$(canvas).on('start-anim', (evt) => {
  _this.timeline.addTween(cjs.Tween.get(_this.instance).wait(1));
});
```

メインのスクリプトでイベントを発火します。

```js:script.js
setTimeout(() => {
  animKanpai.$elm.trigger('start-anim');
}, 2000);
```

[差分](https://github.com/butchi/animate-sample/commit/d5ccf13a78d318589d68b40150b2a474c195cf0c)


## アニメーション終了イベントの設定

アニメーション側で、アニメーションが終了した時にCanvas要素に `end-anim` というイベントが発火します。 `stop()` を仕込んだところに発火の命令を記述します。

```js:AnimKanpai.js
// timeline functions:
this.frame_19 = function() {
  $(canvas).trigger('end-anim');
  stop();
}
```

アニメーション終了のイベントハンドラで任意のコードを実行します。

```js:script.js
animKanpai.$elm.on('end-anim', (evt) => {
  console.log('end anim.');
});
```

[差分](https://github.com/butchi/animate-sample/commit/77e0107b16a8898dba06c4867bd66e8dfc1d4f9f)


## 呼び出し元スクリプト

呼び出し側のスクリプトがするのはこれだけです。
ここでは `setTimeout` で2000ミリ秒待った後にイベントを発火していますが、ここも任意です。

```js:script.js
import $ from 'jquery';
import AnimKanpai from './module/AnimKanpai';

let animKanpai = new AnimKanpai();

animKanpai.$elm.on('end-anim', (evt) => {
  console.log('end anim.');
});

setTimeout(() => {
  animKanpai.$elm.trigger('start-anim');
}, 2000);
```

これでひと通りのアニメーションが完成しました！


# 最後に

今回は割と単純なアニメーションだったので、もっと複雑なことをしようとすると手を加えるところは増えてくると思います。
特にスクリプトと組み合わせたりすると可能性が広がるんじゃないかと思うので、個人的にも幅広い表現を模索していこうと思います。