現在は note で発信しています。
2018年9月29日に都内で開催したCSS Nite LP58「Coder’s High 2018」のフォローアップとして、伊藤 由暁さん(まぼろし)の『現場で役立つCSSアニメーション』セッションのスライドなどを公開します。
この度はCSS Niteにご参加いただきありがとうございました! スライドの一部でテキストが重なって読めないミスがあり、大変申し訳ありませんでした…!
ウェブ制作の現場では、CSSアニメーションは不可欠な存在となりつつあります。アニメーションの追求には専門知識が必要で、それを習得しているデザイナー、エンジニアは多くはありません。にも関わらずウェブではアニメーションが「おまけ」のように扱われている側面があります。
おまけでちょっとしたアニメーションを入れたい、そういうオーダーを言ってくるクライアントが多いけど、「おまけ」で「ちょっとした」アニメーションで合意を取るのがどれだけ難しいかは、うなずいていただける方も多いでしょう。
とても幸いなことに、ウェブには素晴らしいCSSアニメーションがあふれています。それらを探し、再現し、ストックする。それだけで自分の武器が増えます。
CSSアニメーションはCSSで作られていますので、同じようにコードを書けば同じように動きます。この時点で難しさというのは極限まで下がっていると言って過言ではありません。あとは「なぜ素晴らしいと感じたのか」がわかれば、合意形成までの距離はぐっと縮まりますね。
発表の中の「ウケるアニメーションとは」でお話しした通り、全てのキモは「イージング」と「複数の動き」です。まずはイージングを工夫し、次に複数のプロパティーでアニメーションできるかどうか考えてみると良いです。
cubic-bezier.comで3次ベジェ曲線を自分で調整するデモをお見せしたかったのですが、うまく行かず申し訳ありませんでした。
cubic-bezier.comでは青いハンドルと赤いハンドルをドラッグすることで自由にイージングをカスタムすることができます。Adobe Illustratorでのベクターツールをお使いの方なら調整にはさほどハードルは感じないかと思います。
ベジェ曲線がどうなっていると動きにどう違いが出るのか、体感できる非常に良いウェブサイトなので、少し時間を作って触ってみてください。
僕が普段チェックしているのはCodePenですが、能動的に見に行くタイミングは「CodePen Spark」という無料メルマガが届いたときです。Sparkではデザインや機能が素晴らしいものやTIPS、ハック的なものが週一で紹介されており、その中でCSSアニメーションで作られたものがあります。それらをfavしておいて、あとで見返して詳しくコードを読み解き、手元にストックしています。
CodePenにも日本のユーザーはいます。アニメーションのクオリティと量共に、下記のお二方のCodePenは要チェックです。
僕のアカウトもあります! CSSアニメーションは全然作ってませんが、先日「写真エリアの中央のボタンにhoverすると、ボタンの外側の写真エリアもtransitionする」というのを作りました。ただしクライアントにはそんなにウケませんでした。
複数のアニメーションを連続で順番で再生したい場合、3ステップ以上あるアニメーションならJSで管理したほうが調整が楽です。CSSでは順番でアニメーションさせたいもののdelayが1つ変わったら後のアニメーションの全ての記述を見直さないといけないのでとても大変です。
前のアニメーションが終わったことをちゃんと検知して次のアニメーションを始めたいなどの場合はJSでの処理が必要です。その時はアニメーションは全てJSで管理するようにした方が楽です。実装時に見るべきファイルが少ない方が開発しやすさも上です。
表示パフォーマンスの面ではWebGLやcanvasを利用できるJSライブラリの方が上です。使い分けや各ライブラリの特徴はICS MEDIAの「現場で使えるアニメーション系JSライブラリまとめ(2018年版) ? TweenMax, CreateJS, WebAnimation, Velocityなど」の記事で非常にわかりやすくまとまっているので、参考になります。
デザイナーさんからアニメーションを入れたいと言われたときは、まず「具体的にこのサイトで使われているやつなどあったらリンクを教えてください」と伝えています。そこでリンクを教えていただけたらそのまま利用するだけなので最も話が早いです。その上にこちらの提案を被せてもコミュニケーションが増えるだけですので、それはせずにありがたく指示を受け入れましょう。
アニメーションは入れたいけど、相手が詳細なイメージを持っていない場合、まず「どんな系統のアニメーションなのか」をヒアリングし、それに応じてアニメーションさせるtransition-property
を決めます。
width
,height
,transform: scale()
を利用opacity
を利用color
とbackground-color
を利用top
,left
,transform: translate()
を利用transform: rotateZ()
を利用ここからはクライアントに見せられるサンプルが必要になります。
例えば透明度が変わる系では、まずはピュアに要素丸ごとのopacity
をアニメーションさせるサンプルを作り、そこからバリエーションを2つほど派生させ、それに応じた質問もあらかじめ用意しておきます。
このように、サンプルは極力少なくし、どれかをベースにしたコミュニケーションでアニメーションを調整して行くと意外とすんなり決まっていきます。
もちろん、もっと細かいバリエーションを見て決めたいと言われてしまったら、頑張って作りましょう…!
「視線の誘導」としてのアニメーションがユーザーに与える影響が大きいです。わかりやすい例としてはブラウザの高さいっぱいにメインビジュアルが置かれたコンテンツです。ページを下にスクロールできることを示すアイコンや装飾を「スクロールヒント」と言いますが、それがゆったりアニメーションしていると、メインビジュアルを邪魔しすぎず、存在に気づいてもらうことができますね。
スマホコンテンツでも、一見すればスワイプできないエリアでも、指先のイラストが左右に動くアニメーションが表示されれば、ユーザーはそれによってスワイプできることを知れます。動いていることで視線を誘導し、動きの様子でユーザー操作を補助しているわけです。
アニメーションがユーザビリティを高めるシーンは多々あります。何がどのように変化するか、その先に何があるのかを知っているのは制作サイドの人間だけですので、アニメーションを活用すれば誰にとってもわかりやすいサイトが作れます。
発表では「CSSアニメーションは結論必要です」とでっかくお伝えしましたが、ない方がいい場合もあります。
こうなってしまうとユーザービリティが下がるだけですので不要です。一番重要視したいのは、アニメーションがあることでユーザーの助けになることです。あってもなくても同じであれば、制作側の自己満足にしかなりませんので、まずはその線引で分けてみましょう。
「回っている寿司は美味そうに見える」ともお伝えしましたが、回転寿司がイマイチ美味そうに見えない気がするのは、寿司が「全て」「linearで」「ずっと動いている」からです。情報が平坦化されて、どれも大事に見えなくなってしまうわけですね。イージングが違うレーンで旬のネタが流れてきたら、美味そうに見えるでしょう。
リンクボタンやハンバーガーボタンは、スマホでは指で隠れてしまってアニメーションに気づかない場合はあります。しかし、スマホサイトをスマホだけで見るとは限りません。CSSメディアクエリでスタイルを書き分けている場合、デスクトップブラウザでも画面幅を狭めればスマホレイアウトの状態を確認できます。ブラウザを見ながら執筆などの並行作業をしているときに、画面幅を狭くしている人はそれなりにいます。そういったユーザーにはスマホレイアウトでもhoverやfocusは有効ですので、スマホサイトだからアニメーションはいらない、と切り捨ててしまうのは早計です。
発表ではボタンのCSSアニメーションしか紹介できませんでしたが、ページ内リンクのスムーススクロールやドロワーナビのスライドインでも、イージングの活用は可能です。
SPA(Single Page Application)で現在いるページがアニメーションで消えてから次のページを表示する場合なども、ただopacity :0
にするだけのアニメーションより、各要素がtransform: scaleY(0)
にもなる、テキストと画像が順番に画面外へフレームアウトして消えるなど、様々なパターンが考えられますね!
鬼の心で止めましょう。「やりすぎかな?」と思ったらほぼ確実にやりすぎています。
僕が担当してきた案件では、CSSアニメーションは下記の5種類のパターンが多いです。
これらそれぞれで派生があればサンプルとしては十分です。似たパターンをたくさん用意してもその違いを判別できるクライアントは多くはありません。派生は、複数の動きを持つものと、イージングが違うものがあれば3つ程度で良いです。
また、アニメーションさせるトリガーとしては
が主だったものです。hover以外を再現するにはJavaScriptの助けが必要になりますが、サンプルとして見せるならどれもhoverにしておいたほうが、すぐにアニメーションを発動させられるので確認がスムーズになります。
それらを見せながら「スクロールで画面に入ったときに、大きくなりつつopacityが0から1になる感じですね」などといったコミュニケーションが取れるようになれば、サンプルとして十分役割を果たせています。
もし差し支えなければ、レビューしますのでご連絡ください! コードの良し悪しではなくサンプルとしての過不足をお伝えできます!
CSSアニメーションのパフォーマンスというと、transform: translate3d(0,0,0)
のような、Null Transformと呼ばれる「おまじない」を利用してGPUレンダリングさせる手法が古くからあります。
<pre><code class="language-none">.elem {
width: 50%;
height: 50%;
background-color: tomato;
transform: translate3d(0,0,0); /* 強制的にGPUレンダリングさせる */
}
.elem:hover {
width: 200%;
height: 10%;
background-color: gold;
}</code></pre>
</div>
<p>GPUレンダリングで表示パフォーマンスが向上することは事実です。特に座標が変わる系では効果が顕著です。</p><p>僕がこれまで案件をやってきて、CSSアニメーションの表示パフォーマンスの改善が難しかったのは以下のパターンです。</p><ul><li>パララックス</li><li>画面全体で動いている</li><li>長大なアコーディオンの開閉</li><li>写真やイラストなどラスター画像を動かしている</li><li>一度に動かす数が多い</li><li>一度に動かす距離が長い</li><li>画面スクロール中に動かしている</li></ul><p>上記のパターンはNull Transformを利用してもほとんど改善できません。GPUレンダリングをしていても、その範囲が大きいとか数が多いと効果が出にくいようです。</p><p>さらに、ラスター画像を動かす場合は <code>transform: translate3d(100%,0,0)</code> で要素を画面の右へ追いやるよりも、<code>left: 100%</code>の方が表示パフォーマンスが良いケースも過去にありました。</p><p>一方、ボタンなどのマイクロインタラクションでは、Null Transformを意識しなくても表示パフォーマンスに体感で影響が出ることはほとんどありません。ですので、今回のデモでもNull Transformは使用しません。</p><p>CSSアニメーションをオーダーされた時は、必ず「表示が重くなる可能性」と「表示パフォーマンスを改善できない可能性」を説明します。それを理解した上でやりたいならば、もちろんやります。</p><h4>BEMでElementの中にも要素があるので、そのElementもBlockにしたいがどう命名すれば良いか?</h4><p>CSSアニメーションの話ではないのですが、僕はBEMが大好きなのでこれには答えざるを得ません。</p><p>ElementもBlockである場合とは、例えば、下記のようなHTMLでしょうか。</p>
<div>
<pre><code class="language-markup"><div class="p-split">
<div class="p-split__col p-code"><!-- p-split__col が p-code を兼ねている -->
<div class="p-code__header">...</div>
<div class="p-code__body">...</div>
</div>
<div class="p-split__col p-view"><!-- p-split__col が p-view を兼ねている -->
<div class="p-view__header">...</div>
<div class="p-view__body">...</div>
</div>
</div></code></pre>
</div>
<p>個人的にBEMでElementとBlockを兼業させるのはオススメしません。なぜならば、<code>p-code</code> や<code>p-view</code> Blockのスタイルが<code>p-split__col</code>に依存するCSSで構成される場合があり、別の場所で<code>p-code</code>や<code>p-view</code>だけで使ったときにスタイルが成立しなくなる可能性があるからです。依存があるのかないのかがまちまちですと、メンテナンス性は下がりますね。</p><p>ですので、「依存がない」に統一するために、ElementとBlockを兼業させず、Blockだけで常に独立しているHTML構造にするべきです。</p>
<div>
<pre><code class="language-markup"><div class="p-split">
<div class="p-split__col"><!-- p-split__col でしかない -->
<div class="p-code"><!-- p-code でしかない -->
<div class="p-code__header">...</div>
<div class="p-code__body">...</div>
</div>
</div>
<div class="p-split__col"><!-- p-split__col でしかない -->
<div class="p-view"><!-- p-view でしかない -->
<div class="p-view__header">...</div>
<div class="p-view__body">...</div>
</div>
</div>
</div></code></pre>
</div>
<p>では、ElementとBlockを兼業させないことを守った上で、<code>p-code</code> Block が <code>p-split__col</code> Elementの中にあるゆえに必要なスタイルは、どう書かれているべきでしょうか? Modifierで管理しがちですが、Modifierは使いません。理由はいくつかあります。</p><ul><li>命名が難しい</li><li>使いまわさないModifierになる</li><li>シングルクラスのModifierのSass管理しづらさ</li><li>マルチクラスのModifierのHTMLの冗長さ</li></ul><p>まず命名が難しいです。先ほどのHTMLの、<code>p-split__col</code>の中にいる<code>p-code</code>にどんなModifierをつければ妥当でしょうか。<code>--inSplit__col</code>でしょうか? <code>--blackBack</code> でしょうか? 悩む時間の無駄さが半端ないです。ただでさえElementの命名で悩んでいるというのに。</p><p>そして悩んで命名したけど <code>p-split__col</code> の中以外の場所では使わないModifierになったりしませんか?</p><p>シングルクラスBEMにしているとSassのextendを使わないとしんどいですよね。そのときもextendを使うルールも決めておかないとあとあと管理できなくなってきます。</p><p>かと言ってマルチクラスBEMでModifierつきの名前をHTMLに併記すると、これがまた冗長すぎて苛立ってきませんか。</p><p>ということで、普通に子孫セレクタにして良いです。</p><div><pre><code class="language-css">.p-split__col .p-code {margin: 30px;}</code></pre></div><p>scssファイルはBlockごとにファイル分割し、<code>_p-code.scss</code> のファイルに <code>p-split__col</code> の中にいるときのスタイルを記述しましょう。</p>
<div>
<pre><code class="language-Sass">.p-code {
.p-section__col & {
margin: 30px;
}
}
.p-code__header {...}
.p-code__body {...}</code></pre>
</div>
<p>これらの管理手法は「<a href="https://necomesi.jp/blog/tsmd/posts/152">細かすぎるけど伝わってほしい私的BEMプラクティス30(ぐらい)</a>」を参考にしています。</p><p>さて、BEMの理解でつまづくのが、「Element=Block直下の要素」という先入観です。これは明確に誤りで、ElementはElementを入れ子にすることができます。</p>
<blockquote>
<div>
<pre><code class="language-markup"><ul class="menu">
<li class="menu__item">
<a class="menu__link" href="https://">...</a>
</li>
</ul></code></pre>
</div>
<p><a href="https://en.bem.info/methodology/html/#nesting-of-elements%E3%82%88%E3%82%8A">https://en.bem.info/methodology/html/#nesting-of-elementsより</a></p>
</blockquote>
<p>ですので、「Elementの中にも要素がある」からといってBlockにしなければならないわけではなく、そのままElementを増やして良いです。その際に命名に困るというのがBEMあるあるですが、そこは割り切っていくと気持ちが軽やかになるでしょう。割り切りとは、以下のような命名を受け入れることです。</p>
<div>
<pre><code class="language-markup"><div class="p-split">
<div class="p-split__inner">
<div class="p-split__inner2"><!-- 割り切りポイント -->
<div class="p-split__col">
<div class="p-code"></div>
</div>
<div class="p-split__col">
<div class="p-view"></div>
</div>
</div>
</div>
</div></code></pre>
</div>
<p>おわかりでしょうか、 <code>p-split__inner2</code>です。 <code>__inner</code>の中に別の「インナー的なもの」が必要になったとき、こうして番号をつけてしまうことで考える時間をぐっと減らせます。無理して命名しなくてもいい、命名で困ったらブロックにしてもいい、そんなくらいに考えると良いです。もちろん別の場所で独立したBlockとなるものはちゃんとBlockにしましょう。</p><p>ElementはElementを入れ子にできると書きましたが、「Elementを繋いだセレクタを作って良い」とは言っていませんので、注意してください。Elementを繋いだセレクタとは、<code>.menu__item__link</code> とったものです。これは<a href="https://en.bem.info/methodology/faq/#why-not-create-elements-of-elements-block__elem1__elem2">BEM的にもやってはいけないとされています</a>。</p><h4>終わりに</h4><p>アンケート全体として、<a href="https://oti.github.io/lp58/#demo-3">easings.netのcubic-bezier() を見比べられるサンプル</a>にご好評をいただく声が多く、大変嬉しく思います。</p><p>ウェブサイトとして参照するだけでなく、リポジトリをフォークし、自分の客先に提案しやすいように色や大きさを自由にカスタマイズしてご利用ください。</p><p>ありがとうございました!</p>