这篇文章不会详细介绍每个属性,它将作为一部快速指南,主要想介绍这些很棒的属性是如何帮我们呈现更面向用户的内容,如何帮我们省去抠图的。

2D Transform

transform形变属性,它可以对元素同时进行倾斜(skew)、旋转(rotate)、位移(translate)、缩放(scale)操作。

skew

skew( [, ]?)skewX()skewY()点击看效果

1
2
3
.item {
transform: skewX(25deg);
}

点击看效果

点击看效果

rotate

rotate()点击看效果

1
2
3
.item {
transform: rotate(25deg);
}

translate

translate(x, y)translateX()translateY()

1
2
3
.item {
transform: translate(-50%, -50%);
}
  1. 居中
  2. hover 效果

scale

scale( [, ]?)scaleX() 、scaleY()

1
2
3
.item {
transform: scale(1.2);
}

点击看效果

matrix

matrix方法可以将多个形变转换为一个,有点像 transform 的缩写。有很多工具可以进行matrix转换,比如:The Matrix Resolutions

1
rotate(45deg) translate(24px,25px)

上述代码可以被写为:

1
matrix(0.7071067811865475, 0.7071067811865476, -0.7071067811865476, 0.7071067811865475, -0.7071067811865497, 34.648232278140824)

3D Transform

点击看效果

translate3d

translate3d(x,y,z)translateZ(z)translate3dtranslate快,会开启硬件加速,阅读translate3d vs translate performance

scale3d

scale3d(sx,sy,sz) scaleZ(sz)

rotate3d

rotate3d(x,y,z) rotateX() rotateY() rotateZ()

matrix3d

perspective

要激活 3D 空间,元素需要透视perspective。有两种方式:

1
2
3
.panel--red {
transform: perspective(400px) rotateY(45deg);
}
1
2
3
4
5
6
.scene--blue {
perspective: 400px;
}
.panel--blue {
transform: rotateY(45deg);
}

点击看效果
两者的区别是: perspective()方法作用于单个元素,perspective属性对声明元素的所有直系子元素产生作用。

perspective的值决定了 3D 效果的强度。可以把值当做观察者到物体之间的距离。值越大,距离越大,视觉效果越不明显。

perspective-origin

3D 视角的透视点默认是中心点,通过perspective-origin可以更改透视点的位置。

1
2
3
.item {
perspective-origin: 25% 75%;
}

卡片翻转效果

HTML 结构:

1
2
3
4
5
6
<div class="scene">
<div class="card">
<div class="card__face card__face--front">frontdiv>
<div class="card__face card__face--back">backdiv>
div>
div>

.scene是 3D 空间的容器,.card作为 3D 对象,两个.card-face元素是卡片的两个面。首先,激活 3D 视角:

1
2
3
4
5
.scene {
width: 200px;
height: 260px;
perspective: 600px;
}

perspective只为直系子元素提供 3D 视角,即.card元素,为了让.card的子元素处于同样的 3D 视角中,可设置transform-style: preserve-3d使得子元素继承父元素的视角:

1
2
3
4
5
6
7
.card {
width: 100%;
height: 100%;
position: relative;
transition: transform 1s;
transform-style: preserve-3d;
}

定位两个面,通过设置backface-visibility: hidden,当面对观察者时,可将背面隐藏:

1
2
3
4
5
6
.card__face {
position: absolute;
height: 100%;
width: 100%;
backface-visibility: hidden;
}

.card__face--back元素放置到背后:

1
2
3
4
5
6
7
8
.card__face--front {
background: red;
}

.card__face--back {
background: blue;
transform: rotateY(180deg);
}

最后,使 3D 对象.card翻转:

1
2
3
4
5
.card {
&:hover {
transform: rotateY(180deg);
}
}

以上,我们实现了一个以中心点进行翻转的效果。如果想实现从右侧进行滑动翻转的效果改怎么实现呢?

1
2
3
4
5
6
.card {
transform-origin: center right;
}
.card:hover {
transform: translateX(-100%) rotateY(-180deg);
}

通过transform-origin更改变化的原点,translateX进行移动来实现。点击看效果

transform-origin

设置元素在形变时的基点,比如旋转时的选择中心。水平和垂直方向的偏移量以形变元素的左上角为基点。

1
transform-origin: bottom right 60px; // x y z

transform-style

设置元素的子元素是位于 3D 空间还是位于 2D 平面空间被扁平化,有三种取值:

  1. flat
  2. preserve-3d
  3. inherit

will-change

在卡片翻转效果的例子中,动画有时会出现卡顿的现象。可以使用will-change进行优化。will-change为 web 开发者提供了一种告知浏览器该元素会有哪些变化的方法,这样浏览器可以在元素属性真正发生变化之前提前做好对应的优化准备工作,有四种取值:

  1. auto
  2. scroll-position
  3. contents
  4. , 如:transform ,opacity
1
2
3
4
5
6
7
8
9
.scene:hover .card {
will-change: transform;
}
.card {
transform-origin: center right;
&:hover {
transform: translate3d(-100%, 0, 0) rotateY(-180deg);
}
}

点击看效果

过渡使用will-change会消耗很多机器资源,可能导致页面响应缓慢。所以我们在鼠标进入.scene时再给.card设置will-change: transform,减少浏览器资源消耗。同时使用translate3d代替translateX

backface-visibility

在上面的例子中,我们为.card__face卡片元素添加了backface-visibility: hidden属性,顾名思义,“背面可见性”。默认情况下,元素的背面也是可以看到的,当翻卡片时,背面隐藏,才更符合我们想要实现的效果。

1
2
backface-visibility: visible
backface-visibility: hidden

立方体

HTML 结构:

1
2
3
4
5
6
7
8
9
10
<div class="scene">
<div class="cube">
<div class="cube__face cube__face--front">frontdiv>
<div class="cube__face cube__face--back">backdiv>
<div class="cube__face cube__face--right">rightdiv>
<div class="cube__face cube__face--left">leftdiv>
<div class="cube__face cube__face--top">topdiv>
<div class="cube__face cube__face--bottom">bottomdiv>
div>
div>

设置立方体的 6 个面.cube__face,3D 对象.cube,3D 场景.scene

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.scene {
width: 200px;
height: 200px;
perspective: 600px;
}

.cube {
width: 100%;
height: 100%;
position: relative;
transform-style: preserve-3d;
}

.cube__face {
position: absolute;
width: 200px;
height: 200px;
}

现在,因为绝对定位,所有的面重叠在一起,我们通过rotate将每个面旋转到对应的位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.cube__face--front {
transform: rotateY(0deg);
}
.cube__face--right {
transform: rotateY(90deg);
}
.cube__face--back {
transform: rotateY(180deg);
}
.cube__face--left {
transform: rotateY(-90deg);
}
.cube__face--top {
transform: rotateX(90deg);
}
.cube__face--bottom {
transform: rotateX(-90deg);
}

旋转完成后,frontback面可见,其余面由于垂直于观察者,在屏幕上呈现出两条相互垂直的线,下面的动图可以展示翻转的过程:

需要注意的是,立方体的每个面旋转后,其坐标系跟着一起旋转,即坐标系旋转。

现在把每个面从中心位置,向外移动100px(立方体边长200px,旋转后每个面位于中心位置,距离立方体边100px)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.cube__face--front {
transform: rotateY(0deg) translateZ(100px);
}
.cube__face--right {
transform: rotateY(90deg) translateZ(100px);
}
.cube__face--back {
transform: rotateY(180deg) translateZ(100px);
}
.cube__face--left {
transform: rotateY(-90deg) translateZ(100px);
}
.cube__face--top {
transform: rotateX(90deg) translateZ(100px);
}
.cube__face--bottom {
transform: rotateX(-90deg) translateZ(100px);
}

注意,这里需要先旋转,后平移,因为旋转改变坐标系,每个面沿着单独的方向平移。

目前为止,看一下我们的立方体的front面:

发现front文字变模糊了,3D 变换会影响文本渲染。我们可将 3D 对象.cube推回去100px,使得front面回到 Z 轴原点,来解决这个问题:

1
2
3
.cube {
transform: translateZ(-100px);
}

接下来,通过为.cube添加不同的类来旋转我们的立方体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.cube.show-front {
transform: translateZ(-100px) rotateY(0deg);
}
.cube.show-right {
transform: translateZ(-100px) rotateY(-90deg);
}
.cube.show-back {
transform: translateZ(-100px) rotateY(-180deg);
}
.cube.show-left {
transform: translateZ(-100px) rotateY(90deg);
}
.cube.show-top {
transform: translateZ(-100px) rotateX(-90deg);
}
.cube.show-bottom {
transform: translateZ(-100px) rotateX(90deg);
}

注意这里的translateZ(-100px)是为了解决文字模糊的问题。点击看效果

长方体

下面实现一个长300px,宽100px,高200px的长方体。

HTML 结构:

1
2
3
4
5
6
7
8
9
10
<div class="scene">
<div class="box">
<div class="box__face box__face--front">frontdiv>
<div class="box__face box__face--back">backdiv>
<div class="box__face box__face--right">rightdiv>
<div class="box__face box__face--left">leftdiv>
<div class="box__face box__face--top">topdiv>
<div class="box__face box__face--bottom">bottomdiv>
div>
div>

定义长方体的长、高以及六个面的宽高:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
.scene {
width: 300px;
height: 200px;
perspective: 500px;
}

.box {
width: 300px;
height: 200px;
position: relative;
transform-style: preserve-3d;
transform: translateZ(-50px);
}

.box__face--front,
.box__face--back {
width: 300px;
height: 200px;
}

.box__face--right,
.box__face--left {
width: 100px;
height: 200px;
}

.box__face--top,
.box__face--bottom {
width: 300px;
height: 100px;
}

position: absolute绝对定位.box__face元素后,每个面重叠于左上角。

为了可以在透视原点perspective-origin翻转每个面,我们需要将左面右面、上面下面移动到透视原点:

1
2
3
4
5
6
7
8
9
.box__face--right,
.box__face--left {
left: 100px;
}

.box__face--top,
.box__face--bottom {
top: 50px;
}

和立方体一样,现在可以rotate了,但是对于长方体而言,平移translateZ的距离是不相同的,上面和下面平移的距离是高的一半100px,左面和右面的平移距离是长的一半150px,前面和后面的平移距离是宽的一半50px

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.box.show-front {
transform: translateZ(-50px) rotateY(0deg);
}
.box.show-back {
transform: translateZ(-50px) rotateY(-180deg);
}
.box.show-right {
transform: translateZ(-150px) rotateY(-90deg);
}
.box.show-left {
transform: translateZ(-150px) rotateY(90deg);
}
.box.show-top {
transform: translateZ(-100px) rotateX(-90deg);
}
.box.show-bottom {
transform: translateZ(-100px) rotateX(90deg);
}

观察者面对的面是长方体的正面,正面平移了50px,所以 3D 对象.box将向后平移50px解决上面提到的文字模糊问题。点击看效果

旋转木马效果

HTML 结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
<div class="scene">
<div class="carousel">
<div class="carousel__cell">1div>
<div class="carousel__cell">2div>
<div class="carousel__cell">3div>
<div class="carousel__cell">4div>
<div class="carousel__cell">5div>
<div class="carousel__cell">6div>
<div class="carousel__cell">7div>
<div class="carousel__cell">8div>
<div class="carousel__cell">9div>
div>
div>

场景.scene宽高设置为201 * 140,每个面的宽高设置为190 * 120,同时为每个面设置top: 10px; left: 10px使其在场景中居中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.scene {
width: 210px;
height: 140px;
position: relative;
perspective: 1000px;
}

.carousel {
width: 100%;
height: 100%;
position: absolute;
transform-style: preserve-3d;
}

.carousel__cell {
position: absolute;
width: 190px;
height: 120px;
left: 10px;
top: 10px;
}

接下来,准备旋转 9 个面,每个面应以 40°(360° / 9)为间隔进行旋转:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
.carousel__cell:nth-child(1) {
transform: rotateY(0deg);
}
.carousel__cell:nth-child(2) {
transform: rotateY(40deg);
}
.carousel__cell:nth-child(3) {
transform: rotateY(80deg);
}
.carousel__cell:nth-child(4) {
transform: rotateY(120deg);
}
.carousel__cell:nth-child(5) {
transform: rotateY(160deg);
}
.carousel__cell:nth-child(6) {
transform: rotateY(200deg);
}
.carousel__cell:nth-child(7) {
transform: rotateY(240deg);
}
.carousel__cell:nth-child(8) {
transform: rotateY(280deg);
}
.carousel__cell:nth-child(9) {
transform: rotateY(320deg);
}

旋转完的效果如上图,接下来要做的与立方体、长方体时的步骤一致,对每个面进行平移translate,上面两个例子的平移量很容易计算,不是宽的一半,就是高的一半或者是长的一半,那么旋转木马的平移量又该怎么计算呢?

先看一看旋转木马的俯视图:

旋转木马的俯视图是一个 9 边形,边长等于场景.scene的宽210pxr是中点到边长的距离,即我们要平移的距离,根据三角函数计算可得:

r的值应为288px

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
.carousel__cell:nth-child(1) {
transform: rotateY(0deg) translateZ(288px);
}
.carousel__cell:nth-child(2) {
transform: rotateY(40deg) translateZ(288px);
}
.carousel__cell:nth-child(3) {
transform: rotateY(80deg) translateZ(288px);
}
.carousel__cell:nth-child(4) {
transform: rotateY(120deg) translateZ(288px);
}
.carousel__cell:nth-child(5) {
transform: rotateY(160deg) translateZ(288px);
}
.carousel__cell:nth-child(6) {
transform: rotateY(200deg) translateZ(288px);
}
.carousel__cell:nth-child(7) {
transform: rotateY(240deg) translateZ(288px);
}
.carousel__cell:nth-child(8) {
transform: rotateY(280deg) translateZ(288px);
}
.carousel__cell:nth-child(9) {
transform: rotateY(320deg) translateZ(288px);
}

当我们想改变旋转木马每个面的宽度或者面的个数时,可以使用下面的JS公式结算平移的距离:

1
2
3
var r = Math.round((cellSize / 2) / Math.tan(((Math.PI * 2) / numberOfCells )/2)
// 约分
var r = Math.round((cellSize / 2) / Math.tan (Math.PI / numberOfCells )

最后,通过为 3D 对象.carousel设置rotateY的值,就可以使“木马”旋转起来了。点击看效果

参考文献

  1. CSS-TRICKS transform
  2. MDN transform
  3. Intro to CSS 3D transforms
  4. MDN will-change
  5. CSS-TRICKS will-change
  6. MDN backface-visibility
  7. CSS3 的 3D 立方体旋转动画
  8. MDN transform-origin
  9. Typography Effects with CSS3 and jQuery