CSS3 3D笨蛋教程 - 西瓜刀

爱因斯坦说 所有概念都必须介绍给儿童们,若他们无法了解,这些理论就毫无价值。

透视

一个元素需要一个透视点才能激活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 ) );

总结经验

即便是狭义相对论,去找一集BBS看一遍,相信你也能知道是什么东西,虽然不一定能明白背后运作的物理学定理。本文讲述的是CSS3D各种变形函数基本用法,这些函数实际上是对matrix3d()函数的封装,而matrix3d()函数则牵扯到线性代数、立体几何、三角学等的各种知识。未来的前端开发会变成什么样??

我来评几句
登录后评论

已发表评论数()