只使用 CSS!拆解寶可夢 3D 閃卡特效

913 字

在網路上看到 pokemon-cards-css 把寶可夢卡牌的 3D 及閃膜用純 CSS 做出來,真的超帥!這篇整理我理解原始碼並自己實作的過程。


1. 卡片骨架

卡片的 DOM 結構只有三層 div:

  • .cardContainer:卡片的容器,用來置中卡片並設定比例。
  • .card:卡片本身,負責建立 3D 空間。
  • .rotator:實際執行 3D 旋轉的元素
PokemonCard.jsx
<div className='cardContainer'>
<div className='card'>
<div className='rotator'>
<img src='圖片網址' />
</div>
</div>
</div>

搭配 CSS 讓卡片擁有 3D 空間:

PokemonCard.module.css
.cardContainer {
display: flex;
justify-content: center;
align-items: center;
}
.card {
/* 透視深度,數值越小,透視變形的效果越強 */
perspective: 600px;
/* 讓裡面的子元素都位於 3D 空間 */
transform-style: preserve-3d;
}
.rotator {
position: relative;
transform-style: preserve-3d;
transform-origin: center;
/* 利用 CSS 變數控制轉動,初始值為 0 */
transform: rotateY(var(--rotate-y, 0)) rotateX(var(--rotate-x, 0));
}

這樣一來,我們的卡片就已經具備了 3D 的基礎,雖然現在看起來還不會動。

Front of 妙蛙種子 Pokemon Card

2. 卡片隨滑鼠傾斜

接著,我們需要用到 JavaScript 讓卡片可以偵測到滑鼠的位置,並跟著傾斜。

.card 上綁定 onMouseMove 事件:

PokemonCard.jsx
const onMouseMove = (e) => {
const rect = e.currentTarget.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const xp = Math.round((x / rect.width) * 100);
const yp = Math.round((y / rect.height) * 100);
const dx = xp - 50;
const dy = yp - 50;
setCardVars({
'--pointer-x': `${xp}%`,
'--pointer-y': `${yp}%`,
'--rotate-x': `${dy / 2}deg`, // Y 軸偏移量拿來轉 X 軸
'--rotate-y': `${-dx / 3.5}deg`, // X 軸偏移量拿來轉 Y 軸
'--rotator-transition': '120ms',
});
};

程式碼的邏輯很單純:

  1. 取得滑鼠在卡片內的座標 (x, y)
  2. 將座標轉換成百分比 (xp, yp)
  3. 計算滑鼠位置與卡片中心的偏移量 (dx, dy)
  4. 根據偏移量去設定 --rotate-x--rotate-y 等 CSS 變數,卡片就會跟著旋轉了!

記得再透過 setCardVars 設定 CSS 變數,卡片就會神奇地動起來了!

如何使用 JS 設定 CSS 變數?
PokemonCard.jsx
const cardRef = useRef(null);
const pointerStateRef = useRef({ x: 50, y: 50 });
const setCardVars = (vars) => {
const node = cardRef.current;
if (!node) return;
for (const [k, v] of Object.entries(vars)) {
node.style.setProperty(k, v);
if (k === '--pointer-x') {
const n = parseFloat(v);
if (!Number.isNaN(n)) pointerStateRef.current.x = n;
}
if (k === '--pointer-y') {
const n = parseFloat(v);
if (!Number.isNaN(n)) pointerStateRef.current.y = n;
}
}
};

當滑鼠移開時,也要記得把卡片轉回原位:

PokemonCard.jsx
const onMouseLeave = () => {
setCardVars({
'--rotate-x': '0deg',
'--rotate-y': '0deg',
'--rotator-transition': '600ms',
});
};
Front of 妙蛙種子 Pokemon Card

3. 加上眩光效果

最後,來為卡片加上閃亮的眩光效果吧!

先在 .rotator 裡面加上一個 .glare 元素:

PokemonCard.jsx
<div className='rotator'>
<img src='圖片網址' />
<div className='glare' />
</div>

.glare 的樣式是利用 radial-gradient 做出來的:

PokemonCard.module.css
.glare {
position: absolute;
inset: 0;
mix-blend-mode: overlay;
/* 眩光本體 */
background-image: radial-gradient(
farthest-corner circle at var(--pointer-x, 50%) var(--pointer-y, 50%),
hsla(0, 0%, 100%, 0.8) 10%,
hsla(0, 0%, 100%, 0.65) 20%,
hsla(0, 0%, 0%, 0.5) 90%
);
opacity: var(--glare-opacity, 0);
transition: opacity var(--glare-transition, 300ms) ease-out;
pointer-events: none; /* 讓滑鼠可以穿透這層 */
}

記得一樣要在 setCardVars 裡面設定 --glare-opacity--glare-transition,這樣當滑鼠移進卡片時,眩光才會出現:

PokemonCard.jsx
const onMouseMove = (e) => {
...
setCardVars({
...
'--glare-opacity': '1',
'--glare-transition': '180ms',
});
};
const onMouseLeave = () => {
setCardVars({
...
'--glare-opacity': '0',
'--glare-transition': '600ms',
});
};
Front of 妙蛙種子 Pokemon Card

完整程式碼

這篇會再持續學習並更新其他閃卡特效~


© 2024 - 2026 kir4che