iT邦幫忙

2026 iThome 鐵人賽

DAY 22
0
Modern Web

從實務需求出發:前端視覺與 UI 互動開發實踐系列 第 22 篇

【Day 22】動畫實作:用 perspective 製作 CSS 3D 翻牌效果

  • 分享至 

  • xImage
  •  

昨天做佈告欄時,已經先用了一點 3D:perspective() 搭配 rotateX(),讓紙片被掀起時有立體感。今天就把 CSS 3D 完整拆開來,先認識 CSS 3D 的幾個關鍵屬性,再用它做出「翻牌」動畫。那我們開始吧!


一、CSS 3D 介紹

可以把 CSS 3D 想像成一個拍攝現場:

  • 螢幕是一個攝影空間,元素是擺在裡面的道具。道具可以被移動、旋轉(transform)。
  • 要拍出立體感,還得有一台鏡頭(perspective)。
  • 如果道具是由好幾塊組成的,還得告訴場景「這些零件都在同一個空間裡」(preserve-3d)。
  • 最後,道具的背面要不要被拍到,也可以決定(backface-visibility)。

下面逐一說明。

從 2D 到 3D,多了一個 Z 軸

2D 只有 X(左右)、Y(上下)兩個方向,3D 則多了 Z 軸,代表朝向或遠離螢幕的方向。要注意 CSS 的 Y 軸是往下為正。

座標軸示意圖

對應的轉換函式有:

類型 函式
旋轉 rotateX()、rotateY()、rotateZ()、rotate3d()
位移 translateZ()、translate3d()
縮放 scaleZ()、scale3d()
  • rotateX():繞著水平軸轉,像「前後翻」。
  • rotateY():繞著垂直軸轉,像「左右開門」。
  • rotateZ():繞著 Z 軸轉,其實就是 2D 的 rotate()。

角度的正負值,依 MDN 的說明,正值是順時針、負值是逆時針。但要特別注意,這個「順時針」是從該軸的正端朝原點看的方向,不是從螢幕正面看。

rotateX(45deg)、rotateY(45deg) 對照圖

perspective 立體感的來源

只對元素做 rotateY(60deg),看起來只是被橫向壓扁,因為沒有「遠近」的概念,近的邊和遠的邊一樣大。

這時就需要 perspective,它決定的是「鏡頭離畫面有多遠」:

  • 值越小:鏡頭越近,透視變形越誇張。
  • 值越大:鏡頭越遠,透視越平緩。

perspective 對照圖

perspective 有屬性與函式兩種寫法:

/* 寫在父層:所有子層共用同一個消失點 */
.stage {
  perspective: 900px;
}

/* 寫在自己:只影響自己,要放在 transform 最前面(昨天用的就是這種) */
.card {
  transform: perspective(900px) rotateY(60deg);
}

鏡頭的位置預設在中心,若想調整,可以使用 perspective-origin 屬性改變鏡頭的位置(消失點)。

preserve-3d 讓子層保有 3D

perspective 只會對「直接子層」生效,不會被繼承下去。如果子層裡面還有孫層,預設的 transform-style: flat 會把孫層壓平在子層的平面上,看不出各自的 3D 位置。

設成 preserve-3d 之後,孫層才會跟著待在同一個 3D 空間裡。

.parent { perspective: 900px; }

.child {
  transform-style: preserve-3d; /* 孫層才能保有 3D */
}

💡 preserve-3d 要「一層一層」連續設定。中間任何一層沒設,畫面一樣會被壓扁。另外,部分屬性(例如 overflow 不是 visible、opacity 小於 1、filter 等)也會讓 preserve-3d 失效,完整清單請參考 MDN。

preserve-3d 會讓瀏覽器多做 3D 的處理,遇到效能問題時,可以在不需要立體的地方維持預設的 flat。

backface-visibility 背面要不要顯示

元素轉到背對螢幕時,預設會看到「鏡像的背面」,也就是反著的圖像。設成 hidden,背對螢幕時就直接不顯示。

.face {
  backface-visibility: hidden;
}

這就是翻牌效果的核心:把兩個面疊在一起,兩面都設成「背對時不顯示」,轉到哪一面,就只會看到哪一面。


二、實作

畫面是一排牌卡,hover 時會稍微靠近鏡頭。原本是牌背朝上,點擊牌卡後,沿著垂直軸翻轉,露出牌面;再點一次就翻回去。

1. 結構:外層、內層、兩面

<div class="flip-card">
  <div class="flip-card-inner">
    <div class="flip-card-back">牌背</div>
    <div class="flip-card-front">牌面</div>
  </div>
</div>

三層各司其職:

  • .flip-card:加上 perspective,提供立體空間(鏡頭)。
  • .flip-card-inner:加上 transform-style: preserve-3d,並負責旋轉。
  • .flip-card-back、.flip-card-front:牌的正反兩個面,用 position: absolute 疊在一起,並設定 backface-visibility: hidden。

💡 注意 perspective 下在被旋轉者的父層。因為 perspective 屬性是作用在子元素身上,如果和 rotateY 放在同一個元素上,這個元素自己的旋轉就不會有透視效果。

2. 翻轉邏輯

.flip-card {
  width: 200px;
  aspect-ratio: 5 / 7;
  perspective: 900px;
}

.flip-card-inner {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  transition: transform 0.35s ease;
}

.flip-card:hover .flip-card-inner {
  transform: translateZ(30px);
}

.flip-card.is-flipped .flip-card-inner {
  transform: rotateY(180deg);
}

.flip-card.is-flipped:hover .flip-card-inner {
  transform: translateZ(30px) rotateY(180deg);
}

.flip-card-back,
.flip-card-front {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
}

.flip-card-front {
  transform: rotateY(180deg); /* 預先翻到背後 */
}

用一點 JavaScript(或 Vue 的 :class)切換 is-flipped:

document.querySelectorAll('.flip-card').forEach(card => {
  card.addEventListener('click', () => card.classList.toggle('is-flipped'));
});

重點在 .flip-card-front 這一行 transform: rotateY(180deg)。牌面一開始就先轉到背後,加上 backface-visibility: hidden,所以一開始看到的是牌背。當 .flip-card-inner 轉了 180 度,牌背轉到後面被隱藏,原本在後面的牌面剛好轉到前面。

其他小細節:

  • 牌背和牌面是疊在一起的兩個元素,如果內容(例如圖片)把其中一面撐出不同高度,兩面就對不上了。所以外層用 aspect-ratio 固定長寬比,兩個面都用 inset: 0 撐滿,才能完全疊合。
  • 用 transition,而不是 @keyframes,因為翻牌是「狀態 A ↔ 狀態 B」的來回切換,這種情況用 transition 最適合。

這樣,一張可以翻轉的牌就完成了!

CodePen 範例連結

demo gif


其實翻牌也可以只用 scaleX 做到:讓牌在水平方向從 1 縮到 0,換成另一個面,再從 0 放大回 1。不過因為沒有視覺上的透視感,比起被翻過去,更像被壓扁、拉伸。想要更像真實的翻牌,還是要用 CSS 3D。

今天用了 perspective、preserve-3d 製作出有立體感的翻轉動畫,讓網頁的動態效果更加吸睛。


參考資料


上一篇
【Day 21】動畫實作:掀起釘選的紙片
系列文
從實務需求出發:前端視覺與 UI 互動開發實踐 共 22 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言