使用css3的3d transform,就能夠在平面的網頁裏添加炫酷的三維視覺效果,這很使人愉悅。css
須要注意的是,3d transform只是css的一部分,它並非一個三維引擎(3d engine)。三維引擎通常是這樣的(遊戲引擎Unity3D):html
包括JavaScript 3D庫three.js在內,簡單來講,它們這些能夠稱爲三維引擎的,都會包括:css3
獨立的三維座標系統。web
幾何圖形和材質貼圖。瀏覽器
光照和攝像機。函數
3d transform和它們相比起來,是缺乏這些內容的。畢竟,css的關注點是網頁樣式,而不是建立虛擬空間。工具
儘管3d transform的三維空間能力有所不足,但它仍然能夠建立出很棒的三維效果。這就須要咱們開發者來用心了。佈局
在下面的內容開始以前,若是你還對perspective
等3d transform相關的css屬性徹底不瞭解,能夠閱讀我之前寫的css三維變換的文章。post
咱們很熟悉的網頁是平面的,一個DOM元素,好比一個<div>
,它會有一個初始座標系(initial coordinate system):動畫
每個DOM元素都有一個這樣的初始座標系。其中,原點位於元素的左上角,z軸指向觀察者(也就是屏幕外的咱們)。初始座標系的z軸並不算是三維空間,而是像z-index
那樣做爲參照,決定網頁元素的繪製順序,繪製順序靠後的元素將覆蓋繪製順序靠前的。
在使用transform的時候,狀況則有所不一樣。transform所參照的並非初始座標系,而是一個新的座標系:
transform所用的這個座標系,相比初始座標系,x、y、z軸的指向都不變,只是原點位置移動到了元素的正中心。若是想要改變這個座標系的原點位置,使用transform-origin
。transform-origin
的默認值是50% 50%
,所以,默認狀況下,transform座標系的原點位於元素中心。
咱們均可能像transform: rotateY(45deg) translateX(100px);
這樣使用多個變換函數。這種時候,須要意識到變換函數的順序。這是由於,每個變換函數不只改變了元素,同時也會改變和元素關聯的transform座標系,當變換函數依次執行時,後一個變換函數老是基於前一個變換後的新transform座標系。
例如,下面一個包含兩個變換函數的transform的效果(gif):
若是交換這兩個變換函數的順序,是這樣的效果:
能夠看到,因爲座標系會隨着每一次變換髮生改變,所以不一樣順序的狀況下,元素最終的位置也不一樣。
對此還有一種解釋,即變換函數是經過數學上的矩陣乘法運算完成的,而矩陣的乘法是不知足交換律的。任意座標空間內的變換函數或者變換函數的組合,均可以轉換爲一個矩陣(還有一個矩陣小工具能夠幫你作這個轉換)。
前面已經提到,3d transform並無像三維引擎那樣爲你建立三維場景提供全面的資源。所以,就以建立一個三維物體來講,咱們只能利用網頁目前已有的內容,本身想辦法。
在網頁裏,你並不能直接定義一系列座標爲(x, y, z)
的空間中的點,而後基於這些點來生成三維圖形。網頁裏有的,是平面圖形。不論是<div>
仍是其餘html元素,它們都是平的,沒有厚度,像紙片同樣。但紙片就能夠搭東西,因此,一個DOM元素用做三維物體的一個「面」,把這些「面」有序地組織起來,獲得的就是三維物體了!
事實上,在三維引擎裏,三維物體也不是實體,它們都是由一系列平面(多邊形)所圍成的(並能夠在平面上添加紋理和貼圖)。
如今來作一個正方體,如今先不用考慮perspective。正方體有六個面,而後須要用一個元素來裝這六個面,因此html是:
<div class="cube"> <div class="surface surface-1">1</div> <div class="surface surface-2">2</div> <div class="surface surface-3">3</div> <div class="surface surface-4">4</div> <div class="surface surface-5">5</div> <div class="surface surface-6">6</div> </div>
對應的css是(邊長120px,省略瀏覽器私有前綴,後文同):
.cube{ position: absolute; transform-style: preserve-3d; } .cube .surface{ position: absolute; width: 120px; height: 120px; border: 1px solid #ccc; background: rgba(255,255,255,0.8); box-shadow: inset 0 0 20px rgba(0,0,0,0.2); line-height: 120px; text-align: center; color: #333; font-size: 100px; } .cube .surface-1 { transform: translateZ(60px); } .cube .surface-2 { transform: rotateY(90deg) translateZ(60px); } .cube .surface-3 { transform: rotateX(90deg) translateZ(60px); } .cube .surface-4 { transform: rotateY(180deg) translateZ(60px); } .cube .surface-5 { transform: rotateY(-90deg) translateZ(60px); } .cube .surface-6 { transform: rotateX(-90deg) translateZ(60px); }
其中,transform-style: preserve-3d;
保證全部子元素都處於同一個三維空間(這裏是三維渲染上下文3D rendering context)內,也就是告訴瀏覽器你是想用這些元素作一個三維場景,而不只僅只是要單個元素的簡單三維效果。
position: absolute;
是一個習慣作法,由於三維物體並不符合通常平面網頁內容的排版,因此咱們會比較多地但願它不要佔據佈局空間。
6個面位置都不同,但卻都有translateZ(60px);
,你已經知道這是由於巧妙搭配了在它以前的變換函數。
一旦構成正方體的6個div.surface
的位置肯定後,就能夠操做它們的父元素div.cube
來總體移動、旋轉這個正方體。
只是有了這樣的一個三維物體,咱們就能夠說有了一個三維場景了。可是,三維場景和平面圖不一樣,好比這個正方體,我從不一樣的位置,不一樣的角度去觀察它,我眼裏看到的都是不同的:
那麼,這樣的三維場景要如何呈如今平面的網頁裏呢?
這就是三維引擎裏的攝像機的概念來源了。就像電影裏表現同一個場景會用不一樣的鏡頭那樣,咱們須要定義場景裏的攝像機來生成最終呈如今屏幕裏的二維畫面。好比three.js裏的攝像機是這樣的感受:
這個圖裏標明的是攝像機定義時使用的參數,其中包括視野範圍,圖像寬高比等。能夠感覺到仍是有不少內容的。
相比之下,網頁裏的三維場景攝像機就弱多了,你須要用的是perspective
和perspective-origin
。
perspective
定義攝像機(也就是做爲觀衆的咱們)到屏幕的距離,perspective-origin
定義攝像機觀察到的畫面中的滅點(vanishing point)的位置。雖然它們並不能方便地讓你直接定義攝像機的位置和觀察角度等,但只要適當地應用它們,是能夠必定程度上控制攝像機的畫面效果的。
網頁裏的攝像機通常是這樣用的:
<div class="camera"> <div class="cube1"></div> <div class="cube2"></div> <!-- more 3d objects... --> </div>
.camera{ position: relative; perspective: 1200px; perspective-origin: 50% 50%; transform-style: preserve-3d; }
在網頁裏,不管你搭建了怎樣的三維場景,只要你但願它顯示出來,就應該像這樣把構成場景的三維物體都放在一個容器元素裏,而後爲容器元素添加攝像機屬性(perspective
和perspective-origin
)。
此外,還須要注意添加transform-style: preserve-3d;
以保證多個三維物體都位於同一空間(這樣纔有三維引擎的味道,對吧?)。
下面這個場景裏有三個正方體,而後攝影師正在作彈跳練習(限支持3d transform的瀏覽器):
http://runjs.cn/detail/daqoq5tf
這段動做的動畫代碼是這樣:
.camera{ animation: cameraMove 2s ease-out infinite alternate both; } @keyframes cameraMove{ 0%{ perspective-origin: 50% 180px; } 100%{ perspective-origin: 50% -200px; } }
能夠看出,perspective-origin
雖然是指三維透視的滅點的位置,但它的確和咱們理解的攝像機的位置是緊密關聯的。若是攝像機在空間裏的位置是(x, y, z)
的話,perspective-origin
的兩個值有一點像指定x
和y
的感受。這裏只說「有一點像」,是由於滅點位置和攝像機的位置畢竟是不一樣的概念,這可能還須要多看一些三維空間來體會。
那麼,在上面的例子中,攝影師不僅是這樣跳起來,而是想要向更深處前進,應該怎麼作呢?
答案是,在網頁裏,你不能這樣移動攝像機,你須要換一個思路,參照相對運動的關係,改成讓整個三維場景向你移動。不過,說到這裏,前面提到的攝像機的另外一個屬性,perspective
,爲何它不行呢?
perspective
表明攝像機距離屏幕的距離,看上去和z軸深度很是近似。可是,它並不等同於攝像機的z
座標位置(perspective
還只能取正值),而是會影響攝像機自己的其餘屬性。下面用這個圖說明perspective
的值變化的效果(修改自w3c的配圖):
圖中d1
和d2
分別表示兩個不一樣的perspective
的值,其中d2
小於d1
。而後,你會驚奇地發現,一個本來位於屏幕以後(z
座標爲負值)的物體,居然是隨着「走近」而變得更小了!顯然,這不符合咱們在三維空間裏運動的基本感覺。其緣由是,網頁的三維投影平面是固定的,perspective
在改變攝像機的位置的同時,也同時改變了攝像機自己的其餘屬性(相似前面的three.js的攝像機那張圖裏的各類參數)。
因此,通常來講,perspective
應維持一個固定的值。想要用3d transform作出在三維空間裏自由移動的效果(就像各類3d遊戲),應該經過相對運動的方法實現。
transform影響的是視覺渲染,而不是佈局。所以,除如下狀況外,transform不會影響到佈局:
這個由於overflow
生成滾動條從而影響佈局的反例,也發生於position: relative;
再進行偏移的狀況。
相對於transform的translate3d()
這類改變空間位置的變換函數,原來css裏就有的定位屬性left
、top
彷佛會讓狀況變得很複雜。
對此,有一個比較推薦的分析方式:就三維空間的位置而言,常規屬性left
、top
,甚至margin-left
等,是先生效的,它們的效果其實只有一個,就是改變元素的初始位置,從而改變元素的transform-origin
的那個原點位置,而後三維空間的transform是後生效的,它會再基於前面的transform-origin
繼續改變位置。
如今你已經瞭解到,perspective-origin
是一個攝像機的屬性,定義的是透視畫面的滅點,而transform-origin
是任意元素都有的,定義的是的元素的transform座標系的原點。
本文是我對目前網頁裏用3d transform建立三維空間的總結,其中混入了一些三維引擎的知識,並對比着來一一說明。感受3d transform仍是有本身比較明確的定位的,雖然和三維引擎相比很簡陋,但它已經足夠用來爲網頁添加吸引人的三維效果。
若是你也想過用3d transform作稍複雜的三維空間,但願本文能有所幫助。
(從新編輯自個人博客,原文地址:http://acgtofe.com/posts/2015/12/xyz-3d-in-css)