英文原文An Introduction to CSS 3-D Transforms
愛因斯坦說所有概念都必須介紹給兒童們,若他們無法了解,這些理論就毫無價值。
透視
一個元素需要一個透視點才能激活3D空間,有兩種方法可以得到透視點:
- 使用transform屬性,賦上perspective函數作為值。
-webkit-transform: perspective(600);
- 或使用perspective屬性。
-webkit-perspective: 600;
左邊是使用transform屬性的,右邊使用perspective屬性
這兩種方法都能觸發3D空間,但卻有所不同。首先,使用函數方式可以方便快捷地對單一元素應用3D變形,但是當你要應用在多個元素上時,它們可能不會按照預期的效果排列。如果你使用同樣的transform屬性應用在多個不同位置的元素上,每個元素都有自己的消失點。為了避免這種滑稽的效果,使用perspective屬性應用在它們的父容器元素上,這樣每個元素都共享了同一個消失點。
左邊是使用transform屬性的,右邊使用perspective屬性
perspective屬性的值決定了3D效果的強度。
你拿一本書平放在面前,看着書感受一下透視感,perspective屬性值就是眼睛和書之間的距離,距離越遠,數值越大,透視感越小;距離越近,數值越小,透視感越強。
默認情況下,3D空間的消失點位於空間的正中央,你可以通過perspective-origin屬性改變消失點的位置。
CSS
-webkit-perspective-origin: 25% 75%;
3D變形函數
作為一個Web設計師,你可能非常熟悉二維世界,X和Y,水平和垂直方向。在perspective創建的三維空間中,我們可以在三個維度上任意變換一個元素。
3D變形使用的是和2D變形類似的transform屬性。如果你熟悉2D變形,你會發現它和基本的3D變形很像。
CSS
rotateX(angle); rotateY(angle); rotateZ(angle); translateZ(tz); scaleZ(sz);
我們借鑒translateX()這個函數,它令一個元素沿着水平X軸方向平移,而translateZ()函數則是沿着垂直的Z軸方向平移,它可以讓3D空間由前往后運作。假設自己作為觀察者,觀察着電腦屏幕上的某個元素,translateZ函數的正向值(越來越大的值)令元素更靠近觀察者,負向值則遠離觀察者。
rotate函數可以在特定軸向上旋轉元素。它的效果不同於你的直覺,通過下圖可以很直觀的感受到。
可能很多人直覺中認為rotateX的效果會是rotateZ那個樣子。
transform函數的一些簡寫:
CSS
translate3d(tx,ty,tz); scale3d(sx,sy,sz); rotate3d(rx,ry,rz,angle);
專家提醒:這些foo3d()變形函數在safari瀏覽器中會觸發硬件加速效果。
翻轉卡片
需要一些基本的標簽:
HTML
<section class="container"> <div id="card"> <figure class="front">1</figure> <figure class="back">2</figure> </div> </section>
.container元素持有3D空間,#card作為一張卡片對象。卡片的每一面就是一個獨立的元素:.front和.back。將3d空間內的各個元素獨立化,可以更容易理解和應用樣式。
我們准備添加一些3D樣式:首先,對3D容器應用必要的perspective屬性,同時添加任意的高寬或位置屬性:
CSS
.container { width: 200px; height: 260px; position: relative; -webkit-perspective: 800; }
現在#card元素可以在該3D空間中進行變形了。我們給#card元素添加絕對位置屬性讓它脫離文檔的流式布局,再加上width:100%;
和height:100%
,保證該對象的transform-origin可以在.center的正中央生效。
讓我們加上CSS3的transition屬性,這樣用戶可以看到整個變形過程。
CSS
#card { width: 100%; height: 100%; position: absolute; -webkit-transform-style: preserve-3d; -webkit-transition: -webkit-transform 1s; }
.container的perspective僅僅應用在直接后代元素上,在本例中是應用在#card上。為了讓所有后代元素都繼承父元素的透視效果並在同樣的3D空間中生效,父元素需要通過transform-style:preserve-3d
來傳遞它的透視屬性。如果沒有transform-style,卡片的兩個面都會失去立體效果,並且背面的旋轉效果也會失效。
要將卡片的兩面定位到3D空間中,我們需要重置這些面元素的2D位置屬性position:absolute。
當卡片的正面朝向觀察者時,為了隱藏相反的另一面,也就是背面,我們可以使用backface-visibility:hidden。
CSS
#card figure { display: block; position: absolute; width: 100%; height: 100%; -webkit-backface-visibility: hidden; }
要翻轉.back面,我們添加基本的3D變形rotateY(180deg)
。
CSS
#card .front { background: red; } #card .back { background: blue; -webkit-transform: rotateY(180deg); }
將兩個面都設置好之后,#card需要一個相應的樣式來翻轉卡片。
CSS
#card.flipped { -webkit-transform: rotateY(180deg); }
現在我們具備了一個可用的3D對象。為了翻轉這張卡片,我們可以切換flipped類。當.flipped添加到#card上時,#card會旋轉180度,將.back面露出來。
立方體
3D卡片對象是3D變形的入門好教材,一旦熟練掌握,你可能希望創建一些真正3D對象,例如棱柱。下面我們從立方體開始。 立方體的HTML標簽和卡片類似,但是這次,我們需要六個子元素來創建立方體的六個面:
HTML
<section class="container"> <div id="cube"> <figure class="front">1</figure> <figure class="back">2</figure> <figure class="right">3</figure> <figure class="left">4</figure> <figure class="top">5</figure> <figure class="bottom">6</figure> </div> </section>
先給六個面設置基本的定位和尺寸樣式,一個疊一個放置在容器里。
CSS
.container { width: 200px; height: 200px; position: relative; -webkit-perspective: 1000; } #cube { width: 100%; height: 100%; position: absolute; -webkit-transform-style: preserve-3d; } #cube figure { width: 196px; height: 196px; display: block; position: absolute; border: 2px solid black; }
對於卡片,我們只需要翻轉它的背面。對於立方體,需要翻轉六個面中的五個。我們稱第一面和第二面為前面和后面,第三第四面為側面,第五第六面為頂面和底面。
CSS
#cube .front { -webkit-transform: rotateY(0deg); } #cube .back { -webkit-transform: rotateX(180deg); } #cube .right { -webkit-transform: rotateY(90deg); } #cube .left { -webkit-transform: rotateY(-90deg); } #cube .top { -webkit-transform: rotateX(90deg); } #cube .bottom { -webkit-transform: rotateX(-90deg); }
現在每個面都旋轉好了,並且只能看到正面。有四個面是垂直於觀察者的,所以他們完全不可見。然后要使translate函數將他們從中心位置推到正確的邊上。立方體的每個邊長是200像素,從中心到邊緣,每個邊需要平移100像素。
CSS
#cube .front { -webkit-transform: rotateY(0deg) translateZ(100px); } #cube .back { -webkit-transform: rotateX(180deg) translateZ(100px); } #cube .right { -webkit-transform: rotateY(90deg) translateZ(100px); } #cube .left { -webkit-transform: rotateY(-90deg) translateZ(100px); } #cube .top { -webkit-transform: rotateX(90deg) translateZ(100px); } #cube .bottom { -webkit-transform: rotateX(-90deg) translateZ(100px); }
注意這里的translateZ函數緊接在rotate之后。順序對於變形函數來說是很重要的,請花一些時間消化這句話。每一個面要先旋轉到正確的朝向,然后沿着各自的朝向向外平移。
現在我們的立方體看起來能用了,但還沒完成。
回到Z軸源點
對於使用者,我們的3D變形不應該失真。但是當我們將元素從Z軸源點移開之后,不論是靠近觀察者還是遠離觀察者,它都會失真。
為了讓3D變形看上去嚴謹,Safari先將元素復合,然后對其應用變形效果。也就是說,文本的抗鋸齒效果會一直保持在變形之前的狀態。
為了解決失真問題,並還原像素,我們可以將整個3D對象向后推,這樣它的正面將回到Z軸源點。
CSS
#cube { -webkit-transform: translateZ(-100px); }
轉動立方體
我們需要一個能暴露任意面的樣式。事實上我們只需要對整個立方體對象動手腳,在這里,立方體對象就是#cube。我們切換類名來應用不同的樣式,一種樣式就是一種變形,暴露不同的面。
CSS
#cube.show-front { -webkit-transform: translateZ(-100px) rotateY(0deg); } #cube.show-back { -webkit-transform: translateZ(-100px) rotateX(-180deg); } #cube.show-right { -webkit-transform: translateZ(-100px) rotateY(-90deg); } #cube.show-left { -webkit-transform: translateZ(-100px) rotateY(90deg); } #cube.show-top { -webkit-transform: translateZ(-100px) rotateX(-90deg); } #cube.show-bottom { -webkit-transform: translateZ(-100px) rotateX(90deg); }
注意這里變形函數的次序和每個面的函數次序相反,首先要把立方體推回Z軸源點,然后旋轉立方體。 完成之后,我們添加一個transition屬性來展現旋轉時的動畫效果。
CSS
#cube { -webkit-transition: -webkit-transform 1s; }
矩形棱柱
立方體很容易制作,它是規則的,我們只需要關心一個度量值。但對於一個不規則的矩形棱柱呢?讓我們嘗試做一個300像素長,200像素寬,100像素高的棱柱。
HTML標簽和#cube的一樣,但我們要把cube換成box,容器的樣式保留大部分:
CSS
.container { width: 300px; height: 200px; position: relative; -webkit-perspective: 1000; } #box { width: 100%; height: 100%; position: absolute; -webkit-transform-style: preserve-3d; }
現在定位各個面。每個面需要設置他們自己的尺寸,較小的面(左、右、頂、底)要定位到容器的正中央,這樣他們可以方便地旋轉然后置換到外側。較薄的左面和右面設置位置為left:100px;((300-100)÷2)。較寬大的頂面和底面設置位置為top:500px((200-100)÷2)。
CSS
#box figure { display: block; position: absolute; border: 2px solid black; } #box .front, #box .back { width: 296px; height: 196px; } #box .right, #box .left { width: 96px; height: 196px; left: 100px; } #box .top, #box .bottom { width: 296px; height: 96px; top: 50px; }
旋轉值可以和立方體案例中一致,但對於矩形棱柱,平移值需要一些變化。
CSS
#box .front { -webkit-transform: rotateY(0deg) translateZ(50px); } #box .back { -webkit-transform: rotateX(180deg) translateZ(50px); } #box .right { -webkit-transform: rotateY(90deg) translateZ(150px); } #box .left { -webkit-transform: rotateY(-90deg) translateZ(150px); } #box .top { -webkit-transform: rotateX(90deg) translateZ(100px); } #box .bottom { -webkit-transform: rotateX(-90deg) translateZ(100px); }
就像立方體一樣,#box需要六個樣式來暴露各個面。
CSS
#box.show-front { -webkit-transform: translateZ(-50px) rotateY(0deg); } #box.show-back { -webkit-transform: translateZ(-50px) rotateX(-180deg); } #box.show-right { -webkit-transform: translateZ(-150px) rotateY(-90deg); } #box.show-left { -webkit-transform: translateZ(-150px) rotateY(90deg); } #box.show-top { -webkit-transform: translateZ(-100px) rotateX(-90deg); } #box.show-bottom { -webkit-transform: translateZ(-100px) rotateX(90deg); }
旋轉木馬
本例的HTML標簽和矩形棱柱、立方體、卡片一樣。讓我們構建一個9個面的木馬。
HTML
<div class="container"> <div id="carousel"> <figure>1</figure> <figure>2</figure> <figure>3</figure> <figure>4</figure> <figure>5</figure> <figure>6</figure> <figure>7</figure> <figure>8</figure> <figure>9</figure> </div> </div>
現在,應用一些基本的布局樣式。讓我們用left屬性和right屬性給每個面之間添加20像素的間距。每個面的有效寬度為210像素(其中實際為186像素,兩邊各2像素邊框,邊框外各10像素空隙,一共210像素)。
CSS
.container { width: 210px; height: 140px; position: relative; -webkit-perspective: 1000; } #carousel { width: 100%; height: 100%; position: absolute; -webkit-transform-style: preserve-3d; } #carousel figure { display: block; position: absolute; width: 186px; height: 116px; left: 10px; top: 10px; border: 2px solid black; }
下一步,旋轉每個面。該旋轉木馬由9個面構成,要讓9個面圍成一圈,每個面要旋轉40度(360÷9)。
CSS
#carousel figure:nth-child(1) { -webkit-transform: rotateY(0deg); } #carousel figure:nth-child(2) { -webkit-transform: rotateY(40deg); } #carousel figure:nth-child(3) { -webkit-transform: rotateY(80deg); } #carousel figure:nth-child(4) { -webkit-transform: rotateY(120deg); } #carousel figure:nth-child(5) { -webkit-transform: rotateY(160deg); } #carousel figure:nth-child(6) { -webkit-transform: rotateY(200deg); } #carousel figure:nth-child(7) { -webkit-transform: rotateY(240deg); } #carousel figure:nth-child(8) { -webkit-transform: rotateY(280deg); } #carousel figure:nth-child(9) { -webkit-transform: rotateY(320deg); }
現在每個面都位於旋轉木馬對象的正中央,像之前制作立方體和矩形棱柱時一樣,每個面要向外推到正確的位置上。這里我們需要機選translate函數的值,看圖:
這張圖是俯瞰該旋轉木馬對象,210像素是每個面的寬,r就是translate的值,簡單的三角函數運算。
我們要將每個面向外推288像素。
CSS
#carousel figure:nth-child(1) { -webkit-transform: rotateY(0deg) translateZ(288px); } #carousel figure:nth-child(2) { -webkit-transform: rotateY(40deg) translateZ(288px); } #carousel figure:nth-child(3) { -webkit-transform: rotateY(80deg) translateZ(288px); } #carousel figure:nth-child(4) { -webkit-transform: rotateY(120deg) translateZ(288px); } #carousel figure:nth-child(5) { -webkit-transform: rotateY(160deg) translateZ(288px); } #carousel figure:nth-child(6) { -webkit-transform: rotateY(200deg) translateZ(288px); } #carousel figure:nth-child(7) { -webkit-transform: rotateY(240deg) translateZ(288px); } #carousel figure:nth-child(8) { -webkit-transform: rotateY(280deg) translateZ(288px); } #carousel figure:nth-child(9) { -webkit-transform: rotateY(320deg) translateZ(288px); }
如果我們決定改變每個面的寬度或面的數量,我們只需要寫一個JS函數,改變兩個變量來獲取正確的translateZ值。
JavaScript
var tz = Math.round( ( panelSize / 2 ) / Math.tan( ( ( Math.PI * 2 ) / numberOfPanels ) / 2 ) ); // 或簡單點 var tz = Math.round( ( panelSize / 2 ) / Math.tan( Math.PI / numberOfPanels ) );
總結經驗
即便是狹義相對論,去找一集BBC看一遍,相信你也能知道是什么東西,雖然不一定能明白背后運作的物理學定理。本文講述的是CSS3D各種變形函數基本用法,這些函數實際上是對matrix3d()函數的封裝,而matrix3d()函數則牽扯到線性代數、立體幾何、三角學等的各種知識。未來的前端開發會變成什么樣??