GODRICH

【一行でコピペOK】CSS 3D アニメーション9選

css 3d アニメーションは、perspectiveとtransform-style: preserve-3dだけで奥行きを作るCSSの技法です。回転するキューブや円周上のカルーセル、ホバーで傾くカードなど9種類を選び、それぞれ一行でコピペOKな短いコードにまとめました。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

9つは立体感を作る3つのプロパティ――perspective(カメラとの距離)・transform-style: preserve-3d(子要素を平面に押しつぶさない)・backface-visibility(裏面を隠す)――をどう組み合わせるかで並べています。前半の3つ(01〜03)はこの3つを最も単純な形で見せ、中盤の3つ(04〜06)は複数の要素が重なる構造へ進み、後半の3つ(07〜09)は舞台全体を3Dで組み立てるところまで踏み込みます。perspectiveとtransform-styleはいずれもMDNのBaselineで「Widely available」と記されていて、backface-visibilityだけは2022年3月に対応がそろった少し新しい機能です。コードは要点だけを抜き出していて、完成した9本のファイルはzipにまとめてあります。

01キューブ回転

4面のキューブがperspectiveの中でY軸を軸に回り続け、面ごとに違う情報を順番に見せます。ロゴや短い一言を面ごとに変えて、LPヒーローの商品紹介キューブやバナーの情報切り替えに使います。

perspectivetransform-style: preserve-3dbackface-visibility
.cube {
  transform-style: preserve-3d;
  animation: spin $duration $easing infinite;
}
.cube__face {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
}
.cube__face--front { transform: translateZ($half); }
.cube__face--right { transform: rotateY(90deg) translateZ($half); }
.cube__face--back  { transform: rotateY(180deg) translateZ($half); }
.cube__face--left  { transform: rotateY(-90deg) translateZ($half); }
@keyframes spin {
  from { transform: rotateY(0deg); }
  to   { transform: rotateY(360deg); }
}

023Dカルーセル

カード5枚を円周上にrotateYで配置し、親のリングだけを回し続けてシリンダーのように回転するカルーセルを作ります。028のマーキーはtranslateXで押しますが、これは回転だけで回るので、ロゴショーケースや回転する商品タグバッジに向いています。

rotateY 배치transform-style: preserve-3d부모만 회전
.ring {
  transform-style: preserve-3d;
  animation: orbit $duration $easing infinite;
}
.ring__card {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
}
// Sassで先に角度を掛け算する — rotateY(0 * 72deg) のような無効なCSSを防ぐ
@for $i from 1 through 5 {
  .ring__card:nth-child(#{$i}) {
    transform: rotateY(#{($i - 1) * $step}) translateZ($radius);
  }
}
@keyframes orbit {
  from { transform: rotateY(0deg); }
  to   { transform: rotateY(360deg); }
}

03ホバーでチルト

カーソルを乗せると、カードが決まった角度にふっと傾きます。032の3Dチルト視差はマウス座標をJSで毎フレーム読みますが、これはJSを一行も使わず:hoverの値ひとつだけをトランジションするので、機能紹介カードやサムネイルプレビューに向いています。

perspective:hover 고정 각도JS 0줄
.frame { perspective: 600px; }
.tilt {
  transform: rotateX(0deg) rotateY(0deg);
  transition: transform $duration $easing;
}
.tilt:hover,
.tilt:focus-visible,
.tilt:active {
  transform: rotateX(10deg) rotateY(-14deg);
}
@media (hover: none) {
  .tilt:active { transform: rotateX(10deg) rotateY(-14deg); }
}

04本が開く効果

表紙が左辺を軸に-150degまで開き、内側のページを見せます。006のフリップカードはカード全体を中心軸で180deg裏返しますが、これは片方の辺だけが軸で完全には裏返らないので、オンボーディングガイドやカタログ表紙の展開に向いています。

transform-origin: leftbackface-visibility경첩 회전
.book__cover {
  transform-style: preserve-3d;
  transform-origin: left center;
  animation: open $duration $easing infinite;
}
.book__face {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
}
.book__face--back { transform: rotateY(180deg); }
@keyframes open {
  0%, 15%  { transform: rotateY(0deg); }
  55%      { transform: rotateY(-150deg); }
  85%, 100% { transform: rotateY(0deg); }
}

05フリップギャラリー

格子タイル4枚が0.15秒ずつずれて順番に裏返り、カテゴリを見せます。006はカード1枚だけですが、これは複数枚が波のように続くので、メディアギャラリーのカテゴリ格子やダッシュボードウィジェットの紹介に向いています。

backface-visibilityanimation-delay 시차grid
.tile__inner { transform-style: preserve-3d; }
.tile__face {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
}
.tile__face--back { transform: rotateY(180deg); }
.tile:nth-child(1) .tile__inner {
  animation: flip $duration $easing infinite;
}
.tile:nth-child(2) .tile__inner {
  animation: flip $duration $easing infinite;
  animation-delay: .15s;
}
@keyframes flip {
  0%, 15%  { transform: rotateY(0deg); }
  50%      { transform: rotateY(180deg); }
  85%, 100% { transform: rotateY(360deg); }
}

06カバーフロー

カード5枚が同じkeyframesを負のanimation-delayでずらして回り、中央は大きく正面を向き、両脇は小さく傾いて流れます。JSを使わずCSSだけで循環させるので、アルバムやポートフォリオのカバースライダーに向いています。

rotateY + translateZ음수 animation-delayJS 0줄
.flow__card {
  animation: flow $duration $easing infinite;
  animation-delay: calc(var(--i) * -0.4s);
}
@keyframes flow {
  0%   { transform: translateX(-130px) translateZ(-90px) rotateY(45deg) scale(.6); }
  40%  { transform: translateX(0) translateZ(0) rotateY(0deg) scale(1); }
  80%  { transform: translateX(130px) translateZ(-90px) rotateY(-45deg) scale(.6); }
  100% { transform: translateX(-130px) translateZ(-90px) rotateY(45deg) scale(.6); }
}

07回転ステージ

楕円の床の上でオブジェクトが5秒に一周ゆっくり回ります。キューブ回転(01)より遅く、傾いた床が加わってターンテーブルのように見えるので、新商品の360度プレビューやECの商品詳細ページに使います。

perspectivebackface-visibility타원 바닥 rotateX
.turntable__floor {
  border-radius: 50%;
  transform: translateX(-50%) rotateX(70deg);
}
.turntable__object {
  transform-style: preserve-3d;
  animation: turn $duration $easing infinite;
}
.turntable__face {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
}
.turntable__face--back { transform: rotateY(180deg); }
@keyframes turn {
  from { transform: rotateY(0deg); }
  to   { transform: rotateY(360deg); }
}

08立体テキスト

同じ色を一段ずつ暗くした8層のtext-shadowで文字に厚みを出し、下辺を軸にrotateXが傾いて戻ります。LPヒーローのタイトルやイベントバナーの見出しに向いています。

다중 text-shadowperspectiverotateX
@function 押し出し影($c, $n) {
  $out: ();
  @for $i from 1 through $n {
    $out: list.append($out, #{$i}px #{$i}px darken($c, $i * 4%), comma);
  }
  @return $out;
}
.sign {
  text-shadow: 押し出し影($color, 8);
  transform-origin: center bottom;
  animation: tilt $duration $easing infinite;
}
@keyframes tilt {
  0%, 20%  { transform: rotateX(0deg); }
  55%      { transform: rotateX(-34deg); }
  100%     { transform: rotateX(0deg); }
}

093Dルームスクロール

正面の壁がない部屋を9秒で一周させ、短いperspectiveでカメラが部屋の中にいるように見回させます。キューブ回転(01)の閉じた6面構造と違い、内側から見る開いた構造なので、ポートフォリオの3Dナビゲーションやインタラクティブな紹介セクションに使います。

짧은 perspectivetransform-style: preserve-3dbackface-visibility
.room-scene { perspective: 260px; }
.room {
  transform-style: preserve-3d;
  animation: look-around $duration $easing infinite;
}
.room__wall {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
}
.room__wall--back  { transform: translateZ(-$half); }
.room__wall--left  { transform: rotateY(90deg) translateZ(-$half); }
.room__wall--right { transform: rotateY(-90deg) translateZ(-$half); }
@keyframes look-around {
  from { transform: rotateY(-35deg); }
  to   { transform: rotateY(325deg); }
}

どこで崩れるか — 落とし穴

backface-visibility: hiddenを使う5つ(01・04・05・07・09)は、いずれも裏返る要素自身ではなくpreserve-3dを持つ親の内側にそれを置いています。理由は単純です。backface-visibility自体には効果がなく、祖先のどれか一つでもpreserve-3dを欠くとそこで3Dのコンテキストが切れ、子要素は平面に押しつぶされてしまうからです。04の本が開く効果を最初に組んだとき、表紙の中身である.book__faceにはpreserve-3dを付けたのに、実際に回転させる.book__cover自身に付け忘れていて、内側のページが裏返らずただ消えるだけになりました。

02の3Dカルーセルで角度をカードごとに変えようとして、rotateY(#{$i} * #{$step}deg)のようにSassの変数をそのまま文字列としてつなげると、rotateY(0 * 72deg)という無効なCSSが出力されます。ブラウザはcalc()なしではこの掛け算を読めないため、カード5枚が同じ場所に重なって止まって見えました。角度はSass側で先に掛け算を済ませ、完成した数値のリテラルとして渡す必要があります。この直し方まで反映した9本のファイルを開く合言葉はbfmwjndpで、下のカードから取り出せます。

06のカバーフローではz-indexをkeyframesの中に書きましたが、このプロパティはアニメーション可能な値ではないため、途中の値には補間されずキーフレームの境界で一気に切り替わります。滑らかに前後が入れ替わって見えるのは、実際にはopacityとscaleが同時に変わって目をごまかしているだけで、z-index自体はカクッと切り替わったままです。

アクセシビリティ

OSの「動きを減らす」設定を有効にした人のために、9つともprefers-reduced-motion: reduceを処理します。回転や傾きは止めますが、それぞれ決まった角度で止めるので、内容が急に消えたようには見えません。03のホバーチルトだけは例外で、transform自体をnoneに戻します。触れても反応がないと誤解されないようにするためです。

@media (prefers-reduced-motion: reduce) {
  .cube              { animation: none; transform: rotateY(-28deg); }
  .ring              { animation: none; transform: rotateY(0deg); }
  .tilt, .tilt.is-demo { animation: none; transition: none; transform: none; }
  .book__cover       { animation: none; transform: rotateY(-150deg); }
  .gallery.is-demo .tile__inner { animation: none; transform: rotateY(180deg); }
  .flow__card        { animation: none; transform: translateX(0) translateZ(0) rotateY(0deg) scale(1); opacity: 1; }
  .turntable__object { animation: none; transform: rotateY(-20deg); }
  .sign              { animation: none; transform: rotateX(-20deg); }
  .room              { animation: none; transform: rotateY(-15deg); }
}

03のカードにはtabindex="0"でキーボードフォーカスが当たるようにしてあり、:focus-visibleにも:hoverとまったく同じ角度を返します。マウスを使わない人にも同じ情報が届きます。

.tilt:hover,
.tilt:focus-visible,
.tilt:active {
  transform: rotateX(10deg) rotateY(-14deg);
}

perspective・transform-style・backface-visibilityはどれもcaniuseのCSS 3D Transforms対応表で主要ブラウザの現行バージョンがすべて対応済みと確認できます。3D以外のCSSアニメーションはカテゴリー一覧に、このサイトが何をしているかはこのサイトについてにまとめています。

FAQ

perspectiveの値はどれくらいにすればいいですか?

小さいほどカメラが近づいた分だけ歪みが強くなり、大きいほど平坦に見えます。09の3Dルームスクロールのように中に入り込む感覚が欲しいときは260px程度と短く、01のキューブ回転のように一つの物体を見せたいときは600px前後(01は640px)が扱いやすいです。数字を一つ覚えるより、9つの値を変えながら見比べるほうが感覚をつかみやすくなります。

回転が速すぎて酔いそうです。

すべて$durationという変数一つで速さを決めています。01のキューブ回転は2秒、07の回転ステージは5秒、09の3Dルームスクロールは9秒とそれぞれ違い、要素が大きく画面を広く占めるほど、ゆっくり回したほうが酔いにくくなります。

React版ではカードの並びをどう渡していますか?

zipのreact/フォルダでは、02や06のようにカードが複数枚ある項目を配列からmapして描画しています。カードごとに違う角度(02)や遅延時間(06)はSCSS変数ではなくインラインのstyleで渡し、残りの共通変数(duration・easing・color)の3つだけをpropsで受け取ります。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。