昨天把動畫「動」起來了;但你可能發現,有些動畫看起來順、有質感,有些就像瞬移或卡卡的。
秘訣通常不在「有沒有動」,而在怎麼動:速度曲線(easing)、從哪裡變形(transform-origin)、以及別讓瀏覽器做吃力不討好的事(效能)。
今天我們把動畫功力再加一階,全部純 CSS,看得懂就能用
想像成坐電梯:
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),換不同「速度曲線」,你就能直接看見手感差異。實務上:
---- ✤ 短距離=短時間(150–250ms),大距離=長一點(250–400ms)。太慢就有拖泥帶水感 ✤----
預設縮放是「從中心」開始。如果你想讓選單從上緣打開、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
;,視覺上就像「向下展開」
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 的」卡頓更少!!
用 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 空間will-change 告訴瀏覽器:「等下我要動這個屬性,先準備好」,
用在即將動畫的元素上(例如 hover 前一刻),效果會更穩;但大量長期設定會耗資源。
.btn:hover { will-change: transform, opacity; }
/* 或者在要進場的 .modal 加上,顯示完畢再移除(用 CSS 只要在「動畫階段」的 class 上加) */
原則:只在「有動畫的當下」加,動畫結束就移除(用 class 控制)。
有些使用者會在系統裡選擇「減少動態」。我們可以自動淡化動畫。
@media (prefers-reduced-motion: reduce) {
好處:更包容、避免暈動不適
<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; }
邏輯:先小、淡 → 出場時放大+變清晰
(高度未知時不建議動畫 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 讓過渡更自然
速度曲線 →
ease-out
~200msease-in-out
220–320mscubic-bezier(.22,1,.36,1)
,輕跳感....只是參考....
之前只會「讓它動」,現在知道又這麼多種方法可以使用: