CSS权威指南第五版-第17章 变形

17.1 坐标系

在变形中使用两种类型的坐标系,熟悉这两种坐标系是一个好主意。

第一种是笛卡尔坐标系,或者通常称为x/y/z坐标系。这种坐标系使用两个数字(二维)或三个数字(三维)表示一个点在空间中的位置。在CSS中,这个坐标系使用三个轴表示:x轴(横轴)、y轴(纵轴)和z轴(深度轴)。这在图17-1中得到了说明。

图17-1. CSS变形中使用的三个笛卡尔轴

二维变形只需关注x轴和y轴。按约定,x轴上的正值在右侧,负值在左侧。类似地,y轴上的正值沿纵轴向下,负值沿纵轴向上。

这可能看起来有点奇怪,学过初等代数的人大都知道,数值越大位置应该越高,而不是越低(在图17-1中,“y”标注在y轴的底部;三个轴的标注都在正方向那一刻)。如果你用过CSS中的绝对定位,回想一下绝对定位元素时使用的top属性:值为正数时元素下移,值为负数时则上移。

鉴于此,若想把元素向左下方移动,要把x值设为负数、把y值设为正数,如下所示:

translateX(-5em) translateY(33px)

稍后就会发现,这个变形值是有效的。上述代码的作用是把元素向左移动5em、向下移动33像素。

如果想在三维空间中改变元素的形态,要加上z轴值。这个轴从显示器上“跃出”,指向你的眼前。理论上,就是这样。Z轴上的正值离你较近,负值离你较远。在这方面,z轴与z-index属性是完全一样的。

下面在上述移动过的元素上再加个z轴值:

translateX(-5em) translateY(33px) translateZ(200px)

现在,在未指定z值相比,元素离我们的距离近了200像素。

真正需要注意的是,每个元素都有自己的参照系,各轴都相对自身而动。也就是说,如果旋转了元素,轴也随之旋转,如图17-2所示。旋转之后再变形,是相对旋转后的轴计算的,而不是显示器的轴。

​图17-2. 元素参照系

现在,假设你想把一个元素在显示的平面上顺时针旋转45度(即围绕Z轴)。你最可能使用的变形值是:

rotate(45deg)

将其改为-45度,元素将围绕Z轴逆时针旋转(对我们的国际朋友来说是逆时针旋转)。换句话说,它将在xy平面内旋转,如图17-3所示。

图17-3. xy平面内的旋转

提到旋转,这就引出CSS变形功能使用的另一个坐标系——球坐标系。这个坐标系用于描述三维空间中的角度,在图17-4中作了说明。

​图17-4. CSS变形中使用的球面坐标系

在二维变形中只需关注全周360度极坐标系,即由x轴和y轴构成的平面。对旋转来说,二维旋转其实是在绕z轴旋转。类似地,如果绕x轴旋转,元素将偏向我们或远离我们,而绕y轴旋转的话,元素将向两侧旋转。这些都在图17-5中得到说明。

图17-5. 围绕三条轴的旋转

好了,现在我们有了自己的方向,让我们开始使用CSS变形吧。

17.2 变形

变形其实只有一个属性,不过有几个辅助属性用于控制如何变形。先从主要的属性入手。

TRANSFORM

取值

<transform-list> | none

初始值

none

适用于

除“atomic inline-level” 框之外的所有元素 (见下文的说明)

百分数

相对范围(bounding) 框计算(见下文的说明)

计算值

指定的值,不过相对长度值会计算为绝对长度

继承性

动画性

作为一种变形

<transform-list>是一个空格分隔的、定义不同变形函数的列表,就像上一节中使用的例子。我们稍后将深入探讨你可以使用的具体函数。

先说明什么是“范围(bounding)框”。受CSS控制的元素,其范围框是边框(border)框,即元素边框的外边界。也就是说,计算范围框时,轮廓和外边距不算在内。

注意

如果一个表格形式显示(table-display)元素,其范围是表格的容器(wrapper) 框, 包括表格框(table box)和相应的表题框(caption box)。

如果使用CSS变形一个可缩放矢量图形(Scalable Vector Graphics,SVG)元素,其范围框是SVG图形定义的对象范围框( object bounding box)。

请注意,所有经过变形的元素(例如,transform设置为none以外的元素)都有自己的堆叠上下文(stacking context)。

虽然经过缩放的元素可能比变形前小或大,但是元素在页面上所占的空间与变形前保持不变。所有的变形函数都是如此:当你平移或旋转一个元素时,它的兄弟姐妹不会自动移开。

现在,取值句法中的<transform-list>需要说明一下。它指的是一个或多个变形函数的列表,一个接一个,以空格分隔的格式。它看起来像这样,其结果如图17-6所示

#example {transform: rotate(30deg) skewX(-25deg) scaleY(2);}

​图17-6. 一个经过变形的div元素

变形函数一次只处理一个,从第一个(最左边)开始,一直到最后一个(最右边)。从头到尾的处理顺序是很重要的,顺序变了,得到的结果可能就大有不同。请看下面两个规则,它们的结果如图17-7所示:

img#one {transform: translateX(200px) rotate(45deg);} img#two {transform: rotate(45deg) translateX(200px);}

​图17-7. 不同的变形列表,不同的结果

在第一个示例中,图像先沿x轴移动200像素,然后旋转45度。在第二个示例中,图像先旋转45度,然后沿x轴移动200像素,然而这里的x轴指的是变形后元素的x轴,而不是父元素、页面或视区的x轴。也就是说,元素旋转后,其x轴(及其他轴)也随之旋转了。所有变形都是相对元素自己的参照系实施的。

请注意,当你有一系列的变形函数时,所有的函数都必须是正确的格式化;也就是说,它们必须是有效的。如果哪怕只有一个函数是无效的,它就会使整个值无效。例如:

img#one {transform: translateX(100px) scale(1.2) rotate(22);}

因为rotate()的值是无效的--旋转值必须有一个单位--所以整个值被丢弃。有问题的图像将只是停留在它最初的未变形状态,既没有平移也没有缩放,更不用说旋转了。

还有一种情况是,变形通常不叠加。也就是说,如果你对一个元素应用了一个变形,改变了元素的形态,然后又想增加一个变形,你就需要在原变形的基础上修改。请考虑以下情况,如图17-8所示: 

#ex01 {transform: rotate(30deg) skewX(-25deg);} #ex01 {transform: scaleY(2);} #ex02 {transform: rotate(30deg) skewX(-25deg);} #ex02 {transform: rotate(30deg) skewX(-25deg) scaleY(2);}

​图17-8. 覆盖或修改变形

在第一种情况中,第二个规则完全取代了第一个规则,因此元素只会在y轴上缩放。这其实不难理解,就像你在一个地方为元素声明了字号,又在另一个地方为元素声明不同的字号一样,字号是不叠加的,只有其中一个字号起作用。在第二个示例中,第一个规则中的变形都出现在第二个规则中,因此它们将与scaleY()函数一起起作用。

注意

如果您希望属性仅适用于单一类型的转换,例如仅执行旋转的属性或仅缩放元素的属性,我们将在本章后面看到一些这样的属性,所以请继续关注。

有一个重要的注意点:在写这篇文章的时候,变形并不应用于基元(atomic)行内框。基元行内框指span、超链接等行内框。这些元素可以随块级父元素一起变形,但是不能直接旋转span元素,除非把显示方式改为display: block、display: inline-block或类似的方式。这种限制的原因在于不确定性。假设你有一个span元素(或任何行内框),它分为多行。如果旋转它,会发生什么?每个行框是相对于自己旋转,还是所有的行框都作为一个整体被旋转?目前还没有明确的答案,而且争论还在继续,所以目前你还不能直接变形行内框。

变形函数

截至2022年年底,共有21个不同的变形函数。不同的变形函数利用不同格式的值实施相应的变形。表17-1提供了一个所有可用的变形函数的列表,并去掉了它们的值模式(value pattern)。

表17-1. 变形函数

translate()translate3d()translateX()translateY()translateZ()

scale()scale3d()scaleX()scaleY()scaleZ()

rotate()rotate3d()rotateX()rotateY()rotateZ()

skew()skewX()skewY()

matrix()matrix3d()perspective()

我们将首先解决最常见的变形类型,以及它们存在的相关属性,然后再处理更隐晦或困难的变形。

17.2.1 平移

平移变形只是沿着一个或多个轴的移动。例如,translateX()沿着元素自身的X轴移动元素,translateY()沿着元素自身的Y轴移动元素,translateZ()沿着元素自身的Z轴移动元素。

17.2.1.1 平移函数

函数

可取的值

translateX(), translateY()

<length> | <percentage>

这两个通常被称为"二维"平移函数,因为它们可以上下或左右移动元素,但不能沿Z轴向前或向后移动元素。这两个函数的值都是一个距离值,可以是长度,也可以是百分数。

如果这个值是一个长度,那么效果就和你所期望的一样。用translateX(200px)将一个元素沿x轴平移200像素,它将向右移动200像素。将其改为translateX(-200px),它将向左移动200像素。对于translateY(),正值使元素向下移动,而负值使它向上移动。请记住,平移总是相对于元素本身声明的。因此,例如,如果你通过旋转把元素倒过来,那么translateY()的正值将在页面上向上移动元素。

如果这个值是一个百分数,移动距离相对元素自身的尺寸计算。因此,如果一个元素是300像素宽,200像素高,translateX(50%)将把它向右移动150像素,而translateY(-10%)将把同一个元素向上移动(相对于它自己)20像素。

函数

可取的值

translate()

[ <length> | <percentage> ] [, <length> | <percentage>]?

如果想同时沿x轴和y轴平移一个元素,使用translate()更简单。只要先提供x值,再提供y值,它的作用与你结合translateX()translateY()一样。如果你省略了y值,那么它将被假定为零。因此,translate(2em)被当作translate(2em,0)处理,这也与translateX(2em)相同。参见图17-9,了解一些二维平移的例子。

图17-9. 在两个维度上进行平移

函数

可取的值

translateZ()

<length>

这个函数将元素沿Z轴平移,即在第三个维度中移动元素。与二维平移函数不同,translateZ()只接受长度值。translateZ()不允许使用百分数,实际上任何有关z轴的值都不可以使用百分数。

函数

可取的值

translate3d()

[ <length> | <percentage> ], [ <length> | <percentage>], [ <length> ]

我们知道translate()能同时设定x轴和y轴平移,类似地,translate3d()能同时设定x轴、y轴和z轴的平移量。如果想一次性地将一个元素向右、向上和向前移动,这是很方便的。参见图17-10,了解三维平移的工作原理。图中的箭头表示沿相应轴的移动,最终到达三维空间中的一个点。图中的虚线表示离原点(三条轴的交点)的距离和方向,以及在xz平面上方的距离。

与translate()不同,如果translate3d()的值少于三个,没有假定的默认值。因此,浏览器应该把translate3d(1em,-50px)视为无效,没有实际的平移发生。

​图17-10. 在三维空间中进行平移

17.2.1.2 平移属性

在平移一个元素而不想使用transform属性的情况下,可以使用translate属性来代替。

TRANSLATE

取值

| none | [ <length> | <percentage> ]{1,2} <length>?

初始值

none

应用于

任何可变形的元素

百分数

相对范围框计算

计算值

指定的值,不过相对长度值会计算为绝对长度

继承性

动画性

作为一种变形

与translate()函数非常相似,translate属性接受一至三个长度值,或两个百分比和一个长度值,或更多简化的模式,例如单个长度。与translate()函数不同,translate属性不使用逗号来分隔其值。

如果只给了一个值,那么它就被用作x轴的平移。如果有两个值,第一个是x轴平移,第二个是y轴平移。如果有三个值,则按x y z的顺序取值,任何缺失的值都默认为0px。

如果你回到图17-9,下面的操作将产生与图中所示相同的结果。

translate: 25px; /* equivalent to 25px 0px 0px */ 
translate: 25%; translate: 0 25px; /* equivalent to 0 25px 0px */ 
translate: 0 -25px; 
translate: 20% 20%; 
translate: -20% -20%; 
translate: 110% 25px;

同样地,下面的方法也会产生图17-10中的相同效果:

translate: 150px -50px 100px;

默认值,none,意味着不应用平移。

17.2.2 缩放

缩放变形使一个元素放大或缩小,取决于你提供的值。这些值是无单位的实数,可以是正数也可以是负数。在二维平面上,可以沿着x轴和y轴单独缩放,也可以将它们一起缩放。

17.2.2.1 缩放函数

函数

可取的值

scaleX(), scaleY(), scaleZ()

<number> | <percentage>

提供给scale函数的数字值是一个乘数;因此,scaleX(2)将使元素的宽度是变形前的两倍,而scaleY(0.5)将把元素的高度缩小一半。百分数与数字值的比例为100:1;也就是说,50%的效果与0.5相同,200%与2相同,以此类推。

函数

可取的值

scale()

[ <number> | <percentage> ] [, <number> | <percentage> ]?

如果想同时沿着两个轴进行缩放,请使用scale()。x值总是在前,y值总是在后,所以scale(2,0.5)将使元素的宽度放大两倍,高度缩小一半。如果只提供一个值,它将被用作两个轴的缩放值;因此,scale(2)将使元素的宽度和高度都放大两倍。这与translate()相反,在translate()中,省略的第二个值总是被设置为零。 scale(1)使元素大小不变,正如scale(1,1)一样。

图17-11展示了几个元素缩放的例子,既使用了单轴缩放函数,也使用了组合的scale()。

图17-11. 缩放的元素

如果你可以在两个维度上缩放,你也可以在三个维度上缩放。CSS提供了scaleZ()用于沿Z轴缩放,以及scale3d()用于一次沿所有三个轴缩放。当然,仅当元素有深度时,这两个函数才有效果,而元素在默认情况下是没有深度的。如果让元素具有一定的深度--比如说,围绕x轴或y轴旋转一个元素--那么深度就可以缩放,使用scaleZ()或scale3d()都可以。

函数

可取的值

Scale3d()

[ <number> | <percentage> ] , [ <number> | <percentage> ] , [ <number> | <percentage> ]

与translate3d()类似,scale3d()要求所有三个数字都是有效的。如若不然,无效的scale3d()将导致所属的整个变形值无效。

还要注意的是,如果你缩放一个元素,这将改变任何变形的有效距离。例如,下面的例子将导致元素向右平移50像素。

transform: scale(0.5) translateX(100px);

这是因为该元素被缩小了50%,然后在它自己的参考框内向右移动了100像素,也就是一半大小。换一下函数的顺序,元素将被向右平移100像素,然后从这个位置开始缩小50%。

17.2.2.2 缩放属性

与转换类似,有一个 scale 属性允许放大或缩小元素,而无需使用 transform 属性来执行此操作。

SCALE

取值

none | [ <percentage> | <number> ]{1,3}

初始值

none

应用于

任何可变形的元素

百分数

相对范围框计算

计算值

指定的值

继承性

动画性

作为一个变形

scale处理其值的方式与我们看到的translate属性有些不同。如果你只给一个值,比如scale(2),那么它就被用来在x和y方向上进行缩放。如果有两个值,第一个值用于x轴方向的缩放,第二个值用于y轴方向。如果有三个值,第三个值用于在Z轴方向上的缩放。

如果你参考图17-11,下面会有同样的结果。

scale: 2 1; /* 等于 200% 100% */ 
scale: 0.5 1; /* 等于 50% 100% */ 
scale: 1 2; 
scale: 1 0.5; 
scale: 1.5; 
scale: 1.5; 
scale: 0.5 1.5; 
scale: 1 5 0.5;

默认值,none,意味着不应用缩放。

17.2.3 旋转

17.2.3.1 旋转函数

旋转函数使一个元素围绕一个轴或三维空间中的一个任意向量旋转。有四个简单的旋转函数,还有一个稍微复杂、专门针对三维空间的函数。

函数

可去的值

rotate(), rotateX(), rotateY(), rotateZ()

<angle>

所有四个简单的旋转函数只接受一个值,即角度。这可以用一个数字(可以是正数,也可以是负数)和一个有效的角度单位(deg、grad、rad和turn)来表示。 如果一个值的数字超出了相应单位的常规范围,将化为范围内的值。换句话说,一个437deg与77deg的效果是相同的,与-283deg的效果也相同。

然而,请注意,仅当没有使用任何形式的动画,这样的换算才是完全等效的。如果以动画的形式旋转1100deg,元素将转动几周,最后停止在-20度(如果喜欢用正数,是340度)的倾斜位置。与之相比,如果以动画的形式旋转-20deg,元素将稍微向左倾斜,而不转动,如果以动画的形式旋转340deg,元素将向右转动几乎一周。这三次动画的最终状态是一样的,但是每一次旋转的过程有明显的差异。

rotate()函数实施的是二维旋转,是我们最常用的旋转方式。它在效果等同于rotateZ(),因为它是围绕Z轴(从显示器射出来,直指你的眼睛)旋转元素。类似的,rotateX()使元素围绕X轴旋转,从而使元素向你倾斜或远离你;rotateY()使元素围绕Y轴旋转,就好像它是一扇门。这些都在图17-12中进行了说明。

​图17-12. 围绕三个轴的旋转

警告

图17-12中的几个例子涉及到3D效果。这只有在使用属性 transform-style 和 perspective 的某些值时才有可能,在 "选择 3D 风格 "和 "改变透视 "一节中有所描述,简单起见,这里省略。本章涉及的3D变形都是如此,如果只应用给出的变形函数是得不到图中所示效果的,切记。

函数

可取的值

rotate3d()

<number>`, __&lt;number&gt;__, __&lt;number&gt;__, `<angle>

如果你了解向量,并且想在三维空间中旋转元素,那么rotate3d()就适合你。前三个值指定了三维空间中向量的x、y和z分量,第四个值是角度值,指定绕向量旋转的量。

从一个简单的例子开始:rotateZ(45deg)的3D旋转是rotate3d(0,0,1,45deg)。这个向量在x轴和y轴上的大小是零,在z轴上的大小是1。也就是说,旋转中心是z轴。该元素围绕指定向量旋转45度,如图17-13所示。图中还给出了绕x轴和y轴旋转45度时应该提供给rotate3d()函数的值。

图17-13. 围绕三维向量的旋转

rotate3d(-0.95,0.5,1,45deg)要更复杂一些,它描述的向量指向轴之间的三维空间。为了理解它的工作原理,让我们从一个简单的例子开始:rotateZ(45deg)(如图17-13所示)。相当于rotate3d(0,0,1,45deg)。前三个数字描述了一个向量的分量,这个向量在x轴和y轴上的大小为零,在z轴上的大小为1。因此,它沿Z轴指向一个正方向;也就是说,指向观察者。然后,当你看向向量的原点时,这个元素是顺时针旋转的。

类似地,rotateX(45deg)等效于三维空间中的rotate3d(1,0,0,45deg)。这个向量在x轴上,指向正方向(右方)。如果你站在该向量的末端,看向它的原点,元素绕向量顺时针旋转45度。因此,从通常的观察者位置来看,元素的顶部会远离观察者,元素的底部会朝观察者旋转。

稍微复杂些:假设旋转函数是rotate3d(1,1,0,45deg)。正视显示器时,这个函数描述的向量从左上角指向右下角,正好穿过元素的中心(当然默认情况下是这样;我们将在后面看到如何改变)。因此,该元素的矩形框中有一条45度斜线。然后向量带着元素一起旋转45度。如果看向向量的原点,旋转是顺时针的,所以同样地,元素的顶部远离观察者,而底部靠近观察者旋转。如果我们把旋转改为rotate3d(1,1,0,90deg),在观察者看来,元素是侧立的,倾斜45度,正面面向右上角。你可以找张纸试一下,从左上角到右下角画一条直线,然后绕那条线旋转纸张。

好了,试着想象一下rotate3d(-0.95,0.5,1,45deg)描述的向量。假设有个边长为200像素的立方体,那么向量的分量是沿X轴向左190像素,沿Y轴向下100像素,沿Z轴向观察者200像素。该向量从原点(0,0,0)到点(-190 px,100 px,200 px)。图17-14描述了该向量,以及呈现在观察者面前的最终结果。

所以,这个向量就像一根刺穿被旋转的元素的金属棒。当我们沿着向量线往回看时,旋转是顺时针45度。但由于向量指向左下前方,这意味着旋转后的元素左上角靠近观察者,而右下角远离观察者,如图17-14所示。

请特别注意,rotate3d(1,1,0,45deg)并不等同于rotateX(45deg) rotateY(45deg) rotateZ(0deg)! 这是个很容易犯的错误。这看起来应该是等价的,但实际上并非如此。如果我们把这个向量放在前面提到的200×200×200的假想立方体里,旋转轴就会从原点指向偏右200像素、偏下200像素的(200,200,0)点。

​图17-14. 围绕三维向量的旋转,以及该向量是如何确定的

可见,旋转轴从左上角到右下角,以45度的角度射穿元素。然后元素围绕这个对角线顺时针旋转45度,当你回头看它的原点(左上角)时,元素的右上角向左旋转了一点,而左下角则向右旋转了一点。这与rotateX(45deg) rotateY(45deg) rotateZ(0deg)的结果明显不同,你可以在图17-15中看到。

图17-15. 围绕一个三维轴的旋转和围绕三个不同轴的依次旋转的区别

17.2.3.2 旋转属性

与平移和缩放一样,也有一个旋转属性,允许你围绕各种轴旋转元素,而不必使用transform属性来进行旋转。然而,可取的值的语法有点不同。

ROTATE

取值

none | <angle> | [ x | y | z | <number>{3} ] && <angle>

初始值

none

应用于

任何可变形的元素

百分数

相对范围框计算

计算值

指定的值

继承性

动画性

作为一个变形

有效值分为三个互不相干的语法选项。最简单的是,默认值none意味着不应用旋转。

如果你想围绕一个轴进行旋转,最简单的方法是在给出轴的标识符的同时给出你想旋转的角度。在下面的代码中,每一行都包含了围绕一个给定轴旋转元素的两种等价方式。 

transform: rotateX(45deg); rotate: x 45deg; 
transform: rotateY(33deg); rotate: y 33deg; 
transform: rotateZ(-45deg); rotate: z -45deg; 
transform: rotate(90deg); rotate: 90deg;

最后一行与前面讨论的rotate()函数的处理类似:具有单个角度值的旋转是 xy 平面上的 二维旋转。(参见图17-12以了解这方面的情况)。

在你想定义一个三维向量作为旋转轴的情况下,rotate的值看起来有些不同。例如,假设我们想把一个元素围绕向量-0.95, 0.5, 1旋转45度,如图17-14所示。下面两个声明中的任何一个都会有这种效果。

transform: rotate(-0.95, 0.5, 1, 45deg); rotate: -0.95 0.5 1 45deg;

如果需要,可以使用此模式围绕主轴旋转;即,rotate: z 23deg和rotate: 0 0 1 23deg会有同样的效果(和rotate: 23deg一样)。这在通过JavaScript更改旋转向量的情况下很有用,但在其他情况下很少。

需要注意的是,transform有一种功能是rotate无法复制的:可以依次进行连锁旋转的能力。例如,transform: rotateZ(20deg) rotateY(30deg) 将首先围绕Z轴将元素旋转20度,然后将旋转的结果围绕Y轴旋转。旋转属性只能单独做其中的一个或另一个。获得相同结果的唯一方法是计算向量和角度,使元素处于与变形操作相同的效果。做到这一点的数学方法当然存在,但不在本书的范围之内(尽管见本章后面的 "矩阵函数")。

17.2.4 单个变形属性的顺序

当使用单个变形属性时,效果总是按照先平移、后旋转、再缩放的顺序应用。换句话说,以下两条规则在功能上是等同的。

#mover { 
    rotate: 30deg; 
    scale: 1.5 1; 
    translate: 10rem;}
 
#mover { 
    transform: translate(10rem) rotate(30deg) scale(1.5 1); 
}

这很重要,因为,例如,先平移再旋转与先旋转再平移是非常不同的。如果你需要让一个元素的变形以平移-旋转-缩放以外的顺序发生,请使用transform而不是单个属性。

17.2.5 倾斜函数

当你倾斜一个元素时,你将它沿着X轴和Y轴中的一个或两个轴进行倾斜。不存在Z轴或其他三维倾斜。

函数

可取的值

skewX(), skewY()

<angle>

这两个函数使元素倾斜指定的角度。展示倾斜比用语言解释要容易得多,所以图17-16显示了一些沿x轴和y轴的倾斜例子。

图17-16. 沿x轴和y轴的倾斜

函数

可取的值

skew()

<angle> [, <angle> ]?

skew(a,b)的效果与skewX(a)skewY(b)不同。前者通过矩阵运算[ax,ay]实施二维倾斜。图17-17展示了几个矩阵偏斜的例子,以及与使用两个单轴倾斜变形的结果对比;这些变形初看起来是一样的,但其实不是。

警告

由于各种原因,包括skew(a,b)与``skewX(a)skewY(b)的不同方式,CSS规范明确不鼓励使用`skew()。如果可能的话,你应该避免使用它;这里记录了它,以防你在遗留代码中遇到它。

​图17-17. 倾斜的元素

如果提供两个值,第一个始终是x轴的倾斜角度,第二个是y轴的倾斜角度。如未指定y轴倾斜角度,假定为零。

注意

与平移、旋转和缩放不同,截至2022年末,没有倾斜属性,所以任何倾斜都必须通过transform 属性来管理。

17.2.6 矩阵函数

CSS中没有matrix属性。

函数

可取的值

matrix()

<number> [, <number> ]{5,5}

在CSS变形规范中,我们发现matrix()被描述为一个 "以六个值a-f的变形矩阵的形式指定一个二维变形"的函数。

首先:一个有效的matrix()值是一个由六个以逗号分隔的数字。不能多,也不能少。这些值可以是正数或负数。第二,该值描述了元素变形后的最终状态,可以涵盖其它所有变形类型(旋转、倾斜等)。第三,很少有人真正使用这种语法来自己写代码,尽管它经常由绘图或动画软件生成。

本节不会具体说明矩阵计算的复杂过程。本节只介绍基本句法,以及在CSS中的用法。

这里简要介绍一下它是如何工作的。假设你将这个函数应用于一个元素。

matrix(0.838671, 0.544639, -0.692519, 0.742636, 6.51212, 34.0381)

这就是用来描述这个变形矩阵的CSS语法:

0.838671 -0.692519 0 6.51212 
0.544639 0.742636 0 34.0381 
0 0 1 0 
0 0 0 1

对。那么这有什么作用呢?它的结果如图17-18所示,与写这个的结果完全一样:

rotate(33deg) translate(24px,25px) skewX(-10deg)

​图17-18. 使用矩阵变形的元素及与之等效的变形函数

这归结为,如果你熟悉或需要使用矩阵,你绝对可以而且应该使用它们。如果不是这样,可以使用多个变形函数得到相同的结果,这样更容易理解。

现在,这只是针对普通的二维变形。如果你想用矩阵来进行三维变形呢?

函数

可取的值

matrix3d()

<number> [, <number> ]{15,15}

同样,我们来看看CSS 变形规范中对matrix3d()的定义:"以列主序排列一个4*4的齐次矩阵,用这16个值指定三维变形。" 这意味着matrix3d()函数的值必须是一个由16个逗号隔开的数字组成的列表,不能多也不能少。这些数字按列序排列在一个4×4的矩阵中,所以矩阵的第一列是由数值中的第一组四个数字组成的,第二列是由第二组四个数字组成的,第三列是由第三组数字组成的,以此类推。因此,你可以使用以下函数:

matrix3d( 
    0.838671, 0, -0.544639, 0.00108928, 
    -0.14788, 1, 0.0960346, -0.000192069, 
    0.544639, 0, 0.838671, -0.00167734,
    20.1281, 25, -13.0713, 1.02614)

得到的矩阵为:

0.838671 -0.14788 0.544639 20.1281 
0 1 0 25 
-0.544639 0.0960346 0.838671 -13.0713 
0.00108928 -0.000192069 -0.00167734 1.02614

最终状态等效于:

perspective(500px) rotateY(33deg) translate(24px,25px) skewX(-10deg)

如图17-19所示。

​图17-19.使用三维矩阵变形的元素及与之等效的变形函数

17.2.7 关于最终状态等效的说明

重要的是要记住,matrix()函数与一系列变形函数等效的只是最终状态。原因与讨论旋转那一节说的一样:旋转393deg与旋转33deg得到的最终视觉效果是一样的,但是如果以动画的形式表示的话,前者将导致元素像滚筒一样旋转,而后者不会。如果用matrix()函数实现相同的最终状态,也不会有滚动效果,而是以最简短的路径到达最终状态。

可能有些抽象,下面举个例子。假设下述变形函数列表与matrix()函数是等效的:

rotate(200deg) translate(24px,25px) skewX(-10deg) 
matrix(-0.939693, -0.34202, 0.507713, -0.879385, -14.0021, -31.7008)

注意200度的旋转。我们自然而然地将其解释为顺时针旋转200度,事实也是如此。然而,如果以动画的形式表示这两次变形,过程是不同的:链式函数版本确实将顺时针旋转200度,而matrix()版本将逆时针旋转160度。两次变形都在相同的位置停止,但方式不同。

有时看似相同的过程,其实也是有差异的。原因还是由于matrix()函数始终取最短的路径到达最终状态,而链式函数版本则按部就班(其实,也不一定)。请看下面两个貌似等效的变形:

rotate(160deg) translate(24px,25px) rotate(-30deg) translate(-100px) 
matrix(-0.642788, 0.766044, -0.766044, -0.642788, 33.1756, -91.8883)

这两个变形也在相同的位置停止,但是,如果以动画的形式表示,到达最终状态的过程是不一样的。乍一看,区别不是那么明显,但确实有差异。

倘若不以动画的形式表示变形,这都不重要,但是一定要知道这个区别,说不定什么时候就想使用动画了。(希望是在阅读了第18章和第19章之后!)。

17.2.8 设置元素的视域

如果你在三维空间中变形一个元素,你很可能希望它有一定的视域。视域给人以前后深度的感觉,你可以改变应用于一个元素的视域程度。

TIP

请注意,函数perspective ()与属性perspective非常相似,后者将在后面介绍,但它们的应用方式截然不同。一般来说,你会想使用perspective属性而不是perspective ()函数,因为perspective属性为整个三维场景创建一个一致的视域。函数将视域应用于一个特定的元素,即使你将完全相同的视域值应用于多个元素,它们也不会看起来是同一个三维场景的一部分。

函数

可取的值

perspective()

<length>

将视域指定为一个距离可能看起来有点奇怪。毕竟perspective(200px)设定的距离无法在z轴上准确衡量。然而,存在即合理。深度幻象就围绕我们指定的值构建。较小的数得到更极端的视角,就好像你离元素很近一样。较大的数字得到更温和的视角,就像从远处通过变焦镜头观看元素一样。特别大的视域值会产生等距效应,这看起来和没有透视一样。

这在一定程度上是有道理的。你可以把视域想象成一个金字塔,它的顶点在视域原点(默认情况下,未变形的元素的位置的中心),它的底部是你所看到的浏览器窗口。顶点和底点之间的距离越短,就会产生一个更浅的金字塔,从而产生更极端的变形。这在图17-20中有所说明,假设的金字塔代表200px、800px和2000px的视域距离。

​图17-20. 不同的视域金字塔

在Safari浏览器的文档中,Apple指出,视域值小于300px时往往会出现极度扭曲,高于2000px的值会产生 "非常温和 "的扭曲,而500px和1000px之间的值会产生 "温和的视域"。为了说明这一点,图17-21展示了一系列具有完全相同的旋转的元素在不同的视域值下的显示。

图17-21. 不同视域值的影响

视域值必须始终是正的、非零的长度。任何其他值都会导致perspective()函数被忽略。还要注意的是,它在链式函数中的位置是非常重要的。如果你看一下图17-21的代码,perspective()函数是在rotateY()函数之前。如果你把顺序颠倒过来,尚未应用视域就旋转了,所以图17-21中的所有四个例子看起来都是一模一样的。因此,如果你打算通过链式变形函数来应用视域值,请确保perspective()排在第一位,或者至少排在依赖视域的变形之前。编写transform函数的顺序非常重要。

17.3 更多的变形属性

除了基本的transform 属性和独立的变形属性(如rotate)之外,还有一些相关的属性,它们有助于定义诸如变形的原点、用于 "场景 "的视域等。

17.3.1 移动变形的原点

到目前为止,我们看到的所有变形都有一个共同点:元素的精准中心被用作变形原点。例如,当旋转元素时,它围绕其中心旋转,而不是,比如说,围绕一个角。这是默认的行为,但是通过transform-origin属性,你可以改变它。

TRANSFORM-ORIGIN

取值

[ left | center | right | top | bottom | <percentage> | <length> ] | [ left | center | right | <percentage> | <length> ] && [ top | center | bottom | <percentage> | <length> ] ] <length>?

初始值

50% 50% (0 0 in SVG)

应用于

任何可变形的元素

百分数

相对范围框计算(见下文的说明)

计算值

计算为一个百分数;值为长度值时,计算为绝对长度

继承性

动画性

<length>, <percentage>

语法定义看起来非常深奥和混乱,但实际上它相当简单。transform-origin属性的值为两个或三个关键字,用于定义相对哪个点变形:第一个值针对x轴,第二个值针对y轴,还可以选择一个沿Z轴的长度。对于横轴和纵轴,你可以使用普通的关键字,如top和right,也可以使用百分数、长度,或不同类型的关键字组合。对于Z轴,你不能使用纯英文的关键词或百分数,但可以使用任何长度值,其中像素值是目前最常用的。

长度值被当作从元素的左上角开始的距离。因此,transform-origin: 5em 22px将把变形原点在元素左侧5em、从元素顶部向下22像素。同样,transform-origin: 5em 22px -200px定义的变形原点右移5em、下移22像素、后移200像素;也就是说,在元素的未变形位置后面200像素。

百分数相对对应的轴和元素的尺寸计算,设定的是距元素左上角的偏移量。例如,transform-origin: 67% 40%将变形原点放在元素左边的距离为宽度的67%,距元素顶边的距离为高度的40%。图17-22说明了几种原点的计算方法。

​图17-22. 各种原点的计算

那么,修改原点的位置有什么用呢?最简单的方法是用二维旋转来说明其作用。假设你将一个元素向右旋转45度。它的最终位置将取决于它的原点。图17-23说明了几种不同的变形原点的效果。图中以圆圈标出的是变形原点。

17.3.2 选择变形的框

上一节写得好像变形原点总是相对于外边界(outer border)计算的,而这的确是HTML中的默认情况。至少在理论上,我们可以通过属性transform-box来改变这种情况。

TRANSFORM-BOX

取值

border-box | content-box | fill-box | stroke-box | view-box

初始值

view-box

应用于

任何可变形的元素

计算值

指定的值

继承性

动画性

其中两个值与HTML直接相关:

border-box

使用元素的边框(由外边框边缘定义)作为变形的参考框。

content-box

使用元素的内容框作为变形的参考框。

剩下的三个是为SVG设计的,尽管它们也可以在HTML上下文中应用:

fill-box

使用元素的对象范围框(object bounding box)作为参考框。

stroke-box

使用元素的描边范围框(stroke bounding box)作为参考框。

view-box

使用该元素的最近的SVG视口作为参考框。

截至2022年底,只有WebKit支持HTML和SVG中的所有五个值。其他浏览器引擎只支持SVG上下文中的fill-box和view-box,而在HTML上下文中似乎忽略了所有五个值。

在SVG上下文中使用fill-box会导致对相关元素进行转换,就像人们对HTML所期望的那样。另一方面,默认的view-box会导致所有的变形都是相对于SVG viewBox属性所建立的坐标系的原点来计算的。图 17-25 说明了差异,这是以下 CSS 和 SVG 的结果:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg"
     viewBox="0 0 500 200" width="500" height="200"
     fill="none" stroke="#000" stroke-width="1">
  <defs>
    <style>
      g rect {transform-origin: 0 0; transform: rotate(20deg);}
      g rect:nth-child(1) {transform-box: view-box;}
      g rect:nth-child(2) {transform-box: fill-box;}
    </style>
  </defs>
  <rect x="0" y="0" width="100%" height="100%" stroke-dasharray="4 3" />
  <rect x="100" y="50" width="100" height="100" />
  <rect x="300" y="50" width="100" height="100" />
  <g stroke-width="3" fill="#FFF8">
    <rect x="100" y="50" width="100" height="100" />
    <rect x="300" y="50" width="100" height="100" />
  </g>
</svg>

​图17-25. 围绕 SVG 原点及其自身原点旋转的正方形

第一个正方形,也就是左边的那个,从它的起点旋转了20度,旋转中心是整个SVG文件的左上角(虚线框的左上角)。这是因为这个正方形的transform-box的值是view-box。第二个方块的transform-box是fill-box,所以它使用自己的填充框(fill box)的左上方--在HTML中我们称之为背景区域--作为旋转中心。

17.3.3 选择3D风格

如果在三维空间中改变元素的形态--例如,使用translate3d()或rotateY()--你可能希望在三维空间中呈现元素。transform-style可以帮助你实现这一点。

TRANSFORM-STYLE

取值

flat | preserve-3d

初始值

flat

应用于

任何可变形的元素

计算值

指定的值

继承性

动画性

假设你有一个元素,你想让它 "靠近 "你的眼睛,然后再倾斜一点,有一个适度的视角。使用的规则和HTML如下:

div#inner {transform: perspective(750px) translateZ(60px) rotateX(45deg);}

<div id="outer">
outer
<div id="inner">inner</div>
</div>

结果如图17-26所示;差不多与所想的一样。

​图17-26. 3D变形内层div

但是,如果向某一边旋转外层div,结果就与我们设想的不一样了:内层div像粘在外层div上的照片一样,这与预期不符。

然而,结果就是这样,因为 transform-style 的默认值是flat。内层div的上部前倾、下部后靠,像是紧贴在外层div上的图像,随外层div一起旋转,如图 17-27 所示。

div#outer {transform: perspective(750px) rotateY(60deg) rotateX(-20deg);} 
div#inner {transform: perspective(750px) translateZ(60px) rotateX(45deg);}

然而,如果将该值改为 preserve-3d,情况就会突然不同。相对于其父级外部 div,内部 div 将被绘制为一个完整的 3D 对象,漂浮在附近的空间中,而不是作为粘贴在外部 div 前面的图片。你可以在图17-27中看到这一变化的结果。

div#outer {transform: perspective(750px) rotateY(60deg) rotateX(-20deg); 
    transform-style: preserve-3d;} 
div#inner {transform: perspective(750px) translateZ(60px) rotateX(45deg);}

​图17-27. flat与3D-preserved变形样式效果

注意,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

  • isolation: auto

这些都是相应属性的默认值,所以只要你不试图为你保留的3D元素改变任何一个属性,你就不会有事!但如果你发现编辑一些CSS时突然把你可爱的3D变形弄得很扁平,这些属性中的一个可能是罪魁祸首。

17.3.4 修改视域

实际上有两个属性用于定义如何处理视域:一个是定义视域距离,就像前一节讨论的perspective ()函数那样;另一个是定义视域的原点。

17.3.4.1 定义视域

首先,让我们考虑perspective这个属性,它接受一个定义透视金字塔深度的长度。乍一看,它和前面讨论的perspective ()函数一样,但有一些关键的区别。

PERSPECTIVE

取值

none | <length>

初始值

none

应用于

任何可变形的元素

计算值

绝对长度,否则就是 none

继承性

动画性

举个简单的例子,如果想创建一个非常深的视域,模仿变焦镜头的效果,可以声明perspective: 2500px。如果想让深度浅些,模仿鱼眼镜头的近景效果,可以声明perspective: 200px。

那么它与perspective()函数之间到底有什么不同呢?当使用perspective ()时,是为被赋予该函数的元素定义了视域效果。比如申明transform: perspective(800px) rotateY(-50grad);那么只有应用这个规则的元素才使用设定的视域。

而perspective属性定义的视域深度应用到目标元素的所有子元素上。下面举例说明这个区别,结果如图17-28所示:

div {transform-style: preserve-3d; border: 1px solid gray; width: 660px;}
img {margin: 10px;}
#func {perspective: none;}
#func img {transform: perspective(800px) rotateX(-50grad);}
#prop {perspective: 800px;}
#prop img {transform: rotateX(-50grad);}

<div><img src="rsq.gif"><img src="rsq.gif"><img src="rsq.gif"></div>
<div id="func"><img src="rsq.gif"><img src="rsq.gif"><img src="rsq.gif"></div>
<div id="prop"><img src="rsq.gif"><img src="rsq.gif"><img src="rsq.gif"></div>

图17-28. 共享视域与独立视域

在图17-28中,第一排图像还没有变形。在第二行中,每幅图像都向我们旋转了50个弧度(相当于45度),但每幅图像都有自己的独立视域。

在第三行图像中,各个图像自身都没有视域。这一排中的图像都在为div容器设定的视域(由perspective: 800px定义)中绘制。正因为它们都是在共有的视域中变形,所以它们看起来是 "正确的";即与预期一致,就像把三个实物照片贴在透明玻璃片上,绕着玻璃片中间的横轴向我们旋转一样。

这就是perspective属性和perspective ()函数之间的关键区别。前者创建了一个由其所有子元素共有的三维空间。后者只影响它所应用的元素。另一个区别是,perspective ()函数的效果是不同的,取决于它在变形链中何时被调用(应放在变形链的开头或前部)。perspective属性总是在所有其他变形之前被应用,这是你通常想要创建的3D效果。

在大多数情况下,你要使用perspective属性而不是perspective()函数。其实,3D变形经常使用页面布局中惯用的div容器(或其它元素),目的主要是提供共有视域。在前面的例子中,<div id="two">的唯一作用就是充当视域容器。没有这个容器,图中的效果也就无从谈起。

17.3.4.2 移动视域原点

如果允许以3D形式呈现,元素在三维空间中的变形将使用视域(参见前文的transform-style和perspective属性)。该视域会有一个原点,也称消隐点(vanishing point),你可以用perspective-origin这个属性改变它的位置。

PERSPECTIVE-ORIGIN

取值

[ left | center | right | top | bottom | <percentage> | <length> ] | [ left | center | right | <percentage> | <length> ] && [ top | center | bottom | <percentage> | <length> ] ]

初始值

50% 50%

应用于

任何可变形的元素

百分数

相对范围框计算(见下文的说明)。

计算值

计算为一个百分数,长度值计算为绝对长度

继承性

动画性

<length>, <percentage>

使用perspective-origin,你定义了视线汇聚的点,就像视域一样,这个点是相对于一个父容器定义的。

与大多数三维变形属性一样,这个属性的作用通过演示更容易说明白。考虑一下下面的CSS和标记,如图17-29所示:

#container {perspective: 850px; perspective-origin: 50% 0%;}
#ruler {height: 50px; background: #DED url(tick.gif) repeat-x;
    rotate: x 60deg;
    transform-origin: 50% 100%;}

<div id="container">
    <div id="ruler"></div>
</div>

图17-29. 一个简单的 "标尺"

我们所拥有的是一个由尺子上的刻度线组成的重复背景图像,包含这些刻度线的 div 倾斜 60 度,远离我们。标尺上的刻度线都指向同一个消隐点,即容器 div 顶边的中点(因为perspective-origin的值是 50% 0%)。

下面我们在同样的条件下变更视域原点的位置,如图17-30所示。

图17-30. 具有不同视域原点的 "标尺"

正如你所看到的,视域原点位置的变化对3D变形元素的渲染是有影响的。

请注意,之所以能看到图中的效果,是因为我们声明了perspective。如果perspective的值是默认的none,不管把perspective-origin设为什么,都将被忽略。这是有道理的,因为在没有视域的情况下,你不可能有一个视域原点!

17.3.5 处理背面

在你布置元素的这些年里,你可能从未想过:如果我们能看到元素的背面,会是什么样子?现在,3D转形已经成为可能,很可能有一天你会看到一个元素的背面。你甚至可能有意要这样做。这样的情况如何处理,由 backface-visibility属性决定的

BACKFACE-VISIBILITY

取值

visible | hidden

初始值

visible

应用于

任何可变形的元素

计算值

指定的值

继承性

动画性

与我们已经谈论过的许多其他属性和函数不同,这个属性相当简单。它所做的只是决定一个元素的背面是否在朝向观众时被渲染。就这么简单。

所以,假设你翻转了两个元素,一个元素的backface-visibility设置为默认值visible,另一个设置为hidden。你会得到图17-31中所示的结果:

span {border: 1px solid red; display: inline-block;}
img {vertical-align: bottom;}
img.flip {rotate: x 180deg; display: inline-block;}
img#show {backface-visibility: visible;}
img#hide {backface-visibility: hidden;}

<span><img src="salmon.gif"></span>
<span><img src="salmon.gif" class="flip" id="show"></span>
<span><img src="salmon.gif" class="flip" id="hide"></span>

图17-31. 显示和隐藏背面

正如你所看到的,第一张图像没有变化。第二张图像沿x轴翻转了,所以我们从背面看到它。第三张也被翻转了,但我们根本看不到它,因为它的背面被隐藏了。

这个属性在很多情况下都能派上用场。最简单的情况是,一个能反转的UI元素,在两面呈现不同的内容。比如一个搜索区域,在背面显示首选项配置,或者一张照片,在背面记一些信息。让我们来看看后一种情况。CSS和标记可能看起来像这样:

section {position: relative;}
img, div {position: absolute; top: 0; left: 0; backface-visibility: hidden;}
div {rotate: y 180deg;}
section:hover {rotate: y 180deg; transform-style: preserve-3d;}

<section>
    <img src="photo.jpg" alt="">
    <div class="info">(…info goes here…)</div>
</section>

(如果有动画旋转,使卡片在三维空间中翻转,这将更有趣)。

这个例子有一个变种,使用相同的标记,但有一个稍微不同的CSS,在翻转时显示图像的背面。这可能更符合原意,因为它使信息看起来像是写在图像的背面。它导致了图17-32中所示的最终结果:

图17-32. 照片在正面,信息在背面

要实现这一点,我们唯一要做的就是将 backface-visibilty: hidden 应用到 div 上,而不是同时应用到 img 和 div 上。因此,当 div 被翻转时,它的背面是隐藏的,但图像的背面却不是。(好吧,那就使用一个半透明的背景,这样我们就可以同时看到文本和它下面的翻转的图像)。

17.4 小结

CSS变形功能能在二维和三维空间中改变元素的形态,这为设计师提供了新的呈现信息的方式。使用丰富的2D变形可以实现引入注目的效果,利用3D变形还能创建交互式界面,变形功能为设计开辟了一片新天地。有些属性之间有依赖关系,这对刚接触CSS变形的创作人员来说可能有些陌生,但是用得多了也会习惯的。

作者在使用变形时经常做的一件事就是将它们做成动画,这样一张卡片就会翻转过来,一个元素就会平滑地缩放和旋转,等等。在接下来的两章中,我们将讨论如何定义这些过渡和动画的细节。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值