最後更新:2026/10/15
網站做好了,老闆看著首頁說「好像有點死」。他想要的多半是這幾樣:主視覺的背景會慢慢流動、服務的卡片滑過去會翻面、作品集有翻書的感覺、週年慶活動頁有一個抽獎的動畫。這些效果都有外掛,但每裝一個外掛就多一包程式碼,而且常常不能改。
這四個效果用純 CSS 加一小段 JavaScript 就能做,這篇每一個都給程式碼,說明關鍵的幾行在做什麼,最後講一個很多網站漏掉的事:使用者在系統裡設了「減少動態效果」時,動畫要停下來。四個效果我們都在 Chrome 用電腦和手機寬度測過。
一、會流動的漸層背景
.hero {
position: relative;
overflow: hidden;
min-height: 320px;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
color: #fff;
background: linear-gradient(120deg, #ffb703, #fb8500, #e63946, #8338ec);
background-size: 300% 300%;
animation: mimi-flow 12s ease-in-out infinite;
}
@keyframes mimi-flow {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
/* 幾顆慢慢飄的圓 */
.hero .blob {
position: absolute;
width: 220px; height: 220px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.18);
filter: blur(2px);
animation: mimi-float 9s ease-in-out infinite;
}
.hero .blob:nth-child(2) { left: 10%; top: 10%; }
.hero .blob:nth-child(3) { right: 8%; bottom: -40px; width: 300px; height: 300px; animation-delay: -4s; }
@keyframes mimi-float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-30px); }
}
.hero h1 { position: relative; font-size: clamp(1.6rem, 4vw, 2.6rem); margin: 0; text-shadow: 0 2px 12px rgba(0,0,0,.25); }
HTML 是一個 .hero 區塊,裡面放三個 <span class="blob"> 和一個 <h1>。原理:漸層背景做成畫面的三倍大(background-size: 300%),再用動畫慢慢移動背景的位置,看起來顏色就在流動。幾顆半透明的圓用另一個動畫上下飄,animation-delay 設成負數讓它們不要同步。顏色換成你網站的配色,挑法在〈網站配色怎麼選〉。12 秒一個循環是刻意放慢的,流動太快會讓人頭暈。
二、3D 翻轉卡片
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 20px; max-width: 760px; }
.flip { perspective: 1000px; height: 180px; }
.flip-inner {
position: relative; width: 100%; height: 100%;
transform-style: preserve-3d;
transition: transform .6s;
}
.flip:hover .flip-inner, .flip:focus-within .flip-inner, .flip.is-flipped .flip-inner { transform: rotateY(180deg); }
.face {
position: absolute; inset: 0;
display: flex; align-items: center; justify-content: center;
padding: 16px; border-radius: 16px;
backface-visibility: hidden; /* 背面轉過來時,正面不要透出來 */
border: 2px solid #222;
background: #fff7d6;
font-weight: 700;
}
.face.back { transform: rotateY(180deg); background: #222; color: #ffb703; font-weight: 400; }
.flip button { position: absolute; inset: 0; opacity: 0; width: 100%; height: 100%; cursor: pointer; }
每張卡片的 HTML 是:.flip 裡放 .flip-inner,裡面兩個 .face(正面和 .face.back),最後一顆看不見的 <button> 蓋在整張卡上。三個關鍵:外層的 perspective 給 3D 的景深;transform-style: preserve-3d 讓正反兩面在 3D 空間裡各自存在;backface-visibility: hidden 讓轉到背面時正面不會透出來。背面一開始就先轉 180 度,整張卡轉 180 度時,背面剛好正對你。
手機沒有「滑鼠移上去」,所以多了那顆透明的按鈕:按一下加上 is-flipped 類別就翻面,鍵盤的使用者用 Tab 到按鈕也會翻(:focus-within)。
document.querySelectorAll('.flip button').forEach(function (b) {
b.addEventListener('click', function () { b.parentElement.classList.toggle('is-flipped'); });
});
三、3D 翻頁
.book { perspective: 1400px; width: min(100%, 520px); height: 240px; position: relative; margin: 0 auto; }
.page {
position: absolute; left: 50%; top: 0; width: 50%; height: 100%;
transform-origin: left center; /* 以書脊為軸轉 */
transform-style: preserve-3d;
transition: transform .9s cubic-bezier(.3,0,.2,1);
cursor: pointer;
}
.page-face {
position: absolute; inset: 0; padding: 20px; box-sizing: border-box;
border: 2px solid #222; background: #fff; backface-visibility: hidden;
}
.page-face.back { transform: rotateY(180deg); background: #fff7d6; }
.book.is-open .page { transform: rotateY(-180deg); }
.book .cover-left { position: absolute; left: 0; top: 0; width: 50%; height: 100%; border: 2px solid #222; box-sizing: border-box; background: #e8f3ec; padding: 20px; }
翻頁和翻卡片是同一個原理,差別在轉軸:卡片以中心轉,書頁以左邊(書脊)轉,所以 transform-origin: left center,而且往負的方向轉 180 度,右頁就翻到左邊。點一下整本書加上 is-open 類別就翻過去,再點一下翻回來。這是最簡單的單頁翻書,多頁的翻書要用 JavaScript 管每一頁的順序和層次,那就超出這篇的範圍。
四、抽獎滾輪
.drum { width: min(100%, 320px); height: 64px; overflow: hidden; border: 3px solid #222; border-radius: 14px; background: #fff; position: relative; margin: 0 auto 12px; }
.drum::before, .drum::after { content: ""; position: absolute; left: 0; right: 0; height: 18px; z-index: 1; pointer-events: none; }
.drum::before { top: 0; background: linear-gradient(#fff, rgba(255,255,255,0)); }
.drum::after { bottom: 0; background: linear-gradient(rgba(255,255,255,0), #fff); }
.drum ul { list-style: none; margin: 0; padding: 0; transition: transform 3s cubic-bezier(.1,.8,.2,1); }
.drum li { height: 64px; line-height: 64px; text-align: center; font-size: 1.4rem; font-weight: 700; }
.drum.is-done li.winner { color: #e63946; }
.btn { display: block; margin: 0 auto; padding: 10px 24px; border: 2px solid #222; border-radius: 999px; background: #ffb703; font-weight: 700; cursor: pointer; }
(function () {
var names = ['小美', '阿哲', '雅婷', '俊宏', '佳蓉', '志明', '怡君', '家豪'];
var ul = document.getElementById('names'), drum = document.getElementById('drum'), btn = document.getElementById('draw');
var rows = [];
for (var r = 0; r < 6; r++) { names.forEach(function (n) { rows.push(n); }); }
ul.innerHTML = rows.map(function (n) { return '<li>' + n + '</li>'; }).join('');
btn.addEventListener('click', function () {
drum.classList.remove('is-done');
ul.querySelectorAll('li').forEach(function (li) { li.classList.remove('winner'); });
var pick = Math.floor(Math.random() * names.length); // 抽中第幾個人
var index = names.length * 4 + pick; // 先轉 4 圈再停
ul.style.transition = 'none'; ul.style.transform = 'translateY(0)';
void ul.offsetHeight; // 讓瀏覽器先畫一次歸零
ul.style.transition = '';
ul.style.transform = 'translateY(-' + index * 64 + 'px)';
ul.children[index].classList.add('winner');
setTimeout(function () { drum.classList.add('is-done'); btn.textContent = '抽中:' + names[pick] + '(再抽一次)'; }, 3000);
});
})();
滾輪是一個高度固定、超出的部分藏起來的框(.drum),裡面一個很長的名單,每一列 64 像素高。按下按鈕時,程式隨機挑一個人,把名單往上推到那個人剛好在框裡的位置:名單先複製六次,推的距離是「轉四圈再加抽中的位置」,用 3 秒的緩動讓它先快後慢停下來,上下兩道漸層遮罩做出滾筒的感覺,停下來的那一格變紅色。要用在真的活動上,名單從後台或表單匯入,抽中的人記得另外存起來,動畫只是給現場看的。
五、尊重「減少動態效果」
有些人看動畫會暈,手機和電腦的系統都有「減少動態效果」的設定,網站要聽它的話:
@media (prefers-reduced-motion: reduce) {
.hero, .hero .blob { animation: none; }
.flip-inner, .page, .drum ul { transition: none; }
}
這一段放在所有動畫的最後面:使用者設了減少動態,背景不流動、圓不飄、卡片和書頁直接翻到位、滾輪直接跳到結果,功能都在,只是沒有過程。我們在 Chrome 模擬這個設定測過,四個效果的動畫都停了。這是一個不花力氣但很多網站沒做的事。
測試結果
我們把四個效果放在同一頁,在 Chrome 用 1280 和 390 兩種寬度測:背景動畫和飄動都在跑,沒有橫向捲動;按卡片會翻面、按書會翻頁、按抽獎會轉三秒停在抽中的人上;開「減少動態效果」之後,背景動畫變成無、翻轉的過渡時間變成 0 秒。
效果要為內容服務
流動的背景是為了讓主視覺的標題被看到,翻面的卡片是為了讓說明在需要時才出現,抽獎動畫是為了活動的氣氛。效果本身不是目的,一頁放超過兩種動態效果,客人的注意力就散了。按鈕和文字的小動畫在〈CSS 文字動畫與按鈕效果〉,區塊之間的斜角和波浪分隔在〈CSS 區塊分隔線〉,三篇合起來,首頁要的動態差不多都夠了。
如果你想要一個有適度動態、而且在手機上一樣順的網站,可以先看〈IG 帳號不是你的資產〉想想網站要做到什麼。MIMI 專做客製化網頁設計,首頁的動態效果會依品牌設計,並確保手機和減少動態的使用者都能正常看。MIMI 提供台灣網頁設計服務,全台灣的店家都可以用電話、Email 直接和設計師討論;費用可以先看首頁的製作費用參考,歡迎聯絡 MIMI。
