iT邦幫忙

2025 iThome 鐵人賽

DAY 25
0
Modern Web

30天技能樹養成:開啟前端冒險秘境系列 第 25

# Day25. CSS 動畫進階:速度曲線、變形原點、3D 與效能心法

  • 分享至 

  • xImage
  •  

昨天把動畫「動」起來了;但你可能發現,有些動畫看起來順、有質感,有些就像瞬移或卡卡的。
秘訣通常不在「有沒有動」,而在怎麼動:速度曲線(easing)、從哪裡變形(transform-origin)、以及別讓瀏覽器做吃力不討好的事(效能)。

今天我們把動畫功力再加一階,全部純 CSS,看得懂就能用


1. transition-timing-function:決定「怎麼加速、怎麼減速」

想像成坐電梯:

  • 線性(linear)=等速移動
  • ease-in=一開始慢慢加速
  • ease-out=最後慢慢減速(最常用)
  • ease-in-out=先加速再減速(自然)
  • cubic-bezier(x1,y1,x2,y2)=自訂曲線(大人玩法)

HTML

<button class="btn demo linear">linear</button>
<button class="btn demo ease-out">ease-out</button>
<button class="btn demo ease-in-out">ease-in-out</button>
<button class="btn demo bounce">cubic-bezier(彈跳感)</button>

CSS

.btn.demo {
  padding: 10px 16px;
  margin: 6px;
  border: 0; border-radius: 8px;
  background: #74c0fc; color: #fff;
  transition: transform 300ms, background 300ms;
}
.btn.demo:hover { transform: translateY(-6px); background: #4dabf7; }

.linear      { transition-timing-function: linear; }
.ease-out    { transition-timing-function: ease-out; }
.ease-in-out { transition-timing-function: ease-in-out; }
/* 輕微彈跳感:加速快、減速慢,尾端帶點彈性 */
.bounce      { transition-timing-function: cubic-bezier(.22, 1, .36, 1); }

把同一個動作(往上浮 6px),換不同「速度曲線」,你就能直接看見手感差異。實務上:

  • 按鈕/卡片 hover → ease-out(結尾柔和)
  • 開/關抽屜、Modal → ease-in-out(整體自然)
  • 想要活潑一點 → 自訂 cubic-bezier(微彈)

---- ✤ 短距離=短時間(150–250ms),大距離=長一點(250–400ms)。太慢就有拖泥帶水感 ✤----


2. transform-origin:從哪裡長大、從哪裡縮回去

預設縮放是「從中心」開始。如果你想讓選單從上緣打開、Tooltip 從下方冒出,改原點就對了

HTML

<div class="menu">
  <button class="menu-btn">打開選單</button>
  <ul class="menu-panel">
    <li>項目 A</li><li>項目 B</li><li>項目 C</li>
  </ul>
</div>

CSS

.menu { display:inline-block; position:relative; }
.menu-btn { padding:8px 12px; border:1px solid #ddd; border-radius:6px; background:#fff; }

.menu-panel {
  list-style:none; margin:8px 0 0; padding:8px;
  border:1px solid #eee; border-radius:8px; background:#fff;
  transform: scaleY(0); transform-origin: top; /* 從上緣展開 */
  transition: transform 220ms ease-out, opacity 220ms ease-out;
  opacity: 0;
}
.menu:hover .menu-panel { transform: scaleY(1); opacity: 1; }
.menu-panel li { padding:6px 4px; }

邏輯:
用 scaleY 模擬「高度變化」,但因為 transform 不會觸發重排(比直接改高度簡單),再配上 transform-origin: top;,視覺上就像「向下展開」

3. 移動用 transform

top/left 會觸發重排(layout),整個版面得重新計算;
transform: translate 走 GPU 合成,比較省力、更順~

HTML

<div class="row">
  <div class="box move-left">left: 120px</div>
  <div class="box move-translate">translateX(120px)</div>
</div>

CSS

.row { display:flex; gap:16px; }
.box {
  width:160px; height:64px; line-height:64px; text-align:center;
  background:#ffd6a5; border-radius:10px; position:relative;
  transition: all 300ms;
}
.move-left:hover      { left: 120px; }               /* 不推薦 */
.move-translate:hover { transform: translateX(120px);}/* 推薦 */

重點:
畫面上看起來都會動,但「用 translate 的」卡頓更少!!

4. 3D 變形:翻牌、景深與 perspective

用 CSS,就能做出「卡片翻面」的 3D 效果

HTML

<div class="scene">
  <div class="card">
    <div class="card-face card-front">FRONT</div>
    <div class="card-face card-back">BACK</div>
  </div>
</div>

CSS

.scene {
  width:220px; height:140px; perspective: 1000px; /* 給觀察者的距離 */
}
.card {
  width:100%; height:100%;
  position:relative;
  transform-style: preserve-3d;  /* 保留 3D 子元素 */
  transition: transform 600ms cubic-bezier(.22,1,.36,1);
}
.scene:hover .card { transform: rotateY(180deg); }

.card-face {
  position:absolute; inset:0;
  display:grid; place-items:center;
  font: 700 24px/1 system-ui;
  border-radius:14px;
  backface-visibility: hidden; /* 背面翻過來時隱藏 */
}
.card-front { background:#caffbf; }
.card-back  { background:#9bf6ff; transform: rotateY(180deg); }

為什麼能有效果?

  • perspective:決定「景深感」
  • preserve-3d:讓子元素保留 3D 空間
  • 兩面各自 rotateY(0/180deg),滑過時整張卡片翻面

5. 效能心法:will-change 有用,但別亂撒

will-change 告訴瀏覽器:「等下我要動這個屬性,先準備好」,
用在即將動畫的元素上(例如 hover 前一刻),效果會更穩;但大量長期設定會耗資源。

.btn:hover { will-change: transform, opacity; }
/* 或者在要進場的 .modal 加上,顯示完畢再移除(用 CSS 只要在「動畫階段」的 class 上加) */

原則:只在「有動畫的當下」加,動畫結束就移除(用 class 控制)。

6. 友善模式:尊重「減少動效」設定

有些使用者會在系統裡選擇「減少動態」。我們可以自動淡化動畫。

@media (prefers-reduced-motion: reduce) {

  • { animation: none !important; transition: none !important; }
    }

好處:更包容、避免暈動不適

7. 兩個常用小配方

A. Modal「淡入+放大」

<div class="overlay">
  <div class="modal">內容內容</div>
</div>
.overlay {
  position: fixed; inset: 0;
  background: rgba(0,0,0,.4);
  display:grid; place-items:center;
  opacity: 0; pointer-events: none;
  transition: opacity 220ms ease-out;
}
.overlay.show { opacity: 1; pointer-events: auto; }

.modal {
  width: 320px; padding: 20px; border-radius: 12px; background: #fff;
  transform: scale(.92);
  transition: transform 220ms ease-out, opacity 220ms ease-out;
  opacity: 0;
}
.overlay.show .modal { transform: scale(1); opacity: 1; }

邏輯:先小、淡 → 出場時放大+變清晰

B. 手風琴(純 CSS 思路)

(高度未知時不建議動畫 height:auto,用 max-height 模擬)

<div class="acc">
  <button class="acc-btn">更多說明</button>
  <div class="acc-panel">
    <p>從前從前……</p>
  </div>
</div>
.acc-btn { display:block; width:100%; text-align:left; padding:10px; }
.acc-panel {
  max-height: 0; overflow: hidden;
  transition: max-height 300ms ease, opacity 300ms ease;
  opacity: 0;
}
.acc:hover .acc-panel {
  max-height: 200px; /* 足夠大的值,撐到內容的高度 */
  opacity: 1;
}

邏輯:用「可動畫的上限值」代替 height:auto,再搭配 opacity 讓過渡更自然

8. 什麼時候用什麼?

  • 位移/縮放/旋轉transform(效能佳)
  • 透明度opacity(效能佳)
  • 寬高/位置 → 儘量避免動畫;要做就用技巧(如 max-height)

速度曲線 →

  • hover/小互動ease-out ~200ms
  • 視覺切換(Modal/Drawer)ease-in-out 220–320ms
  • cubic-bezier(.22,1,.36,1),輕跳感

....只是參考....


學習心得

之前只會「讓它動」,現在知道又這麼多種方法可以使用:

  • 換個速度曲線,整個手感就升級
  • 改 transform-origin,選單/Tooltip 的出場方向就合理
  • 用 translate/scale ,動畫絲滑流暢
  • 再加上 prefers-reduced-motion,對不同使用者都友善

上一篇
# Day24. CSS 動畫入門:讓畫面活起來 ✨
下一篇
Day26. 動畫小結與實戰:讓 UI 更自然
系列文
30天技能樹養成:開啟前端冒險秘境30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言