CSS 3D(Transform)を実装する時の参考に

目次
  1. 基本
  2. 回転
  3. 移動と拡大縮小

基本

CSS 3D(Transform)は平面に奥行きをつけて変形させます
光源をもとにして陰影をつけることはできません

perspectiveの値は平面(z=0)とユーザー(視線)との距離です

  • 親要素に対して設定する場合
    perspective: 数値px;
  • 対象要素に設定する場合
    「transform関数のperspective(数値px)」を対象要素に設定することもできます
    *例:transform: perspective(500px);
<div class="box-parent">
  <div class="box" style="width:100px; height:100px; background:#ccc; margin: 0 auto"></div>
</div>
.box-parent{
  perspective: 200px;
}
.box{
  transform:rotateX(45deg)
 }
.box{
  transform:perspective(200px) rotateX(45deg)
 }

transform-style: preserve-3d; 
要素の子要素を3D空間に配置することを示します

perspective-originについて

親要素にtransform-style: preserve-3d;を設定すると
perspective-originでユーザーの目線を変更できます
斜めから画面を見た場合など
初期値は50% 50%(center center)

  • キーワード値
    Xではleft(0)・center(50%)・right(100%)
    Yではtop(0)・center(50%)・bottom(100%)
  • 値が1つ:x-position
  • 値が2つ:x-position y-position

transform-style: preserve-3d;で宣言した時
同じ空間の要素transform:translateZ重なりの順番を指定
*translateZは%指定はできません

 <div class="base">
   <div class="first">1</div>
   <div class="second">2</div>
   <div class="third">3</div>
 </div>
<style>
/*boxのポジションや外観のcssは省略*/
.base{
 transform-style: preserve-3d;
 perspective: 15px;
 perspective-origin: -100% 50%;/*左横方向から見るため*/
}
.first{
  transform: translateZ(1px);
}
.second{
 transform: translateZ(2px);
}
.third{
 transform: translateZ(3px);
}
</style>
1
2
3

右上部のControlsを開きNO1(奥行き)
奥行き(translateZ)と画面と視線の距離(perspective)と目線(perspective-origin)を変更して見え方を確認
*正方形を2枚重ねているので奥行き(translateZ)をつけて、目線(perspective-origin)を変更すると下になっている正方形(黒)が見えます
z軸座標がperspectiveの値より大きい要素は描画されません(ユーザーを通りこしている)

NO1(奥行き)
平面
<style>
.parent{
  width:200px; 
  height:200px; 
  margin: 0 auto;
  position: relative;
  transform-style: preserve-3d;
  perspective: 200px;
  perspective-origin:50% 50%;
}
.box{position: absolute;top:0;left:0;width:100%;height:100%;background:#222;font-size: 80px;line-height: 200px;text-align: center;}
.box1.target{
 box-sizing: border-box;
 background:white;
 border:solid 3px #ccc;
 transform: translateZ(0); 
}
</style>
 <div class="parent">
   <div class="box"></div>
   <div class="box target">平面</div>
 </div>

回転

rotateX, rotateY, rotateZ(それぞれXYZの軸に対する回転)とrotate3dがあります

rotate・rotateX・rotateY・rotateZ

rotate

ちなみにrotateZは3D空間でも2Dのrotateと同じ動きです

.box-parent{
 transform-style: preserve-3d;
 perspective: 200px;
}
.box{
  transform:rotateZ(45deg)
 }

transform-originでrotate(回転)の回転軸の位置を変更できます(scaleの原点にもなります)

transform-origin:値
*transform-originを設定しない場合はデフォルト値(50% 50%)で要素の中央を中心に回転

値は2Dの回転では要素の左上を起点(0 0)として値を設定
マイナスや%
Xではleft(0)・center(50%)・right(100%)
Yではtop(0)・center(50%)・bottom(100%)
といったキーワードもつかえます
*値が1つのときは、Y方向は「center」とみなされます

transform-origin起点

transform-originの値が3つある時
3つ目はZオフセットです
*値はpxなどのlengthデータ型で%は不可

右上部のControlsを開きNO2(回転)
回転軸と角度(rotate)と回転軸の位置(transform-origin)を変更して動きを確認
*特にoriginZの動きを確認するため実際のコードは視線をズラし(perspective-origin:-10% 50%;)後ろの正方形はtransform: translateZ(-5px);をつけています

NO2(回転)
回転
.parent{
  perspective: 500px;
}
.rotate{
 background:white;
 border:solid 3px #ccc; 
 transform-origin: center center 0;
 transform:rotateX(0) rotateY(0) rotateZ(0);
}

右上部のControlsを開きNO3(立方体)
距離(perspective)と視点(perspective-origin)を変更して見え方を確認
*translateZはあえて若干遠くしている(105px)

NO3(立方体)
0
Y(180)
Y(90)
Y(-90)
X(90)
X(-90)

立方体
6個の同じ位置に重ねた正方形を作り
正方形の中心を軸にそれぞれX軸とY軸に回転して配置
*回転の起点を設定するtransform-originのデフォルト値が中心(center center)のため設定の必要はなし

<style>
.base{
 margin: 0 auto;
 position:relative;
 width:200px; 
 height:200px; 
 transform-style: preserve-3d;
 perspective: 500px;
 perspective-origin: 50% 50%;
}
.cube{position: absolute; top:0; left:0; width:100%;height:100%;opacity:0.7;font-size: 60px;line-height: 200px; text-align: center;}
.front {
 background: rgba(0, 0, 0, 0.3);
 transform: translateZ(100px);
}
.back {
  background: green;
  color: white;
  transform: rotateY(180deg) translateZ(100px);
}
.right {
  background: yellow;
  transform: rotateY(90deg) translateZ(100px);
}
.left {
  background: pink;
  transform: rotateY(-90deg) translateZ(100px);
}
.top {
  background: orange;
  transform: rotateX(90deg) translateZ(100px);
}
.bottom {
  background: blue;
  transform: rotateX(-90deg) translateZ(100px);
}
</style>
<div class="base">
   <div class="cube front">0</div>
   <div class="cube back">Y(180)</div>
   <div class="cube right">Y(90)</div>
   <div class="cube left">Y(-90)</div>
   <div class="cube top">X(90)</div>
   <div class="cube bottom">X(-90)</div>
</div>

rotate3d(x, y, z, a)
要素を三次元空間内の固定した軸を中心に回転させる変形を定義します
x, y, zの値は0から1aは回転する角度です

*firefoxとsafariではrotate3dのアニメーションで一気に一回転させるコードは動かない😂

右上部のControlsを開きNO4(回転)
立方体の回転(rotate3d)と回転の中心点(transform-origin)を変更して見え方を確認

NO4(回転)
4
3
2
5
6
1
<style>
.grand-parent{
  perspective:500px; /*祖父母の要素にperspectiveをつける(親につけると変形したまま回転する)*/
}
.parent{
 width:100px; 
 height:100px; 
 position:relative;
 transform-style: preserve-3d;
 transform-origin: center center 0px;/*初期値*/
 animation: rotate 10s linear infinite;
}
@keyframes rotate {
 to{
  transform: rotate3d(1,0,0, -360deg);/* firefoxとsafariでは動きません */
  }
}
.cube{position: absolute;top:0;left:0;width:100%;height:100%;font-size: 30px;line-height: 100px;text-align: center;}
.front {background: silver;transform: translateZ(50px); }
.top {background: orange;transform: rotateX(90deg) translateZ(50px);}
.back {background: tomato;transform: rotateY(180deg) translateZ(50px);}
.bottom {background: skyblue;transform: rotateX(-90deg) translateZ(50px);}
.right {background: yellow;transform: rotateY(90deg) translateZ(50px);}
.left {background: pink;transform: rotateY(-90deg) translateZ(50px);}
</style>
<div class="grand-parent" style="overflow:hidden;">
 <div class="parent">
   <div class="cube front">1</div> 
   <div class="cube top">2</div>
   <div class="cube back">3</div>
   <div class="cube bottom">4</div>
   <div class="cube right">5</div> 
   <div class="cube left">6</div> 
 </div>
</div>

三次元空間で要素が回転した時は背面を見ることができます
backface-visibility:visible(初期値:背面を表示)
backface-visibility: hidden(背面を非表示)

translateZが優先
画像は円の上に位置するので、裏面になった時はbackface-visibility: hiddenがなくても円で隠れます

HELLO

<style>
.card2 {
  position: relative;
  margin: 0 auto;
  text-align:center;
  width: 150px;
  border-radius: 20px;
  padding: 2rem 50px;
  box-shadow: 0 20px 20px rgba(0, 0, 0, 0.2);
  transform-style: preserve-3d;
  perspective: 500px;
  animation: rotate 10s linear infinite
}
.card-img{
  transform: translateZ(50px);
}
.circle{
 width: 150px;
 height: 150px;
 background: linear-gradient(120deg, #e0c3fc 0%, #8ec5fc 100%);
 position: absolute;
 border-radius: 50%;
 transform: translateZ(25px);
}
.card-text{
 font-size:30px;
 font-weight: bold;
}
@keyframes rotate{
100%{ transform: rotateY(360deg); }
}
</style>
<div class="card-container">
  <div class="card">
     <img class="card-img" src="https://koro-koro.com/wp-content/uploads/2022/05/03.png">
     <div class="circle"></div>
     <p class="card-text">HELLO</p>
  </div>
</div>

カードをクリックすると回転する
同じ大きさのカードを2枚を重ねて
裏側のカードはtransform: rotateY(180deg);で回転しておく
2枚のカードはbackface-visibility: hiddenでユーザーに対して裏側を向いたときに見えなくします

<style>
.card-inner {
  cursor: pointer;
  position: relative;
  width: 200px;
  height: 200px;
  margin: 0 auto;
  text-align: center;
  font-size:30px;
  line-height:200px;
  transition: transform 0.5s;
  transform-style: preserve-3d;
}
.card-front,
.card-back {
  position: absolute;
  top:0;
  left:0;
  width: 100%;
  height: 100%;
  background: #ddd;
  border-radius: 10px;
  box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.2);
  backface-visibility: hidden;
}
.card-back{
  transform: rotateY(180deg);
}
.card-inner.rotated {
  transform: rotateY(-180deg);
}
</style>
<div class="card-inner">   
  <div class="card-front">表</div>
  <div class="card-back">裏</div> 
</div>
<script>
const card = document.querySelector('.card-inner')
card.addEventListener('click',()=>{
  card.classList.toggle('rotated');
})
</script>

移動と拡大縮小

translate3d(X,Y,Z):X,Y,Zをまとめて移動する
*Zは%指定できません

Static
Moved
<style>
.square{
  width:100px;
  height:100px;
  font-size:24px;
  text-align:center;
  line-height:100px;
  background: #ccc;
  margin:0 auto;
}
.square.moved{
  background:green;
  transform:perspective(1000px) translate3d(0px, 20px, 200px) rotateX(-55deg);
}
</style>  
<div class="square">Static</div>
<div class="square moved">Moved</div>

スターウォーズ風のアニメーション

HELLO WORD

HELLO WORD

HELLO WORD

HELLO WORD

HELLO WORD

<style>
.wrap-text{ 
  overflow:hidden;
  perspective: 500px;
  display: grid;
  place-items: center; 
  background:black;
}
.scroll-text{
  height:180px;
  color:white;
  font-size: clamp(2.5rem, 2.045rem + 2.27vw, 3.75rem);
  line-height: 0.5em;
  font-weight: bold;
  animation: scroll 10s linear infinite alternate;
}
@keyframes scroll {
 0% { transform: translate3d(0px, 100px, 200px) rotateX(45deg); }	
 100%{ transform:  translate3d(0px, -300px, -400px) rotateX(60deg); }
}
</style>
<div class="wrap-text">
  <div class="scroll-text">
    <p>HELLO WORD</p>
    <p>HELLO WORD</p>
    <p>HELLO WORD</p>
    <p>HELLO WORD</p>
    <p>HELLO WORD</p>
  </div>
</div>

scale3d(X,Y,Z)
scaleX, scaleY, scaleZの拡大縮小をまとめて操作

.square.scaled{
  background:green;
  transform: perspective(1000px) scale3d(1.5, 0.5, 2) rotateX(-55deg);
}
Static
Scaled

*SafariのCSS 3D(translateZ)とz-indexの制御順にバグがありtranslateZが優先されるようです
対策:translateZで上にきている要素の親にtransform: translateZ(0)をつける

z-indexの制御順
親要素がローカルの重ね合わせコンテキストを形成していた場合(条件は下記リンク参照)
子要素のz-indexはローカル内でのみ有効になります