先日、山口のクリエイティブハントであの「まぼろし」の松田さんの素晴らしいセッションを受けました。遠い世界だと感じていた「SVGアニメーションの世界」が一気に同じマンションの住民くらいに感じる内容。「あれ?SVGって思ったよりカンタン!?」ということで実践してみました。
ちなみにセッション内容はこちら
目次
この記事の目次
前提
その前に私のスペックですが
- イラレは使える、と言って差し支えないレベル
- htmlは少しかける程度
- cssも少しかける程度
- jsは世に出回ってる分をコピペ書き換えでなんとかレベル
というギリギリwebデザイナーと名乗れるかもレベルでフロントエンド領域はさっぱりです。初心者の方での安心して見てください。
イラレを整える
まずは動かしたいイラレファイルの準備と、ファイル内の要素の整理を。
まずこれが元のイラレ。こんなにフラットかつ立体的には書けません。今回はピクスタ定額制でこちらの方のファイルを使用しました。
https://pixta.jp/illustration/56042803
外国の方ですね。ぜひダウンロードしてあげてくださいね!
レイヤーを整えます。後々の動かしたい単位でレイヤー分けするイメージです。このレイヤー構造がそのままSVGに書き出した際のhtml構造となります。
今回は「車」「ビル」「家」毎にレイヤーを分けました。
SVGに書き出す
「書き出し」で「書き出し形式」でSVGに書き出します。
「オブジェクトID」を「レイヤー名」にすることがポイント。保存してもいいんですが、今回は「コードを表示」で後にコードをまんまhtmlに貼り付けます。
html作成
適当に作ったhtmlファイルに貼り付けます。bodyの中のどっかに貼り付けます。 head内は動かすだけなら何も書かかなくても多分動きます。
body終了直前にJavaScriptファイル「TweenMax」を読み込みます。こいつが今回いい働きをしてくれます。 最後の「svgmove.js」は自分が適当に作ったjsファイルです。このファイルにこれから動きを書いていくことになります。
いちお現状のコード乗っけとくとこんな感じです。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0"> <title>SVG test</title> </head> <body> <!-- ここにイラレからのSVGコードをコピペ --> <!-- 以下js関連 --> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.3/TweenMax.min.js"></script> <script src="assets/svgmove2.js" type="text/javascript"></script> </body> </html>
ちなみにChromeのデベロッパーツールで見ると現状のhtmlでもオブジェクトが「id」単位になってます。 それぞれaltを振り分けることも可能。 最高のアクセシビリティと言ってた所以はこれね。スゲェ…
SVGファイルの中身見るとちゃんとレイヤーの構造が生きてます。「g」がなんのタグかはわかりませんが…
動きをJavaScriptで制御
初心者鬼門のjsです。こちらを参考になんとか形にしました。
セレクタで要素を取得する
よく意味わかってませんが…html内から任意のidを取得して、js内で使えるように定義するイメージでしょうか。
とりあえずこんな感じに書いてみました。
var xmlns="http://www.w3.org/2000/svg", select = function(s) { return document.querySelector(s); }, selectAll = function(s) { return document.querySelectorAll(s); }, //ここからidを定義 road = select('#road'), car15 = select('#car15'), car14 = select('#car14'), house05 = select('#house05'), house04 = select('#house04'), house03 = select('#house03'), car13 = select('#car13'), car12 = select('#car12'), car11 = select('#car11'), car10 = select('#car10'), car09 = select('#car09'), car08 = select('#car08'), car07 = select('#car07'), car06 = select('#car06'), building03 = select('#building03'), building02 = select('#building02'), car05 = select('#car05'), car04 = select('#car04'), car03 = select('#car03'), building01 = select('#building01'), car02 = select('#car02'), car01 = select('#car01'), house01 = select('#house01');
selectのあたりは多分html内の要素を取得していると思われます。大事なのはこのあたり
road = select('#road'),
「select」で取得した「id=”road”」をjs内で「road」と定義するよ、ってこと。これを動かしたい単位で、かつsvgのid名とリンクするよう定義しておく必要があります。欲張りすぎてすごい数になっちゃいましたが。
アニメーションの基準点を設定
//transformの基準点を中心にする TweenMax.set([road, car15, car14, house05, house04, house03, car13, car12, car10, car09, car08 ,car07 ,car06, building03, building02, car05, car04, car03, building01, car02 , car01, house01], { transformOrigin : '50% 50%' });
普通にやっちゃうと画面の左上が原点になるみたいです。 「car01」を現位置のx-200からx+200へ、みたいな指定のほうがおそらくラクなので、各要素の中心からアニメーションするようにしたい。
「road,car…house01」までは先程selectで定義した分を入れて、transformOriginで上記のように指定すると、原点が各要素の中心となります。
タイムラインを設定する
タイムラインってなんぞ?という人も多いかと。ここからはハマリポイント。
「タイムライン設定→そのタイムラインの中身の要素 」の順で作ります
???って感じだと思いますが今回はこうしました
- タイムライン1…道路が現れてビルと家がニョキニョキ生えてくるライン
- タイムライン2…道路の上を車が走るタイムライン
それぞれアニメーションの設定が違う(車のタイムラインは無限ループさせたい、など)から分けています。まずはタイムライン1の大枠から作ってみましょう。
タイムライン1の作成
//タイムライン1を作成する var timeline1 = new TimelineMax({repeat:0, delay:1, yoyo:false, paused:false});
細かい定義は後で説明しますが、タイムラインそのものに「repeat…リピート回数」などの設定をします。次に、その中身の要素を設定します。
めっちゃ量多く見えますが大したことはありません。
//道路が出現し家とビルが生えるアニメーション timeline1.from(road, 0.5, { scale: 0, ease: Power1.easeOut }), timeline1.fromTo(house01, 0.5, {scaleY: '0', transformOrigin: '50% 100%'}, {scaleY: '1', ease: Bounce.easeOut}, '0.5'), timeline1.fromTo(house02, 0.5, {scaleY: '0', transformOrigin: '50% 100%'}, {scaleY: '1', ease: Bounce.easeOut}, '0.75'), timeline1.fromTo(house03, 0.5, {scaleY: '0', transformOrigin: '50% 100%'}, {scaleY: '1', ease: Bounce.easeOut}, '1'), timeline1.fromTo(house04, 0.5, {scaleY: '0', transformOrigin: '50% 100%'}, {scaleY: '1', ease: Bounce.easeOut}, '1.25'), timeline1.fromTo(house05, 0.5, {scaleY: '0', transformOrigin: '50% 100%'}, {scaleY: '1', ease: Bounce.easeOut}, '1.5'), timeline1.fromTo(building01, 1, {scaleY: '0', transformOrigin: '50% 100%'}, {scaleY: '1', ease: Bounce.easeOut}, '2.5'), timeline1.fromTo(building02, 1, {scaleY: '0', transformOrigin: '50% 100%'}, {scaleY: '1', ease: Bounce.easeOut}, '3'), timeline1.fromTo(building03, 1, {scaleY: '0', transformOrigin: '50% 100%'}, {scaleY: '1', ease: Bounce.easeOut}, '3.5');
簡単に言うと、
timeline1.from(road, 0.5, { scale: 0, ease: Power1.easeOut }),
「road」が0.5秒かけて0%の大きさから元の大きさに戻るアニメーション
timeline1.fromTo(house01, 0.5, {scaleY: '0', transformOrigin: '50% 100%'}, {scaleY: '1', ease: Bounce.easeOut}, '0.5'),
「house01」が0.5秒かけて縦0%から縦100%へ0.5秒遅れで変化する
「fromeTo」はbefore、afterの状態を示すことでアニメーションさせる
これを家とビルの数ひたすら並べただけです。
この時点でも
ヌルヌル動きますね!(車残っちゃってますが…)
タイムライン2(車)の作成
次に車ラインの作成です。要領は先程と同じです。
//タイムライン2(車用)を作成する var timeline2 = new TimelineMax({repeat:-1, delay:3, yoyo:false, paused:false}); //車が走るアニメーション timeline2.fromTo(car01, 5, {x: '-400',y: -250}, {x: '800',y:450}, '2'), timeline2.fromTo(car02, 5, {x: '800',y: 450}, {x: '-400',y:-200}, '3'), timeline2.fromTo(car10, 5, {x: '-700',y: -400}, {x: '600',y:325}, '4'), timeline2.fromTo(car13, 5, {x: '-400',y: -200}, {x: '1100',y:575}, '6'), timeline2.fromTo(car08, 5, {x: '-1000',y: -550}, {x: '300',y:175}, '8'), timeline2.fromTo(car12, 5, {x: '800',y: 425}, {x: '-400',y:-225}, '5'), timeline2.fromTo(car11, 5, {x: '600',y: 330}, {x: '-700',y:-350}, '7'), timeline2.fromTo(car09, 5, {x: '500',y: 250}, {x: '-800',y:-400}, '9'), timeline2.fromTo(car04, 5, {x: '-1300',y: 800}, {x: '700',y:-400}, '1'), timeline2.fromTo(car03, 9, {x: '600',y: -335}, {x: '-2100',y:1185}, '1'), timeline2.fromTo(car05, 9, {x: '600',y: -335}, {x: '-2100',y:1185}, '5'), timeline2.fromTo(car07, 5, {x: '-1400',y: 800}, {x: '700',y:-400}, '1'), timeline2.fromTo(car06, 9, {x: '1150',y: -635}, {x: '-1550',y:885}, '2'), timeline2.fromTo(car14, 9, {x: '600',y: -335}, {x: '-2100',y:1185}, '4'), timeline2.fromTo(car15, 9, {x: '600',y: -335}, {x: '-2100',y:1185}, '7');
先ほどと同じく「fromTo」で前後の状態を指定しますが、今回は大きさではなく前後座標を指定、時間を微妙ずらしてして動かしています。
これが結構大変でした…やってみたら分かるけどはじめのイラレの作り方工夫が必要でしたね。
最終的なアニメーションはこのような形に。ヌルヌル動いて感動です。
最終コードはピクスタの分をそのまま埋め込むわけには行かなかったのでSVGは作り直しました。下手くそでイラストの作者に申し訳ない…そしてサイズが変わったので動きが変ですがそのうち調整します。