CSSの3D系の操作を使いこなせるようになりたい!〜初級編:手前や奥に動かしたい!〜


※ タイトルが「初級編」となっていますが、続編を記載する予定はありません m(_ _)m

あらすじ

メルカリの企業サイトを拝見したところ、
創作意欲をかきたてられるUIアニメーションが豊富に使われており、衝撃を受けました。
とくにサイドメニューを表示したとき、メインページが左下に避けるように動く動作に感動しました。
自分も、もっとtransformプロパティを使いこなして、
ユーザーが直感的に何が起きてるか理解できるようなUIアニメーションを作りたい!と思いました。

そこで、まっさきに思い浮かんだのは奥行きを利用したデザインでした。
(そのとき見せたい要素を手前にもってきて、そうじゃない要素を奥にもっていく、など。)
奥行きを利用したデザインを実装するためには、
これまで見てみぬフリをしていた、transform: translateZ について勉強が必要だと思いました。
なんとなく難しそうなイメージは持っていましたが、
予想通り、translateZの値を変更するだけでは要素は動きませんでした。
思い立ったが吉日!とのことで、3D操作についていっぱい調べたので、
その内容を、ここにまとめます。

将来的にtransformの魔術師になれたら嬉しいですが、
まずは、手前や奥に動かすようなアニメーションを拡大・縮小で無理やり表現しなくて済むようになるだけでも
だいぶ進歩になると思ったので、まずはそれを習得するために必要な知識だけ学習した感じになってます。

ぶっちゃけまだまだよくわかっていないので、
コメント欄でプロのご意見を拝聴して、より深く学んでいきたいと思います!💪←

余談:transformプロパティを使うメリット

要素を動かしたいなら、positionwidthheight
transitionを組み合わせればなんとかなるので、
つい先日まで、transformを利用するとどのようなメリットがあるのか知りませんでした。

調べてみたところ、transformを利用すると、
要素をアニメーションするときにGPUを使って動きを演算してくれるらしく、
PCに優しい設計になるそうです。
(通常のアニメーションはCPUを使って演算するらしい。)
参考:CSS アニメーションについて深く知る

ただし、GPUを使うアニメーションの場合、
PCがそのアニメーションのためにずっとメモリ?を確保してしまうらしく、
乱用するのは良くないらしいです。

なので、たとえば位置を動かすようなアニメーションを頻繁に切り替える要素なら transform: translate
位置を調整したいけどアニメーションしたいわけじゃないなら position を使うといいのかな、と思いました。
(違ったら教えて下さい。)

参考リンク

3D操作を学ぶのに、下記サイトにお世話になりました。
もう誤魔化さない!CSS Transform完全入門(3D編)

特に、サイト内にあるシミュレーターはとてもためになりました。
これからもよくわからないときは使っていきたいです。
CSS 3D Transform Live Demo

手前や奥に移動する! translateZ

最初に調べたのは、何はともあれ、
要素を移動するためのプロパティであるtranslateZ です。
これは、要素を手前や奥に何px移動するか、という指定をするプロパティです。

手前に移動するなら数字を増やし、
奥に移動するなら数字を減らせばいいので、
とくに難しい説明は不要ですね!

.target {
  transform: translateZ( 10px );
}

ここからが地獄のはじまりです。(にっこり)

基準となる位置を指定する! perspective

要素を手前や奥に移動するためには、
「そもそも、その要素は今どこにあるのか」を仮定する必要があるそうです。

しかも、仮定のしかたは2通りあります。(!)

文章が長くなってしまうので、
以降「手前や奥に移動させたい要素」のことは「対象要素」と呼びます。

対象要素を、どの要素の位置を基準に動かしたいかによって、
どちらのperspectiveを指定する必要があるのかが変わるみたいです。

親要素の位置を基準に動かしたい場合 perspective

対象要素を、親要素の位置を基準に動かしたい場合は
親要素に perspective: 画面からの距離;と指定します。

この指定方法の場合、対象要素に transform: translateZ(10px) と指定すると、
” 親要素が画面から(perspectiveで指定した距離)px離れていると仮定したときに、対象要素を10px動かすと、どのくらい離れるかor近づくか。”
というシミュレーションをして、スタイルを適用してくれるみたいです。

body {
  perspective: 100px;
}
.target {
  transform: translateZ( 10px );
}

対象要素の位置を基準に動かしたい場合 transform: perspective

対象要素を、その要素自身の位置を基準に動かしたい場合は
translateZを指定するとき、一緒に perspective( 画面からの距離 )も指定します。

この指定方法の場合、対象要素に translateZ(10px) と指定すると、
” その要素自身が画面から(perspectiveで指定した距離)px離れていると仮定したときに、対象要素を10pxの位置に動かすと、どのくらい離れるかor近づくか”
というシミュレーションをして、スタイルを適用してくれるみたいです。

.target {
  transform: translateZ( 10px ) perspective( 100px );
}

どちらの指定方法にも共通するポイント

距離の指定になるので、長さ指定に使える単位であれば、使うことができます。
(話が複雑になるので px しか使っていませんが、em や vw なども使える。)
ただし、相対単位を使うことはできません。
(そもそもの位置を仮定するためのプロパティなので、相対の基準となる位置が無い。)

perspectiveを10pxなどの近い距離に仮定してしまうと、
対象要素を1px動かすたびに、グングン動いてしまうので、
とりあえず思考停止で100pxとかにしとけばいいと思います。

それぞれどう違うのか

perspectiveを指定しているときのtranslateZ

perspectiveを指定しているときのtranslateZは、
基準となる要素からどのくらい離れるか」を指定します。
つまり、相対位置を指定するということですね。

違いがわかりやすいのは、「基準となる要素」の中身(子要素)が1つじゃない場合です。

対象要素の位置を動かした際、「基準となる要素」の中心を基準に位置が動くことになるので、
より立体的な動きになります。

(「中心を基準に」と言いましたが、
基準点は後述のperspective-originで変更できます。)

transform: perspectiveを指定しているときのtranslateZ

transform: perspectiveを指定しているときのtranslateZは、
画面からどのくらい離れるか」を指定します。
つまり、絶対位置を指定するということですね。

こちらは、対象要素自身を基準に移動するので、perspectiveを指定したときよりも、
シンプルな動きになります。

良く言えば、どう動くのかを直感的に予測しやすく、
悪く言えば、ただ拡大・縮小しているのと違いが分からない感じになります。

実際に比べてみた

言葉じゃよくわからないと思うので、
実際に2つのperspectiveを使って、対象要素を移動させてみました!

直感的にわかりやすくするため、すこしだけスタイルを当ててあります。
(Qiitaロゴの使用が適切でない場合は、ご連絡ください💦)

1.なにも移動していない状態


とくに変化をつけてないので、どの要素も同じ距離・同じ大きさに見えています。

2.対象要素(上):transform: perspective(10px) translateZ(-1px);
   対象要素(下):transform: perspective(10px) translateZ(1px);


それぞれの要素が画面から10pxの位置にあると仮定した状態で
画面から1px離す・近づけるような動きになっています。
そのため、画面から見ると、単純に拡大・縮小するのとあまり変わらない状態になります。

3.基準となる要素:perspective: 10px;
   対象要素(上):transform: translateZ(-1px);
   対象要素(下):transform: translateZ(1px);


3つの要素が格納されている枠(div)が画面から10pxの位置にあると仮定した状態で
画面から1px離す・近づけるような動きになっています。
そのため、画面から見ると、
近づけるほど視界の外に飛び出すように動き、
遠ざけるほど中心奥に収束していくような動きになっています。


静止画では理解しづらいと思うので、GIF動画も用意しました。



感想:
対象要素にperspectiveを指定する方法は、動きがわかりやすいです。
単純に、その要素自身の位置を基準に、移動してくれているのがわかると思います。

「基準となる要素」にperspectiveを指定する方法では、
遠近法をかなり理解していないと、動き方を瞬時に予測するのは難しいと思いました。
そのぶん、よりアクロバティックな動きを表現できる感じがします。

基点の指定に注意! transform-originperspective-origin

transform-originperspective-originは、
初期値のままであれば気にしなくていいと思います。
万が一、値が変更されていた場合は、挙動がとってもややこしくなりますので、
必要ない場合、初期値の center center; で上書きしましょう。

対象要素を動かす「点」を指定する transform-origin

対象要素を動かすときに、どの点(=位置)を画面に近づけるのかを指定するプロパティです。
対象要素の中心を基準に画面に近づける場合は center center; を指定します。

対象要素の左上の点(=位置)を基準に画面に近づける場合は left top; を指定します。

.target {
  transform: translateZ( 10px );
  transform-origin: left top;
}

筆者の学習時点では、どういうときに変更すべきなのか予測しきれなかったので、
必要だった経験がある人は、コメ欄で教えてくださいm(_ _)m

画面の位置を指定する perspective-origin

perspectiveを指定した親要素(=基準となる要素)が、
画面から見てどのような位置関係にあるのかを指定するプロパティです。

perspective: center top; と指定すると、
perspectiveを指定した「基準となる要素」は、
画面から見下ろした状態で、今のように見えている、という設定にされます。
(つまり、近づけば近づくほど視界(=画面)の下の方に移動します。)

perspective: left center; と指定すると、
perspective を指定した「基準となる要素」は、
画面が左を向いてる状態で、今のように見えている、という設定にされます。
(つまり、近づけば近づくほど視界(=画面)の右の方に移動します。)

body {
  perspective: 100px;
  perspective-origin: left center;
}

transform-originと比べたら、
必要になる場面はあるんだろうなあ、という感じがしますが、
やはり複雑すぎるので、基本的には center center; でいいと思います。

燃え尽きたぜ… 真っ白にな……

はは…は…。
複雑すぎるぞ、3次元!

transform-originperspective-originを使って
要素を左側の奥や右側の奥などに移動させるとなると、
次は transform: scale も合わせて変更しないと、不自然な見た目になります。

しかし、初級編の目標は、要素を手前や奥に動かすことだったので、
ひとまずここで学習を切り上げます。
余裕ができたら、より深いtransformの世界へ知識を深めていきたいと思います。
アデュー!