実戦で使える本格的なSVGアニメーションを作る
SVGのアニメーションって綺麗だし、カッコいいですよね。
海外のサイトだったり、dribbbleではよく見るのですが、日本のサイトでは凝ったアニメーションはあまり見かけません。
解説記事も偏っていて、モーフィングだったり、ラインアートの記事はたくさんあるのですが、オブジェクトが重なっていくつもシャシャシャッと動くようなアニメーションを解説している記事となると極端に少なくなります。
というわけで、そんなアニメーションを勉強がてら作ってみました!
まずは全体のイメージを作る
今回は試しに、スマホ上にカード形式のタイトル・ディスクリプションが並ぶようなものを作ってみようと思います。 以下のような感じです。
Illustratorを使って作成しました。 Illustratorでなくても、パスが書けるようなツールで、なおかつsvg形式で書き出せるものであれば何でも良いと思います。(sketchとかでもいけるのかな、おそらく)
SVG形式で書き出してみる
Illustratorであれば「ファイル」 > 「別名で保存」からSVG形式で保存ができます。
保存したファイルをSubline Text等のエディタで開いてみると、XML形式のSVGを見ることができます。
先ほどの画像をエディタで開いて、ちょっと整形すると次のようになります。
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 541 446" style="enable-background:new 0 0 541 446;" xml:space="preserve"> <g class="smartphone"> <g class="background"> <path class="bodyShadow" fill="#ccc" d="M180,348.8v-331c0-6.6,5.5-10.5,12-8l179,70c5.7,2.6,12,9.4,12,16v331c0,6.6-7.5,14.5-16,12l-175-73 C186.1,362.9,180,357.8,180,348.8z"/> <path class="headerShadow" fill="#555" d="M175,36V19.8c0-6.6,10.5-12.5,17-10l179,70c5.7,2.6,12,9.4,12,16v19.7l-5,3.8"/> <path class="body" fill="#eee" d="M175,352V21c0-6.6,5.5-10.5,12-8l179,70c5.7,2.6,12,9.4,12,16v331c0,6.6-3.5,11.5-12,9l-179-70 C181.1,366.1,175,361,175,352z"/> <path class="header" fill="#666" d="M175,39.3V21c0-6.6,5.5-10.5,12-8l179,70c5.7,2.6,12,9.4,12,16v20.3"/> </g> <g class="parts" fill="#333"> <circle cx="235.7" cy="46.5" r="3"/> <path d="M302.3,76.4l-51.5-20.8c-1.3-0.5-1.9-2-1.4-3.3v0c0.5-1.3,2-1.9,3.3-1.4l51.5,20.8c1.3,0.5,1.9,2,1.4,3.3h0 C305,76.3,303.6,76.9,302.3,76.4z"/> </g> <g> <path class="card1" fill="#fff" d="M167.7,59.1c0-2.4,2-3.9,4.4-3L358.8,131c2.1,1,4.4,3.5,4.4,5.9v68.9c0,2.4-1.3,4.3-4.4,3.3l-186.6-74.8 c-2.2-1.1-4.4-3-4.4-6.3"/> <g class="text1" fill="#7cccbe"> <polygon points="183,106.5 348.5,172.1 348.5,176.8 183,111.2"/> <polygon points="183,77.2 237.5,98.8 237.5,113.5 183,91.8"/> <polygon points="183,119.5 348.5,185.1 348.5,189.8 183,124.2"/> </g> </g> <g> <path class="card2" fill="#fff" d="M167.7,147.1c0-2.4,2-3.9,4.4-3L358.8,219c2.1,1,4.4,3.5,4.4,5.9v68.9c0,2.4-1.3,4.3-4.4,3.3l-186.6-74.8 c-2.2-1.1-4.4-3-4.4-6.3"/> <g class="text2" fill="#7cccbe"> <polygon points="183,194.5 348.5,260.1 348.5,264.8 183,199.2"/> <polygon points="183,165.2 237.5,186.8 237.5,201.5 183,179.8"/> <polygon points="183,207.5 348.5,273.1 348.5,277.8 183,212.2"/> </g> </g> <g> <path class="card3" fill="#fff" d="M167.7,235.1c0-2.4,2-3.9,4.4-3L358.8,307c2.1,1,4.4,3.5,4.4,5.9v68.9c0,2.4-1.3,4.3-4.4,3.3l-186.6-74.8 c-2.2-1.1-4.4-3-4.4-6.3"/> <g class="text3" fill="#7cccbe"> <polygon points="183,282.5 348.5,348.1 348.5,352.8 183,287.2"/> <polygon points="183,253.2 237.5,274.8 237.5,289.5 183,267.8"/> <polygon points="183,295.5 348.5,361.1 348.5,365.8 183,300.2"/> </g> </g> </g> </svg>
できる限り分かりやすいようにclassを振りました。 この整形作業が最も重要なポイントだと思います。
どのパスが画像でいうどの部分なのかを把握し、分かりやすく配置し直すイメージです。
<path>は文字通りパスを表し、<g>は複数のパスをグループ化したものになります。
図で示すと以下のような構成になっています。
動きをつけてみる
いよいよ本題です。 こいつに動きをつけてみます。
最初は素のSVGだけで何とかならないものかと頑張っていたのですが、かなり辛かったので、TweenMaxというライブラリを使用しました。 TweenMaxを用いると、jQueryライクにメソッドチェーンで直感的に動きをつけることができます。
まずはセレクタで要素を取得する
JSでSVGタグで作成した各要素を取得してきます。
var xmlns="http://www.w3.org/2000/svg", select = function(s) { return document.querySelector(s); }, selectAll = function(s) { return document.querySelectorAll(s); }, smartphone = select('.smartphone'), background = select('.background'), parts = select('.parts'), card1 = select('.card1'), card2 = select('.card2'), card3 = select('.card3'), text1 = selectAll('.text1 polygon'), text2 = selectAll('.text2 polygon'), text3 = selectAll('.text3 polygon');
transformの基準点を中心にする
これから要素を拡大したり動かしたりするのですが、普通にtransformを行うと要素の左上が基準となってしまい、微妙なアニメーションが出来上がってしまいます。
なので、物にもよりますが、拡大させたい要素にはtransformOriginを設定してあげると良いでしょう。
TweenMax.set([background, card1, card2, card3, text1, text2, text3], { transformOrigin : '50% 50%' });
第一引数に要素を配列で渡して、第二引数にCSSを指定するイメージでオブジェクトを渡します。
タイムラインを作成する
実際に動かしてみます。
ここではTimelineMax
というclassを用います。
TimelineMaxのインスタンスを生成時には様々なパラメータが指定できます。
- repeat
- 何回リピートさせるか
- 例えば1を指定すると、計2回再生される
- 無限にリピートさせたい場合は-1を指定する
- delay
- 開始前に何秒遅延させるか
- yoyo
- アニメーションが最後までいったら逆回転で再生させるかどうか
- Boolean値で指定
- paused
- デフォルトはfalseとなっており、アニメーションは自動的に開始されてしまう
- trueを指定した場合は、JSで好きなタイミングで
play()
を呼べば開始できる
今回は以下のように指定しました。
var timeline1 = new TimelineMax({repeat:0, delay:1, yoyo:false, paused:false});
それ以降はメソッドチェーンでアニメーションを記述していきます。
timeline1.from(background, 0.5, { scale: 0, ease: Power1.easeOut }) .from(parts, 0.5, { opacity: 0, ease: Power1.easeOut }) .from(card1, 0.5, { scale: 0 }) .staggerFrom(text1, 0.5, { scale: 0 }, 0.1) .from(card2, 0.5, { scale: 0 }) .staggerFrom(text2, 0.5, { scale: 0 }, 0.1) .from(card3, 0.5, { scale: 0 }) .staggerFrom(text3, 0.5, { scale: 0 }, 0.1);
ここまでで各要素が順番に出現していくアニメーションを作ることができました。
各メソッドの説明
from()
- 第一引数:対象の要素
- 第二引数:何秒間でアニメーションさせるか
- 第三引数:ここで指定した状態から、SVGタグが示す元の状態までアニメーションを行う
to()
- 第一引数:対象の要素
- 第二引数:何秒間でアニメーションさせるか
- 第三引数:SVGタグが示す元の状態から、ここで指定した状態からまでアニメーションを行う
staggerFrom()
これがかなり便利。 複数要素を指定して、少しずつタイミングをズラしながらアニメーションをさせることができる。
- 第一引数:対象の要素
- 第二引数:何秒間でアニメーションさせるか
- 第三引数:ここで指定した状態から、SVGタグが示す元の状態までアニメーションを行う
- 第四引数:複数指定した要素たちの開始タイミング何秒ごと遅延させるか
staggerTo()
staggerFromの逆バージョン。こちらも便利。
- 第一引数:対象の要素
- 第二引数:何秒間でアニメーションさせるか
- 第三引数:SVGタグが示す元の状態から、ここで指定した状態からまでアニメーションを行う
- 第四引数:複数指定した要素たちの開始タイミング何秒ごと遅延させるか
Power1.easeOut
これはメソッドではないですが、一緒に説明してしまいます。
TweenMax側で用意されているeasingです。
どんなものがあるかは以下のease-visualizerを見ると分かりやすいです。(超便利!)
さらに並行してタイムラインを追加する
ついでにもうひとつタイムラインを加えてみます。
上下にふわふわと浮いているようなアニメーションをつけてみます。
var timeline2 = new TimelineMax({repeat:-1, delay:1, yoyo:true, paused:false}); timeline2.from(smartphone, 2, { y: 10, ease: Power1.easeInOut });
yoyo = true
にすることで上下に行ったり来たりさせています。
結果、こんなものができました。
最終的なコードはgistに置いておきます。
まとめ
いかがだったでしょうか。
慣れてしまえば、このレベルのアニメーションであればサクサク作ることができそうです。
TweenMaxには他にもまだまだ機能があったので、おいおい試してみたいと思います。
おまけ
せっかくなので、私が運営しているサービスであるMeyasubacoのトップページに今回の成果物を組み込んでリニューアルしてみました。
ユーザーがある一定位置までスクロールしてきたらアニメーションがスタートするようにしてあります。
こちらももし良ければチェックしてみてください。
非同期通信のエラーハンドリングのあれこれ
シングルページアプリケーションを組んでいると、大量の非同期通信が必要になる。 そして、その非同期通信ごとにSuccess/Errorが存在する。
Successだけ対応していれば楽だけど、ユーザーのためにもエラーハンドリングは必須だ。
大きく分けてエラーは以下の3つ。
- 通信に失敗した(404含む)
- リクエストパラメータが間違っている
- 権限がない
それぞれどんなエラーを出すべきか。
通信に失敗した場合
だいたいサーバ側で何か不都合が起きている場合に発生する。
「○○に失敗しました。時間をおいて再度お試しください。」
あたりが妥当か?
このパターンは基本的にはサーバ側の問題が解決しないと復活しないので、ユーザーが何度試みようと失敗するものは失敗する。
しかし、ユーザーからすると自分の入力が悪いのか、はたまたシステムがバグっているのかの判断がつかない。
よってこのパターンでは、メッセージを出すより、システムエラー用のページにリダイレクトしてあげた方が親切かもしれない。
リクエストパラメータが間違っている場合
ユーザーが入力した要素に何らかの不備がある場合に発生する。
「○○を入力してください」
「○○は半角英数で入力してください」
「不正な文字が含まれています」
などを表示させてあげれば良い。
出来る限り、入力したいどの項目のどこが間違っていたのかを詳細に提示してあげた方が親切。
その分、バリデーションチェック側の手間もかかるけど。
権限がない場合
該当のAPIを叩く権限をユーザーが持っていない場合に発生する。
権限がないのであれば、そもそもボタンを押される前に防ぐべき。
画面側で権限をチェックして、ボタンをdisabledにするか、そもそも非表示にしておけばAPIを叩かれることはない。
画面経由以外で直接叩かれてしまった場合や、デベロッパーツールでdisabledを解除してクリックされた場合ももちろん想定しなくてはならない。
この場合は基本的にユーザーに悪意があるパターンが多いので、一律権限エラーページにリダイレクトさせてしまえば問題ないと思う。
画面内にエラー文言を表示するのかリダイレクトするのか問題
できれば画面内にエラー表示してあげたほうが親切であるとは思う。
特にPOST、PUT、DELETEにおいて、何らかのエラーがあったタイミングでエラーページにリダイレクトされたらイラっとするだろう。
GFTはどうだろうか?
何らかの事由により、データが取得できなかった時、 「データの取得に失敗しました。〈もう一度読み込む〉」 と表示するのが確かに親切かもしれない。
ただおそらくこのパターンは前節でも述べた通り、サーバ側の不具合である可能性が高い。
よって〈もう一度読み込む〉リンクを押下しても再び失敗する可能性が高い。
であれば、システムエラーのページにリダイレクトさせる方が親切だろう。
つまり、CRUD操作関係なしに、最初にあげた以下の3つのエラーに応じてハンドリングするのが良さそうだ。
- 通信に失敗した → リダイレクト
- リクエストパラメータが間違っている → 画面内にエラー表示
- 権限がない → リダイレクト
結局当たり前のような結論になってしまったが、色々自分の中で整理できたので良しとする。
来月公開のES2016の機能とその先について
ES2015がだいぶ浸透してきたと思ったら、今度はES2016の公開が目前に迫っています。
- ES2015(済)
- ES2016(2016年6月)
- ES2017(2017年6月)
ECMAScriptはご存知の通り、一年ごとに策定されることになりました。
各仕様の策定具合は4つのstageで表されています。(Strowmanは省略)
- proposal
- draft
- candidate
- finished
毎年の策定時点でStage4に到達していたものがその年のECMAScriptとして入ってきます。
このあたりは@azuさんのスライドが分かりやすいです。
TC39 Process: Stage · ECMAScriptとは何か?
ES2016に含まれる機能はすでに決定しており、以下の2つとなっています。
- Exponentiation Operator
- Array.prototype.includes
また、現時点(2016/06/10)では新たに以下の2つがStage4に達しています。
これらはES2017入りが決定しています。
- Object.entries
- Object.values
それぞれどんな機能なのか
Exponentiation Operator(ES2016)
xのn乗を計算するための式です。
3 ** 3 // same as 3 * 3 * 3 // 27 let x = 3; x **= 3; // 27
Array.prototype.includes(ES2016)
配列内に特定の要素があるかどうかをチェックします。
今までのarray.indexOf(x) > -1
のようにチェックしなくてはならなかった気持ち悪さから解放されます。
['hoge', 'piyo', 'fuga'].includes('piyo') // true
Object.entries(ES2017)
列挙可能なkeyとvalueの組み合わせであるObjectを二次元配列に変換します。
Object.entries({a : 1, b : 2, c : 3}) // [['a', 1], ['b', 2], ['c', 3]]
Object.values(ES2017)
Objectからvalueを抜き出し、配列にします。
Object.values({a : 1, b : 2, c : 3}) // [1, 2, 3]
これでlodash等を使わずともサクッとvalueを抜き出すことが可能になります。
その他機能の進捗状況
下記ページから確認できます。 github.com
服のセンスとデザインセンス
ずっと気になっていることがある。
- 色の組み合わせ
- 形の組み合わせ
結論
子供が産まれてからの立ち回り方
今、自分は28歳。
- 子供が産まれるまでが人生の第一部
- 子供が産まれてから、その子供が旅立つまでが第二部
- それ以降が第三部
人生の第一部が終わろうとしている。
— シベ (@shibe97) July 27, 2015
何を取り、何を捨てるか
難しいところ。
子供が産まれる前はWeb界隈の勉強会によく行っていたが、勉強会は主に夜実施されるのでなかなか参加が難しくなった。
闇雲に参加するのでなく、行く価値がありそうなものを選ぶようになった。
突発的な飲み会だったり、スノボ旅行とかバーベキュー等の休日行われるイベント系もほとんど行かなくなった。(子供が大きくなったら一緒に参加したい)
会社での仕事
早めのアラート
まとめ
- 取捨選択
- 周りの理解を得ること
- 周りの助けを得ること
付加疑問文について考えてみた
英語を喋ろうと思った時に、最初に立ち憚る壁は、英語と日本語の語順の違いだと思う。
- Don't you〜
- Should I〜
- Do I have to〜
- 肯定文で始めた時は、否定疑問+主語を付け足す
- 否定文で始めた時は、疑問+主語を付け足す
伝えたいことを伝えるための構成力
普段、基本的にパソコンに向かってプログラミングしていることがほとんどで、文章を書くという機会が極端に減った気がする。
破綻しにくい構成
- まず何を一番伝えたいかを考える
- そのためにはどんな具体例を上げていけば良いか考える
- 話したい色々なトピックを列挙する
- それらをまとめて、何が結局伝えたいかを考える