ブログ
PixiJSを使ってカメラのシャッターアニメーションを作ってみた
      こんにちは、フロント担当nakaiです。
さっそくですが、みなさんはJavaScriptでアニメーションを作る時、どんなライブラリを使っていますか?
最近のアニメーション作成といえば、GSAPやVelocity.js、 Three.js、Anime.js、CreateJSなど、様々なライブラリやツールが世に出ています。普段使っているものもあるのですが、中にはチラッとデモを見たり、ちょっとしたDOM操作のアニメーションを作っただけのものもあります。
今回、ある案件でカメラのシャッターアニメーションをWebで表現したいという提案があり、どうせならcanvasを使った方がアニメーションの幅が広がると思い、ライブラリPixiJS(GSAPも)を使って実装してみました。残念ながら今回作ったものは採用されなかったのですが、PixiJSの知見を増やすことができたと自負しております。
目次
もくじ
PIXI.jsとは
Create beautiful digital content with the fastest, most flexible 2D WebGL renderer.
https://www.pixijs.com/
と公式さんが言っているように、速くて柔軟で無料で使用できる2Dのアニメーションが作れるJavaScriptライブラリです。WebGLの知識がなくてもハードウェアアクセラレーションで作れますよ的な。
チュートリアルやサンプルも充実してます。(ドキュメントは英語なので読みにくさは否めないですけど。)
チュートリアル:https://github.com/kittykatattack/learningPixi
デモ:https://pixijs.io/examples/#/demos-basic/container.js
なんといっても、ある程度のJavaScriptの知識があればWebGLやcanvasを使ったアニメーションが作れる!という手軽さなんだと思います。
何を作ったの?
カメラのシャッターとは言いつつ、黒背景+白ボーダーの三角形の矩形を8つ並べて、良きタイミングで動かすだけというシンプルなものなのです。

動きはこちらで確認できます。

実際のデモはこちらで。
一見、ただのスライドショーですが、写真の切り替え時にカメラのシャッターアニメーションを挟んでいます。
どうやって作ったの?
この章からはどうやって作ったのかを紹介していきます。
nodeモジュールを使用して構築するのでマシンにnodeインストールなど下準備が必要。バージョンはnode v12.11.1。
コードはこちらで取得できるので、ライブラリとか確認できますし、下記説明しているコード・ファイルのディレクトリなどの参照にしてください。
Github : Camera Shutter へのリンク
ではまずはpixi.jsとアニメーション部分で使用するgaspをインストールします。
$ npm i pixi.js $ npm i gasp
画像などの素材情報の用意
今回は6枚画像を用意します。
ディレクトリ:/src/images/slideshow/
それらをjs内で使用するためjsonで指定します。
this.data = [
  {
    "name": "slide01",
    "url": "./assets/images/slideshow/img01.jpg"
  },
  {
    "name": "slide02",
    "url": "./assets/images/slideshow/img02.jpg"
  },
  {
   ...Pixi.jsアプリケーション初期設定
ベースとなるPIXIのApplicationを作成します。 今回は全画面なのでclientWidthとclientHeightを設定。
this.app = new PIXI.Application({
    width: document.body.clientWidth, height: document.documentElement.clientHeight, backgroundColor: 0x000000,
    autoStart: true,
    autoResize: true,
    antialias:true,
    resolution: devicePixelRatio,
});スライドショーを入れるコンテナを用意。
this.container = new PIXI.Container();今回のメイン。シャッターオブジェクトの作成。 シャッタークラスの詳細は後述。
this.shutter = new Shutter();画像データのロード
上記のdataで用意した画像用jsonを読み込む。Pixi.jsの画像ロードでは、nameとurlというキー名で登録してloadすれば勝手に読み込んでくれるようです。 
読み込んだ画像(テクスチャ)をimageSpriteBoxesと言う配列に入れていくのと同時に、ビルドを実行します。
this.app.loader.add(this.data);
...
Object.keys(resources).forEach( (key) => {
    this.imageSpriteBoxes.push(new PIXI.Sprite(resources[key].texture))
});
this.build();buildメソッドの中には、スライドショーを入れるコンテナ、各スライドのポジションの設定や比率を求めたり、初期設定に必要なものが実行されます。
スライドの開始
その後、firstExpression()およびslideshowStart()でスタートします。スライドショー自体はsetIntervalで回してる簡単なものですが、 毎回新たなスライドが開始するときに、シャッタークラスのshutterStart()を実行する時に関数を引数として渡しています。この関数はシャッターが完全に閉じた後に実行されるもので、次のスライドを準備し、シャッターが開く動作の開始と同時にズームアウトするという関数です。
this.setIntervalID = setInterval( () => {
    this.shutter.shutterStart({
      onComplete : () => {
        this.slideWrapBoxes[this.cNum].alpha = 0;
        if(this.cNum >= this.maxNum) {
          this.cNum = 0;
        } else {
          this.cNum++;
        };
        this.slideWrapBoxes[this.cNum].alpha = 1;
        this.zooming();
      }
    });
}, this.ssInterval);シャッターの作成
シャッターはShutterクラスとして作成しました。 矩形の作成やポジション、アニメーションなどシャッター自身を管理します。
シャッターの断片の三角形を8つ作ります。 長辺を5000に設定。あまりにも大きいディスプレイだとキレてしまいますが、そこはご愛嬌にしています。
this.shutterlist = this.anglelist.map(obj =>{
    let tri = this.createTriangle(obj);
    this.container.addChild(tri);
    return tri;
});断片の位置の設定
それぞれの配置をセッティング。ここは改善の余地あり。わざわざgsap.timelineでせずともよいのですが…
setPos() {
    let tl = gsap.timeline();
    tl.set(this.shutterlist[0], { x: `-=${this.xpos}`})
        .set(this.shutterlist[4], { x: `+=${this.xpos}`})
        .set(this.shutterlist[2], { y: `-=${this.xpos}`})
        .set(this.shutterlist[6], { y: `+=${this.xpos}`})
        .set(this.shutterlist[1], { x: `-=${this.ypos}`, y: `-=${this.ypos}`})
        .set(this.shutterlist[3], { x: `+=${this.ypos}`, y: `-=${this.ypos}`})
        .set(this.shutterlist[5], { x: `+=${this.ypos}`, y: `+=${this.ypos}`})
        .set(this.shutterlist[7], { x: `-=${this.ypos}`, y: `+=${this.ypos}`})
}シャッターの開閉動作
カメラシャッターの閉じる動作と開く動作はgsapのyoyoで再現しています。
shutterStart(obj) {
    if(typeof this.startTL !== 'undefined') {
      this.startTL.kill();
    }
    let xpos = window.innerWidth / 2;
    let ypos = window.innerHeight / 2;
    this.startTL = gsap.timeline({
      repeat: 1,
      repeatDelay: 0.3,
      yoyo: true,
      yoyoEase:Power2.easeOut,
      defaults: {
        duration: this.durationIn,
        ease: Power2.easeInOut
      },
      onRepeat: () => {
        obj.onComplete();
      },
    });
    this.startTL.to(this.shutterlist, { x: xpos, y: ypos })
}
Pixi.jsのクセ
Pixi.jsに触れてみた感想としては、
- Pixi.jsで何か作る時には、Containerを生成し、その中に組み込んでいく。
 - scaleで大きさを変更する時にはpivotやpositionで中央値を考慮する必要がある。
 - デフォルトではcanvas内でスクロールできなかったり、解像度をしてしないといけなかったりする。
 - 独自APIがあるので調べる必要がある。
 
などと言った少しのクセを知る必要がありますが、なんとかやれなくもないです。
まとめ
カメラのシャッターアニメーションを通じて基本的なことだけで実装しましたが、使うほどにまだまだ奥が深いライブラリだなと思わされます。例えば、アニメーションするマスクでエフェクトを作れたり、カスタムフィルタでGLSLを実行できたり、と。
今回はPixi.jsを使いましたが、もっと他にシャッターとの相性のいいライブラリがあるのかもしれません。それを探すため、まだまだengineer lifeは続くのでしょう。
記事がいいねと思ったら\いいね/してね
Bit Beans
Xもやってます!