ブログ
こんにちは、フロント担当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もやってます!