CSS3 3D笨蛋教程


英文原文An Introduction to CSS 3-D Transforms

愛因斯坦說所有概念都必須介紹給兒童們,若他們無法了解,這些理論就毫無價值。

透視

一個元素需要一個透視點才能激活3D空間,有兩種方法可以得到透視點:

  1. 使用transform屬性,賦上perspective函數作為值。-webkit-transform: perspective(600);
  2. 或使用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()函數則牽扯到線性代數、立體幾何、三角學等的各種知識。未來的前端開發會變成什么樣??


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM