在 CSS 3D 變換中,transform: perspective(400px) rotateY(40deg) 與 transform: rotateY(40deg) perspective(400px) 雖語意相近,但在 Chrome、Firefox 等主流瀏覽器中產生完全不同的渲染結果。本文深入解析矩陣乘法順序對 3D 視覺效果的決定性影響,並提供標準化寫法建議。
/* 正確:有透視效果 */
transform: perspective(400px) rotateY(40deg);
/* 錯誤:無透視效果 (平面旋轉) */
transform: rotateY(40deg) perspective(400px);

CSS Transform 函數按**書寫順序從左至右**依次套用,對應矩陣乘法 M = M₁ × M₂ × M₃ ...,向量變換為 v' = M × v。
M = P × R
v' = P × (R × v)
M = R × P
v' = R × (P × v)
| 屬性/函數 | 作用對象 | 說明 |
|---|---|---|
perspective() 函數 | 單一元素 | 設定該元素的透視距離,僅影響自身及子元素 |
perspective 屬性 | 父容器 | 為所有 3D 變換子元素建立共享透視場景,更自然 |
perspective-origin | 父容器 | 透視中心點 (攝影機位置),預設 50% 50% (元素中心) |
transform-style: preserve-3d | 父容器 | 保持子元素在 3D 空間,而非扁平化至父平面 |
/* 父容器建立共享 3D 場景 */
.scene {
perspective: 800px; /* 攝影機距離 800px */
perspective-origin: 50% 50%; /* 視點在中心 */
transform-style: preserve-3d; /* 關鍵:保持子元素 3D */
}
/* 子元素只需寫變換 */
.card {
transform: rotateY(40deg); /* 無需重複 perspective() */
transition: transform 0.6s;
}
優勢:多個子元素共享同一透視視點,視覺一致;動畫效能更佳;程式碼更語意化。
| 特性 | Chrome | Firefox | Safari | Edge | IE |
|---|---|---|---|---|---|
perspective() 函數 | 12+ (-webkit- 36-) | 10+ (-moz- 16-) | 4+ (-webkit-) | 12+ | 10+ |
perspective 屬性 | 12+ (-webkit- 36-) | 10+ (-moz- 16-) | 4+ (-webkit-) | 12+ | 10+ |
perspective-origin | 12+ (-webkit- 36-) | 10+ (-moz- 16-) | 4+ (-webkit-) | 12+ | 10+ |
transform-style: preserve-3d | 12+ (-webkit- 36-) | 10+ (-moz- 16-) | 4+ (-webkit-) | 12+ | 11+ (部分支援) |


0 0:左上角視點,物體向右下傾斜50% 50%:中心視點 (預設),對稱透視100% 100%:右下角視點,物體向左上傾斜
<!DOCTYPE html>
<html>
<head>
<style>
.scene {
width: 200px; height: 200px;
margin: 50px auto;
perspective: 600px;
perspective-origin: 50% 50%;
transform-style: preserve-3d;
}
.cube {
width: 100%; height: 100%;
position: relative;
transform-style: preserve-3d;
animation: rotate 10s infinite linear;
}
.face {
position: absolute;
width: 200px; height: 200px;
border: 2px solid #333;
background: rgba(255,255,255,0.8);
display: flex; align-items: center; justify-content: center;
font-size: 24px; font-weight: bold;
}
.front { transform: translateZ(100px); background: rgba(255,0,0,0.3); }
.back { transform: rotateY(180deg) translateZ(100px); background: rgba(0,255,0,0.3); }
.right { transform: rotateY(90deg) translateZ(100px); background: rgba(0,0,255,0.3); }
.left { transform: rotateY(-90deg) translateZ(100px); background: rgba(255,255,0,0.3); }
.top { transform: rotateX(90deg) translateZ(100px); background: rgba(255,0,255,0.3); }
.bottom { transform: rotateX(-90deg) translateZ(100px); background: rgba(0,255,255,0.3); }
@keyframes rotate {
from { transform: rotateX(0) rotateY(0); }
to { transform: rotateX(360deg) rotateY(360deg); }
}
/* 控制面板 */
.controls { text-align: center; margin: 20px; }
.controls label { display: block; margin: 10px 0; }
.controls input[type="range"] { width: 300px; }
</style>
</head>
<body>
<div class="scene">
<div class="cube">
<div class="face front">前</div>
<div class="face back">後</div>
<div class="face right">右</div>
<div class="face left">左</div>
<div class="face top">頂</div>
<div class="face bottom">底</div>
</div>
</div>
<div class="controls">
<label>Perspective: <input type="range" id="persp" min="200" max="2000" value="600"> <span id="perspVal">600</span>px</label>
<label>Origin X: <input type="range" id="orgX" min="0" max="100" value="50"> <span id="orgXVal">50</span>%</label>
<label>Origin Y: <input type="range" id="orgY" min="0" max="100" value="50"> <span id="orgYVal">50</span>%</label>
</div>
<script>
const scene = document.querySelector('.scene');
const persp = document.getElementById('persp');
const orgX = document.getElementById('orgX');
const orgY = document.getElementById('orgY');
[persp, orgX, orgY].forEach(input => {
input.addEventListener('input', () => {
scene.style.perspective = persp.value + 'px';
scene.style.perspectiveOrigin = orgX.value + '% ' + orgY.value + '%';
document.getElementById('perspVal').textContent = persp.value;
document.getElementById('orgXVal').textContent = orgX.value;
document.getElementById('orgYVal').textContent = orgY.value;
});
});
</script>
</body>
</html>
| 錯誤寫法 | 問題 | 修正 |
|---|---|---|
transform: rotateY(40deg) perspective(400px) | 透視失效 | transform: perspective(400px) rotateY(40deg) |
子元素用 perspective()、父元素也設 perspective | 雙重透視、效果疊加混亂 | 二選一,推薦父元素 perspective 屬性 |
缺少 transform-style: preserve-3d | 子元素扁平化,3D 結構崩塌 | 父容器必須設定 |
在 transform 中混用 translateZ 和 perspective() | 順序敏感,易產生非預期結果 | 改用容器級 perspective + 子元素 translateZ |
perspective() 必須在旋轉/位移**之前** (左側)perspective 屬性優於函數:共享視點、語意清晰、效能更好perspective (距離) + perspective-origin (視點) + transform-style: preserve-3d (3D 空間保持)transform: matrix3d(...) 直接檢查掌握透視矩陣的幾何意義與乘法順序,能讓你精準控制 CSS 3D 視覺效果,避開「看起來對但沒效果」的順序陷阱,構建專業級的 3D 交互體驗。