学会使用CSS3 transform 变形

7,749 阅读20分钟

第一部分:变形介绍

自层叠样式表诞生以来,元素始终是矩形的,而且只能沿着横轴和纵轴放置。有些技巧能让元素看起来是倾斜的,但是底层的坐标方格并没有变形。CSS变形功能改变了这一现状,能以不同的方式改变对象的形态,而且还不只限于二维。

不论你想要旋转照片,翻转元素等功能使用CSS的变形功能都能做到,CSS变形功能分为2D和3D变形。

CSS变形功能包括:

  • 移动(translate)
  • 缩放(scale)
  • 旋转(rotate)
  • 倾斜(skew)

第二部分:坐标系

学习变形之前,先需要了解一下变形中的坐标系。在CSS中,笛卡尔坐标系使用三个轴表示(如下图):

  • x轴(横轴)
  • y轴(纵轴)
  • z轴(深度轴)

变形功能表示笛卡尔坐标系的三个轴

2D变形只需要关注x轴和y轴。

x轴的正值在右侧,负值在左侧。y轴的正值沿纵轴向下,负值沿纵轴向上。回想一下定位元素的top属性:值为正数时元素下移,值为负数时元素上移。

如果想把元素向左下方移动,要把x值设为负数,把y值设为正数:

 transform: translateX(-5em) translateY(50px);

上面这个变形是有效的。上面代码的作用是把元素向左移动5em,向下移动50像素。

3D变形不仅需要关注x轴和y轴,还要加上z轴。

z轴从显示器上跃出,指向你的眼前。z轴上的正值离你较近,负值离你较远。定位元素的z-index属性是和它完全一样的。

在上面的基础上再加个z轴值:

 transform: translateX(-5em) translateY(50px) translateZ(200px);

与未指定z值相比,元素离我们的距离近了200像素。现在还看不到效果,等介绍到3D变形的时候就会感受到变化了。

2.1 移动参照物

每个元素都有自己的参照物,各轴都相对自身而动。如果旋转了元素,轴也随之旋转。旋转之后再变形,是相对旋转后的轴计算的,而不是显示器的轴。如下图:

CSS变形功能中使用的另一个坐标系——球坐标系。这个坐标系用于描述3D空间中的角度。如下图:

在2D变形中只需要关注全周360度坐标系,即由x轴和y轴构成的屏幕。对旋转来说,2D旋转其实是在绕z轴旋转。类似的,如果绕x轴旋转,元素将偏向我们或远离我们,而绕y轴旋转的话,元素将向两侧旋转,如下图(需要仔细理解):

  • 绕x轴旋转

  • 绕y轴旋转

  • 绕z轴旋转

在2D旋转中,假设我们要在显示器屏幕上(即绕z轴)顺时针旋转一个元素45度,使用的变形值可以是下面这样的:

transform: rotate(45deg);

如果把值改为-45deg,元素将绕z轴轴逆时针旋转。换句话说,元素在xy平面上旋转。如下图:

第三部分:变形语法和注意事项

变形其实只有一个属性,但是有几个辅助属性用于控制如何变形。Transform使用如下表格:

Transform
取值`none `
初始值None
适用于除 “基元行内”框之外的所有元素
百分数相对范围框计算
计算值指定的值,不过相对长度值会计算为绝对长度
继承性
动画性变形
说明:
  1. 范围框:元素边框的外边界。也就是说,计算范围框时,轮廓和外边距不算在内。

  2. 变形的元素有自己的堆叠上下文。经过缩放的元素可能比变形前小或大,不过元素在页面上所占的空间与变形前保持不变。对所有变形函数都成立。

  3. <transform-list> | none 表示一个或多个变形函数,中间以空格分隔。如:transform: rotate(30deg) skewX(-25deg) scaleY(2);

  4. 变形函数一次只处理一个,从最左边第一个开始,一直到最后一个。从头到尾的处理顺序很重要。顺序变了,得到的结果就有可能会不同。

  5. 有多个变形函数时,每个都要设置正确,要确保全部有效。如果有一个函数是无效的,整个值都将失效。

  6. 变形通常不叠加。如果改变了元素的形态,而后再想添加一种变形,那么要在原变形基础上修改。就像你在一个地方声明了字号,又在另一个地方为元素声明了不同的字号一样,字号是不叠加的。只有其中一个字号起作用。不过动画变形除外,不管使用过渡还是真正的动画,效果是叠加的。

  7. 变形现在还不能应用到基元行内框上。基元行内框指span,超链接等行内框。这些元素可以随着块级父元素一起变形,但是不能直接旋转。除非把它们的显示方式改为display:block,display:inline-block等。

transform: translateX(200px) rotate(45deg);
transform: rotate(45deg) translateX(200px);

不同顺序,不同结果

div {transform: translateX(100px) scale(1.2)};
div:hover {transform:rotate(-45deg)};

即使不显式使用过渡或动画,也能通过用户交互伪类(如:hover)实现叠加变形。这是因为悬停等效果就是一种过渡,只不过不是由过渡属性触发的。因此可以向上面这样声明变形属性进行效果叠加。

变形函数

变形函数目前一共有21个。不同的变形函数利用不同格式的值实现相应的变形。

  1. translate()
  2. translate3d()
  3. translateX()
  4. translateY()
  5. translateZ()
  6. scale()
  7. scale3d()
  8. scaleX()
  9. scaleY()
  10. scaleZ()
  11. rotate()
  12. rotate3d()
  13. rotateX()
  14. rotateY()
  15. rotateZ()
  16. skew()
  17. skewX()
  18. skewY()
  19. matrix()
  20. matrix3d()
  21. perspective()

具体语法可以参考这篇文章。非常详细的介绍了每个函数的具体用法。

第四部分:平移函数 translate()

平移函数沿一个轴或多个轴移动。

  • translateX()沿元素自身的x轴移动元素。
  • translateY()沿元素自身的y轴移动元素。
  • translateZ()沿元素自身的z轴移动元素。
函数可取值
translateX(),translateY()` `
translateZ()<length>
translate()`[][,]?`
translate3d()`[][],[]`
4.1 translateX(),translateY()

translateX(),translateY()这两个通常成为“2D”平移函数,因为它们能上下移动元素,也能左右移动元素。但是不能沿着z轴前后移动元素。这两个函数的值都是一个距离值,可以是长度,也可以是百分比。

translateX(200px)  <!--元素向右移动200像素-->
translateY(200px)  <!--元素向下移动200像素-->

如果值是百分数,移动距离相对元素自身的尺寸计算。

translateX(50%)  <!--元素向右移动自身尺寸的一半-->
4.2 translate()

如果想同时沿x轴和y轴移动,使用 translate()更方便。第一个值代表x轴,第二个值代表y轴。translate()translateX(),translateY()结合在一起的作用是一样的。

translate(2em) 等于translate(2em,0) 等于 translateX(2em)

平移例子

4.3 translateZ()

translateZ()沿z轴平移元素,即在第三个维度中移动元素。translateZ()只接受长度值。其实任何有关z轴的值都不可以使用百分数。

4.4 translate3d()

translate3d()这个简写属性能同时指定x轴,y轴,z轴的平移量。3D平移过程如下图:

3D平移过程 图中箭头表示沿相应轴的移动,最终达到3D空间中的一点。与translate()不同,如果translate3d()的值少于3个,没有假定的默认值。因此类似translate3d(1em,-50px)操作是无效的,而不能假定为translate3d(1em,-50px,0)

第五部分:缩放函数 scale()

缩放变形可以把元素放大或缩小。缩放函数的值都是无单位的实数,而且始终为正数。在2D平面中,可以分别在x轴或者y轴上缩放,也可以同时在两个轴上缩放。

函数可取值
scaleX(),scaleY(),scaleZ()<number>
scale()<number>[,<number>]?
scale3d()<number>,<number>,<number>

提供给缩放函数的数字是个乘数,因此,scaleX(2)将把元素的宽度变为变形前的两倍,而scaleY(0.5)将把元素的高度缩小一半。如果想在两个轴上同时缩放,使用scale()。第一个值是x值,第二个是y值。因此scale(2,0.5)将把元素的宽度放大两倍,将元素的高度缩小一半。如果只提供一个值,将用作两个轴的缩放值。因此,scale(2)将把元素的宽度和高度都放大两倍。这与translate()是不同的,在translate()中,省略的第二个值始终被设为0。下面图片展示了使用scale()的例子:

5.1 三维空间缩放

能在二维空间缩放,也就能在三维空间缩放。css提供的scalez()函数仅在z轴上缩放,而scale3d()则能同时在三个轴上缩放。当然,仅当元素有深度时,这两个函数才有效果,而元素在默认情况下没有深度。如果让元素有一定的深度,例如绕x轴或y轴旋转,那么深度就可以缩放。使用scalez()或者scale3d()都可以。

translate()一样,scale3d()的三个数都必须是有效的。不然将导致所属的整个变形值都失效。

第六部分:旋转函数 rotate()

旋转函数绕某个旋转轴元素,或者绕3D空间中的一个向量旋转元素。旋转变形有四个简单的函数,以及一个有些复杂,专门用于3D旋转的函数。

函数可取值
rotate(),rotateX(),rotateY(),rotateZ()<angle>

上面四个简单的函数都只接受一个值:角度。角度以一个数字(可正可负)和一个有效的角度单位(deg,grad,rad和turn)来表示。如果数字超出来相应单位的常规范围,将化为范围内的值。也就是说,437deg和77deg的效果是相同的,与-283deg的效果也是相同的。

但是仅当没有任何形式的动画,这样的换算才算等效的。如果以动画的形式旋转-20deg,元素稍微向左倾斜,而不转动。如果以动画的形式旋转340deg,元素将向右转动几乎一周。这两次动画的最终状态是一样的,但是每次旋转的过程是有差异的。

rotate()函数实施的是2D旋转,是我们最常用的旋转方式。它的效果等同于rotateZ(),因为都是绕z轴旋转(从显示器射出来,直指你的眼睛)旋转的。类似的,rotateX()绕x轴旋转,使元素向你倾斜或者远离你倾斜。rotateY()绕y轴旋转元素,像门的开合一样。如下图效果:

上图中有的例子涉及到3D效果。与后面章节的transform-styleperspective属性配和才能达到的效果。这篇文章涉及的3D变形都是如此。如果只应用现在提到的变形函数是得不到图中所展示的效果的。

6.1 rotate3d()
函数可取值
rotate3d()<number>,<number>,<number>,<angle>

如果你了解向量,可以把向量理解为坐标空间中的箭头。如下图分别代表2维坐标和3维坐标的向量:

2维坐标的向量

3D坐标的向量

想在3D空间中旋转元素,使用rotate3d()。前三个值指定3D空间中向量x,y和z的分量,第四个值是角度值,指定绕向量旋转的量。

看个简单的例子:rotate(45deg)用3D旋转表示是 rotate3d(0,0,1,45deg)。这个向量在x轴和y轴上的大小是0,在z轴上的大小是1。也就是说,旋转中心是z轴。元素将绕指定的向量旋转45度。如下图(包括绕x轴和y轴旋转45度时应该提供给rotate3d()的函数的值):

上图中rotate(45deg)在3D空间中,等价于rotate3d(0,0,1,45deg)。前三个数设定向量的三个分量,这个向量在x轴和y轴上的大小为0。在z轴上的大小为1。因此,这个向量在z轴上,指向正方向,即指向观察者。如果看向量的原点,元素是顺时针旋转的。

类似的,rotateX(45deg)等价于rotate3d(1,0,0,45deg)。这个向量在x轴上,指向正方向(右方)。如果站在向量的的终点看向原点,元素绕向量顺时针旋转45度,因此,站在常规的观察者位置上,元素的顶部远离观察者,元素的底部靠近观察者。

假如说旋转函数是rotate3d(-0.95,0.5,1,45deg)描述的向量。假设有个边长200像素的立方体,那么这个向量在x轴上的大小为190像素,指向左方。在y轴上的大小为100像素,指向下方。在z轴上的大小为200像素,指向观察者。这个向量从原点(0,0,0)指向(-190px,100px,200px)。如下图:

这个向量就像一根金属棒,穿过旋转的元素。如果沿着向量向回看,元素顺时针旋转了45度。但是,由于向量指向左下前方,因此旋转后的元素左上角靠近观察着,而右下角远离观察者。

rotate3d(1,1,0,45deg)rotateX(45deg) rotateY(45deg) rotateZ(0deg)不等效。

第七部分:倾斜函数 skew()

函数可取值
skewX(),skewY()<angle>
skew()<angle>,[,<angle>]?

倾斜函数沿x轴或y轴倾斜元素。元素不能沿z轴或3D空间中的向量倾斜。

skewX(),skewY()使元素倾斜指定的角度。如下图:

skew(a,b)的效果是通过矩阵运算实施2D倾斜的。下图展示了几个矩阵倾斜的例子。

第八部分:视域函数 perspective()

在3D空间中改变元素的形态时,基本上都要赋予元素一定的视域。视域为元素赋予前后深度,而这深度可以根据需要设定。

函数可取值
perspective()<length>

设置较小的值得到较极端的视角,设置较大的值得到较温和的视角。如下图:

小于300px的视域值得到的效果特别失真,大于2000px的值失真“十分温和”,而500px到1000px之前的值产生“适中的视域”。下图展示了相同旋转角度下不同视域值得到的结果。

视域值必须是正数,而且不能为零。其他值都将导致 perspective()函数被忽略。 perspective()函数在变形函数列表中的位置十分重要。如果想在变形函数列表中设定视域值,一定要把 perspective()函数放在首位,只要也要放在以来视域的变形之前。

重要:transform函数的编写顺序十分重要。 perspective()函数与后面介绍的 perspective属性十分相似,但是二者的用法完全不同。一般来说,最好使用 perspective属性,不过也有需要使用 perspective()函数的例外情况。

第九部分:修改视域

视域其实由两个属性定义:一个定义视域属性距离,相当于前面讨论过的perspective()函数。另一个定义视域的原点。

9.1:定义视域 perspective 属性

perspective 属性的值是一个长度,定义视域锥体的深度。这么看来,它与前面讨论的 perspective()函数类似,不过两者之间有重要区别。

perspective
取值`none`
初始值none
适用于任何可变形的元素
计算值绝对长度或者none
继承性
动画性

如果想创建特别深的视域,仿照变焦镜头的效果,可以声明perspective: 2500px。如果想让深度浅一些,可以声明perspective: 200px

9.2: perspective 属性与perspective()函数的区别
  • perspective()函数只为目标元素定义视域,比如声明perspective(800px) rotateY(45deg)。那么只有应用这个规则的元素才能使用设定的视域。

  • perspective 属性定义的视域深度应用到目标元素的所有子元素上。

这就是两者的重要区别。perspective 属性创建的3D空间为所有子元素共有,而perspective()函数只对目标元素有效果,而且要放在变形函数列表的开头或前面,多数时候,应该使用perspective属性。

9.3:移动视域原点 perspective-origin 属性
perspective-origin
取值` [leftcenterrighttopbottom ][leftcenterrighttopbottom ] && [leftcenterrighttopbottom ]]? `
初始值50% 50%
适用于任何可变形的元素
百分数相对范围框计算
计算值计算为一个百分数。值为长度值时,计算为绝对长度
继承性
动画性<percentage>,<length>

perspective-origin 定义视线汇聚于哪一点。

下图表示不同视域原点下的“标尺”(视域原点生效的前提是必须声明perspective属性):

第十部分:矩阵函数 matrix()

matrix()
取值<number>[,<number>]{5,5}

css变形规范对matrix()函数做了严格规定:以6个值a-f确定变换矩阵指定2D平面中的变形。

matrix()函数的有效值是6个以逗号分隔的数字。不能多,也不能少。数字可以为正或负。matrix()函数的值所用的句法十分复杂,描述的是元素变形后的最终状态,可以涵盖其他所有变形类型(旋转,倾斜等)。最后,很少有人使用这个句法。

10.1 3D变形矩阵函数
matrix3d()
取值<number>[,<number>]{15,15}

css变形规范对matrix3d()函数做了严格规定:以列主序排列一个4X4的齐次矩阵,用这个16个值指定3D变形。matrix3d()函数的值必须是16个以逗号分隔的数字,不能多也不能少。这些数字按列排列成一个4X4矩阵,第一列由第一组四个数构成,第二列由第二组四个数构成,以此类推。

本章只对矩阵函数进行简单介绍。

第十一部分:移动原点 transform-origin

目前所见的变形有个共同点,都以元素的绝对中心为变形的原点。例如,旋转元素时,是绕着中心旋转的,而不是绕着某一角度旋转的。这是默认行为,不过可以使用transform-origin属性修改。

transform-origin
取值` [leftcenterrighttopbottom ][leftcenterrighttopbottom ] && [leftcenterrighttopbottom ]]? `
初始值50% 50%
适用于任何可变形的元素
百分数相对范围框计算
计算值计算为一个百分数。值为长度值时,计算为绝对长度
继承性
动画性<percentage>,<length>

取值语法看似复杂,但是实际使用起来还是不难的。transform-origin属性的值为两个或三个关键字,用于定义相对哪个点变形。第一个值针对横向,第二个值针对纵向,可选的第三个值是z轴上的长度。

横轴和纵轴可以使用英语关键字,百分数,长度。而z轴不能使用英语关键字或百分数。不过可以使用长度值。目前像素值是最常用的。

长度值设定的是距元素左上角的距离。所以,transform-origin: 5em 22px定义的变形原点距元素的左边5em,距元素的顶边22px。

百分数相对对应的轴和元素的尺寸计算。设定的是距元素左上角的偏移量。如:transform-origin: 67% 40%定义的元素距元素左边的距离为宽度的67%,距元素顶边的距离为高度的40%。如下图:

加入我们把一个元素向右旋转45度,通过设置变形原点的位置,那么元素的最终位置取决于变形原点。下图展示了不同的变形原点得到的结果。

变形原点对其他变形类型也有影响,比如倾斜和缩放。如下图:

有种变形不怎么受变形原点的影响——平移。使用translate()移动元素,不管元素变形原点在哪,元素最终都被移动到相同的位置。

第十二部分:变形方式 transform-style

如果在一个三维空间中改变元素的形态,例如使用translate3d(),或许希望在3D空间中呈现元素。然而,这不是默认行为。默认情况下,不管怎么变形,得到的结果都是扁平的。但是可以使用transform-style修改。

transform-style
取值`flatpreserve-3d`
初始值flat
适用于任何可变形的元素
计算值指定的值
继承性
动画性

假如我们想在适中的视域下移动元素。使用下面css:

.inner {  
    transform: perspective(750px) translateZ(60px) rotateX(45deg); 
}
<div class="outer">
    <div class="inner">inner</div>
</div>

结果如下图:

但是,如果向某一边旋转外层div,结果就与我们设想的不一样了:内层div像粘在外层div上的照片一样,这与预期不符。因为transform-style默认值是flat。内层div的上部前倾,下部后靠,像是紧贴在外层div上的图像,随外层一起旋转。


.outer {  
    transform: perspective(750px) translateY(60px) rotateX(-20deg); 
}

.inner {  
    transform: perspective(750px) translateZ(60px) rotateX(45deg); 
}
<div class="outer">
    <div class="inner">inner</div>
</div>

结果如下图:

然而,把值改成preserve-3d后,结果就截然不同了。内层div绘制成一个3D对象,浮动在外层div附近,因此也就不紧贴在外层div上了。


.outer {  
    transform: perspective(750px) translateY(60px) rotateX(-20deg); 
    transform-style: perserve-3d;
}

.inner {  
    transform: perspective(750px) translateZ(60px) rotateX(45deg); 
}
<div class="outer">
    <div class="inner">inner</div>
</div>

改动后结果如下图:

注意

transform-style设定的变形方式可能会被其他属性覆盖。这是因为那些属性的某些值要求元素及其子元素必须以扁平的方式呈现才能起作用。遇到这种情况,不管你把transform-style设为什么值,都会被强制重置为flat

为了避免被覆盖,下面属性要设为给出的值(这些也都是属性的默认值):

  • overflow: visible
  • filter: none
  • clip: auto
  • clip-path: none
  • mask-image: none
  • mask-border-source: none
  • mix-blend-mode: normal

如果突然发现3D变形效果无效,根源就可能存在上述列表中的某个属性上。

第十三部分:处理背面 backface-visibility

在3D变形中,backface-visibility属性可以看到元素的背面。

backface-visibility
取值`visiblehidden`
初始值visible
适用于任何可变形的元素
计算值指定的值
继承性
动画性

backface-visibility属性决定元素的背面朝向我们时是否渲染背面,仅此而已。

假设翻转两个元素,一个元素的backface-visibility属性设置为visible,另一个设置为hidden

一个简单的卡片翻转效果演示 backface-visibility 的使用:

.card {
  position: relative;
  perspective: 800px;
  transform-style: preserve-3d;
  width:200px;
  height:280px;
  transition: all .6s;
  border: 1px solid #000;
  border-radius: 4px;
}

.card:hover {
  transform: rotateY(180deg);
}
img {
  position: absolute;
  width:100%;
  height: 100%;
  transition: all .5s;
  backface-visibility: hidden;
}
/*由于我们将两个图像都藏在了背面,所以另一面没有任何东西。 所以接下来需要再把.front-face翻转180度*/
.back-face {
  transform: rotateY(180deg);
}

<div class="card">
    <img class="front-face" src="1.jpg">
    <img class="back-face" src="2.jpg">
</div>

参考链接

链接如果打不开直接复制地址在浏览器打开

CSS权威指南(第四版)

从矩阵与空间操作的关系理解CSS3的transform

CSS 3D 旋轉 rotate3d 與 translate3d

www.bilibili.com/video/av673…

meyerweb.github.io/csstdg4figs…

htmldog.com/references/…