ブログ

10周年特設サイト コーダー’sカット 〜タイムラインにのって〜

こんにちは、シマです。前回、告知編にて始まりました本企画。
記事を書きながらリファクタリングする箇所が暴かれる仕組みでお届けします。

今回は①オープニング演出(アニメーション)で採用した、TweenMax × TimelineMax を使ったタイムラインアニメーションについてお話します!(タイトルはPUNPEEのタイムマシーンにのってにのっかりました。すみません。)

TweenMax × TimelineMax <基本編>

特徴

TweenMaxは、GreenSock社製のjavascriptアニメーションライブラリです。

  • ハイパフォーマンス(jQueryの20倍)
  • 高機能
  • マルチプラットフォーム/ブラウザ対応
  • jQueryのようなセレクタ指定で直感的なアニメーションの記述が可能

といった特徴があり、組み合わせで様々なアニメーションを実現できます。公式サンプルは見ているだけでわくわくしてきます!

①準備

GSAPのホームからライブラリ一式をダウンロードし、Get Startedに沿って進めます。

  • 基本(コア機能)版:TweenLite、TimelineLite⇒軽量、シンプル
  • フル版:TweenMax、TimelineMax⇒リピートや遅延など細かい制御が可能

今回はアニメーションにリピートを使いたかったのでTweenMaxを採用しましたが、複雑なことをしなければTimelineはLiteで事足りそうな。

お役目としては、
TweenMax=「アニメーション自体のコントロール」
TimelineMax=「アニメーション(=TweenMax)の発火タイミングのコントロール」
といったところでしょうか。( ᐛ)

タイムラインに一連の動きをあらかじめ登録(宣言)しておいて、それらをいつ再生(実行)したいのかを任意のタイミングで呼び出して使っていきます。

②タイムラインの生成

タイムラインでアニメの発動タイミングを制御するため、まずはタイムラインを生成していきます。

var tl_opening = new TimelineMax({
    paused: true
});

再生タイミングを制御したい場合はpaused:trueにしておくのがポイントです。

③アニメーションの登録

このタイムラインに、TweenMaxのアニメーション(や関数)をセットしていきます。

// #オープニング
var tl_opening = new TimelineMax({
    paused: true
});
tl_opening.add(TweenMax.to('#yano', 0.6, { y: '0%', opacity: 1, ease: Circ.easeInOut }), 0.3);

上記例では、TimelineMaxの.add()メソッドを使い、「TweenMaxのアニメーション(0.6秒かけてy軸移動&opacityを1に)を再生開始0.3秒後に発火する」ようセットしています。
TweenMaxおよびTimelineMaxの詳細な記法に関してはGSAPの公式Docをご参照ください。

TweenMax × TimelineMax <応用編>

ではここからは、実践に役立つTIPSを紹介していきます!( ᐛ)

【TIPS:1】 動きの追加は、TL.add()ではなくTL.append()が便利

TimelineMaxでの動きの追加は.addが主流ですが、.add()を使って追加されたTweenは並列に実行されます。
例えば 「アニメーションA(0.6秒)終了→アニメーションB(0.5秒)終了→アニメーションC(0.8秒)」 という動きを想定した場合、.add()で構成するなら下記のやり方が考えられます。

// #パターン① 待ち時間を考慮する方式
var tl = new TimelineMax({
    paused: true
});
tl.add(TweenMax.to('#a', 0.6 {option}), 0)); //アニメーションA
tl.add(TweenMax.to('#b', 0.5 {option}), 0.6)); //アニメーションB=Aの時間待機して実行
tl.add(TweenMax.to('#c', 0.8 {option}), 1.1)); //アニメーションC=A+Bの時間待機して実行
// #パターン② 完了時に実行する方式
var tl = new TimelineMax({
    paused: true
});
tl.add(TweenMax.to('#a', 0.6 {onComplete:function({
    tl.add(TweenMax.to('#b', 0.5 {onComplete:function({ //アニメーションB=A完了時に実行
        tl.add(TweenMax.to('#c', 0.8 {option}, 0)); //アニメーションC=B完了時に実行
    })}, 0));
})}, 0));

①はTimelineの待ち時間を累積して実行していく方式、②はTweenの完了時によばれるonComplete:で実行していく方式です。正直、どちらもやりにくい&見にくい
タイムラインが長くなってくると、前後の動きへの依存関係が深まっていってソースもカオスになっていきます。

そんなときに便利なのが、.append()です。先程の.addを.appendに書きかえてみます。

// #パターン③ .append()でおしりに追加
var tl = new TimelineMax({
    paused: true
});
tl.append(TweenMax.to('#a', 0.6 {option}), 0)); //アニメーションA
tl.append(TweenMax.to('#b', 0.5 {option}), 0.6)); //アニメーションB=A完了の0.6秒後に実行
tl.append(TweenMax.to('#c', 0.8 {option}), 1.1)); //アニメーションC=B完了+1.1秒後に実行

先程の.addが並列追加だったのに対し、.append()は直列追加的な動きをしてくれます!
つまり「Aの後にBを動かしたい」という状況では.append()、「AとBを動かしたい」ときは.add()、というふうに使い分けると、煩雑な時間管理をする手間も省けて可読性があがります。

※.append()は公式にのってないので、.to()を使って書くのがよさそうです。
→続・リファクタリング編(近日公開)
[参考] https://greensock.com/position-parameter

【TIPS:2】 TweenMax.set()をタイムラインの中で使うときは・・

例えば 「アニメーションAが終わった後に、状態をリセットして、アニメーションBを実行したい」 という場合にはTweenMax.set()でリセットの役割を担うことが出来ますが、このTweenMax.set()は、記述された瞬間(=js的にはタイムラインに動きを登録していく段階)で実行されてしまいます。
これを防ぐには、TweenMax.set()のオプションに immidiateRender:false を設定すると、任意のタイミングで.set()を実行することが出来ます。

// #ジョーカー上昇
var tl_jkFromBottom = new TimelineMax({
    paused: true
});

tl_jkFromBottom.add(TweenMax.set('#joker', { top: '100%', immediateRender:false}), 0); // ポイント:immediateRenderをfalseに!
tl_jkFromBottom.append(TweenMax.to('#joker', 0.9, { top: '0', ease: Back.easeOut.config(1)}), 0);

【TIPS:3】 repeatを使ったTweenが含まれていると、TimelineのonCompleteが呼ばれないので注意

TweenMaxで動きが完了した際に呼ばれるonComplete:同様、Timelineにもタイムラインに登録したすべてのTween終了時に呼ばれるonCompete:メソッドが存在します。これは 「タイムラインAが終わったら、タイムラインB(やら関数やら)を実行」 という際にうまく活用したいオプションです。
ここで注意すべしは、 repeatをつかったTweenがある場合、onComplete:が呼ばれない ということです。

// #ワープシーン
var tl_warp = new TimelineMax({
    paused: true,
    onComplete: function(){ // Timeline完了時のコールバック
        tl_jkFromBottom.play(); //別のタイムラインを再生
    }
});

tl_warp.add(TweenMax.to('.bg__line', 0.2, {y:'+12vh', repeat:-1, yoyo:true, ease: Linear.easeNone}), 0);

// 〜略〜 //

//皆バラバラにびゅいん
tl_warp.add(TweenMax.staggerTo('.member', 0.8, {y: '-200%', opacity: 0, ease: Back.easeInOut}, 0.1), 0.6);

//背景さがる
tl_warp.append(TweenMax.to('#entrance', 1.0, {opacity: 0, ease: Power2.easeOut, onComplete:function(){
    $('#entrance').fadeOut();

    //ポイント:repeatしているTweenをkillする
    TweenMax.killTweensOf('.pscene'); 
    TweenMax.killTweensOf('.bg__line'); //killTweensOf()を使うとTweenを変数に格納しなくてもターゲットを狙える
}}), 0);

解決策として、最後のTweenのonComplete:でrepeatしているTweenを明示的に終わらせると、タイムラインのonComplete:がちゃんと呼ばれてくれました!

【※】本来は無限ループとそれ以外のアニメーションは別のタイムラインで管理したほうが良いそうです。(わざわざkllTweenOfしてonCompleteを呼び出すこともない)
→続・リファクタリング編(近日公開)

Timelineの可読性をあげていきたい(宣言)

タイムラインはどう構成することもできます。onComplete:なんてメソッドがある限り、最悪1つのタイムラインですべてのTweenを管理することも出来ると思います。がしかし!ソースコードの可読性をあげる・動きの依存関係を極力減らすためには、下記を意識するだけで、だいぶ良くなるのでは無いかと思いました。

  • タイムラインは分割!
    • Tween同士で連結ではなく、Timeline同士で連結する
    • タイムラインにはわかりやすい名前をつける
    • 長くなる場合はラベルを追加するなど、どこで何が起きるか明示
  • 入れ子は避ける!おおめにみて1段階まで

ではでは、皆さまもすてきなTween Lifeを!!( ᐛ)