下一代CSS的Transform

发布于 大漠

最新版本的Firefox(v72)浏览器的发布,在CSS方面带来了一些变化,比如对CSS路径动画(CSS Motion Path)的支持和CSS Transforms Module Level 2部分功能的支持。在这篇文章,我们就来一探CSS Transforms Module Level 2给CSS Transform带来的变化以及和CSS Transforms Module Level 1的差异。但在这篇文章中我们不会和大家聊所有Level 2中的内容,只是聊聊和level 1的变化,如果你感兴趣的话,请继续往下阅读。

未来的CSS Transform

首先给大家上一张图:

从上图中我们可获知,在未来使用CSS的transform时,我们可以独立的使用相关的函数。目前为止,Firefox 72+中,我们要是使用CSS的transform的话,对于translatescalerotate三个变换函数可以独立使用,不再需要放到transform属性中。

独立使用translaterotatescale属性允许使用者(CSSer)独立的指定简单的transform,而不必记住transform中的顺序,这些顺序使translate()rotate()scale()的操作独立于屏幕坐标。

那么这三个属性独立使用和放置到transform中的差异是什么呢?如果感兴趣,请继续往下阅读。

如果希望能看到translate()rotate()scale()独立使用渲染出来的效果,首先请使用Firefox 72+版本。在接下来的示例中,如无特别声明,请使用该版本的Firefox浏览器查阅。

Transform Level 1 和 Transform Level 2的异和同

接下来的内容只会围绕着translaterotatescale三者展开,有关于CSS Transform内容暂不在该文讨论。

在CSS Transforms Module Level 1中,translate()rotate()scale()三者是transform的函数(<transform-function>,他们都是transform属性的值。在transform中可以独立使用,也可以组合使用。而且独立使用和组合使用得到的效果都将不同,在组合状态和书写顺序也有着紧密的联系。

在CSS Transforms Module Level 2中,translaterotatescale三者不再是transform的函数,而是独立的CSS属性

虽然在Level 1和Level 2中,他们的性质不同,但效果是不是相同呢?这个就需要我们一探究竟。

translate

先来看他们在语法上的差异:

// Level 1
transform: translate( <length-percentage> [, <length-percentage> ]?)

translate()可以拆分为三个独立的函数:translateX()translateY()translateZ()。而Level 2的语法则是:

// Level 2
tanslate: none | <length-percentage> [ <length-percentage> <length>? ]?

translate属性接受1~3个值,每个值指定一个轴上的平移,这三个值分别代表的是XYZ轴的值:

  • 如果只给translate属性指定一个或两个属性值,那么该属性就指定了一个2D空间的平移,相当于transformtranslate()函数。如果第二个值缺失,则默认为0px
  • 如果给translate属性指定三个属性值,那么该属性氷指定了一个3D空间的平移,相当于transformtranslate3d()函数

来看一个示例:

.old {
    transform: translate(20vh, 30vh);
}

.new {
    translate: 20vh 30vh;
}

效果如下:

在Chrome浏览器中开启“Web实验性属性”(chrome://flags/#enable-experimental-web-platform-features),也可以看到正常的效果。

注意,为了便于向大家展示3D空间的变换效果,接下来的示例将以下面的HTML结构来构建:

<div class="camera"> 
    <div class="space"> 
        <div class="box"></div> 
    </div> 
</div>

这个结构在transform3D空间是很有用的。最外层的div.camera相当于设定了摄影镜头,在该元素上设置perspective-originperspective两个属性的值:

.camera {
    perspective-origin: center center; 
    perspective: 500px;
}

简单来说就是透视点以及镜头到透视点的距离

第二层的div.space用来设定一个立体空间,这个空间的设定方式很简单,只需要将transform-style属性的值设置为3d就能构建一个3D立体空间:

.space{ 
    transform-style:3d; 
}

第三层的div.box大家应该清楚了,就是我们要变换的元素盒子。我们接下来所做的位移、旋转和缩放都是围绕着.box来做的:

为了大家更好的看到相应的效果,在上面的示例基础上做一些调整,你可以在示例中尝试着调整进度条来修改不同轴的值:

:root {
    --x: 50%;
    --y: 50%;
    --z: 0px;
}

.old {
    transform: translate3d(var(--x), var(--y), var(--z));
}

.new {
    translate: var(--x) var(--y) var(--z);
}

效果如下:

从效果上来看,transform: translate(x,y)translate: x y效果等同;transform: translate3d(x, y x)translate: x y z的效果等同。

在CSS Transforms Module Level 1中的translate()可以拆分为translateX()translateY(),而translate3d()可以拆分为translateX()translateY()translateZ()。即:

transform: translate() = transform: translateX() translateY()

transform: translate3d() = transform: translateX() translateY() translateZ()

但是在CSS Transforms Module Level 2中的translate属性并没有这样的语法规则。不过我们可以尝试让其中一个值为0是否具备相应的效果。我们来验证一下这方面的想法:

.old {
    transform: translateX(var(--x));
}

.new {
    translate: var(--x) 0; // 或者:translate: var(--x)
}

两者的效果是等同的:

translate取值为一个值时,那么y轴和z轴相当于设置了值为0

按同样的方式,实现translateY()的效果:

.old {
    transform: translateY(var(--y));
}

.new {
    translate: 0 var(--y);
}

效果是等同的:

但要注意,当translate属性取一个值,比如translate: var(--y)时和translateY()效果并不等同,而会和translateX()等同,那是因为在translate只显式的设置一个值的话,则会默认为x轴的值,此时y轴和z轴相当于等于0,不会做任何的位移。这一点切记。

最后来验证translateZ()

.old {
    transform: translateZ(var(--z));
}

.new {
    translate: 0 0 var(--z);
}

效果如下:

rotate

CSS中的旋转rotate()相对于translate()要复杂的多。就CSS Transforms Module Level1中的旋转,他的语法规则也较为复杂:

// Level 1
transform: rotate(<angle>) // 2d

transform: rotate3d(<number>, <number>, <number>, <angle>)

其中rotate3d()也可以分拆为三个独立的函数,即rotateX(<angle>)rotateY(<angle>)rotateZ(<angle>)

在Level 2中的rotate属性,他的语法规则较为清晰:

// Level 2
rotate: none | <angle> | [ x | y | z | <number>{3} ] && <angle>

rotate属性接受一个角度(<angle>)来旋转一个元素,也可以接受一个轴(x | y | z)来旋转元素,如果使用轴来旋转元素需要带上一定的角度(<angle>),这样表示元素绕着指定的轴旋转指定的角度。如果轴被省略,rotate属性相当于一个2D空间的旋转,和transform中的rotate()函数等同。否则就是一个3D空间的旋转,相当于transform中的rotate3d()函数。在3D空间中,可以分为:

  • 如果在rotate属性中显式指定了xyz中的任何一个轴,表示元素会绕着指定的轴旋转,相当于transform中的rotateX()rotateY()rotateZ()三个函数
  • 另外一种方法是,可以通过给出三个轴表示的原点为中心的向量的xyz分量的数字,这个时候相当于transform中的rotate3d()函数

比如,rotate: 30degrotate: z 30deg都可以让元素旋转,第一个声明的是一个2D空间的旋转,等价于transform: rotate(30deg);第二个声明的是一个3D空间的旋转,等价于transform: rotateZ(30deg)

注意,CSS变换中的旋转,除了可以使用deg角度作为单位之外,还可以使用radgradturn等。有关于这方面更详细的介绍可以阅读《CSS 的值和单位》和《聊聊Web中的度数单位》。

接下来,我们分别来看看Level 1和Level 2具体的使用,以及他们之间的异同。先来看roate()roate

:root {
    --angle: 45deg;
}

.box {
    transform-origin: left bottom; //将变换原点设置为盒子的左下角顶点处
}

.old {
    transform: rotate(var(--angle));
}

.new {
    rotate: var(--angle);
}

效果和大家预期的一样,两者的效果是等同的:

上面我们看到的是一个2D空间的旋转效果的对比,接下来看看3D空间的旋转。我们都知道transform属性取值为rotate3d()rotateX()rotateY()rotateZ()函数时,表示的都是元素在3D空间旋转。在新的rotate也有类似的:

  • transform: rotateX(<angle>) 相当于 rotate: x <angle>
  • transform: rotateY(<angle>) 相当于 rotate: y <angle>
  • transform: rotateZ(<angle>) 相当于 rotate: z <angle>
  • transform: rotate3d(x, y, z, <angle>) 相当于 rotate: x y z <angle>

先来看rotateX(<angle>)rotate: x <angle>的效果。在上面的示例上稍作调整:

:root {
    --angle: 45deg;
}

.old {
    transform: rotateX(var(--angle));
}

.new {
    rotate: x var(--angle);
}

效果是等同的:

以同样的方式来看rotateY(<angle>)rotate: y <angle>的效果:

:root {
    --angle: 45deg;
}

.old {
    transform: rotateY(var(--angle));
}

.new {
    rotate: y var(--angle);
}

效果如下:

下面这个示例是让元素围绕着Z轴旋转,即rotateZ(<angle>)rotate: z <angle>的效果:

:root {
    --angle: 45deg;
}

.old {
    transform: rotateZ(var(--angle));
}

.new {
    rotate: z var(--angle);
}

最后来看rotate3d(),这个相对来说复杂一点,该函数接受四个参数,前三个接受的是<number>值,一般是0~1之间的值,分别对应的是xyz轴的向量值,最后一个值是<angle>,即旋转的角度值。比如下面这个效果:

:root {
    --x: 0.5;
    --y: 0.3;
    --z: 0.2;
    --angle: 45deg;
}

.box {
    transform: rotate3d(var(--x), var(--y), var(--z), var(--angle));
}

效果如下:

我们在上面这个示例的基础来扩展,把rotate: x y z <angle>的示例添加上去:

:root {
    --x: 0.5;
    --y: 0.3;
    --z: 0.2;
    --angle: 45deg;
}

.old {
    transform: rotate3d(var(--x), var(--y), var(--z), var(--angle));
}

.new {
    rotate: var(--x) var(--y) var(--z) var(--angle);
}

效果如下:

rotate3d()中有些场景和rotateX()rotateY()rotateZ()是等同的:

  • rotateX(<angle>)函数功能等同于rotate3d(1,0,0,<angle>)
  • rotateY(<angle>)函数功能等同于rotate3d(0,1,0,<angle>)
  • rotateZ(<angle>)函数功能等同于rotate3d(0,0,1,<angle>)

那么相应的延伸到rotate属性中:

  • rotate: 1 0 0 <angle>等同于rotate: x <angle>
  • rotate: 0 1 0 <angle>等同于rotate: y <angle>
  • rotate: 0 0 1 <angle>等同于rotate: z <angle>

在Level 1中,我们还可以将多个旋转函数运用于同一个transfrom属性,这种现象常称为链式变换,比如下面这个示例:

:root {
    --angleX: 45deg;
    --angleY: 30deg;
    --angleZ: 20deg;
}

.old {
    transform: rotateX(var(--angleX)) rotateY(var(--angleY)) rotateZ(var(--angleZ));
}

效果如下:

CSS的链式transform是个复杂的事情:

:root {
    --angleX: 45deg;
    --angleY: 30deg;
    --angleZ: 20deg;
}

.old {
    transform: rotateX(var(--angleX)) rotateY(var(--angleY)) rotateZ(var(--angleZ));
}

.new {
    transform: rotateZ(var(--angleZ)) rotateY(var(--angleY)) rotateX(var(--angleX));
}

就上例来说,如果我们把书写顺序换一换,效果就会不同:

但是在Level 2中暂时是还不支持链式的写法,比如:

.new {
    rotate: x var(--angleX) y var(--angleY) z var(--angleZ)
}

上面的这个是无效的语法值。如果换多行写:

.new {
    rotate: x var(--angleX);
    rotate: y var(--angleY);
    rotate: z var(--angleZ);
}

根据CSS语法规则来判断,最终仅rotate: z var(--angleZ)运用到元素.new上,这是因为相同CSS属性运用在同一个元素之上的话,那么后面的相同的属性名会替换前面的属性。

在链式的变换中,还可吧不同的变换函数。这个我们放到后面独立来和大家探讨。从这里我们可以得知:

在Level 2中的rotate属性,无法获取链式的旋转效果

scale

同样的,在CSS的transform中有关于缩放的函数有scale()scale3d(),前者是2D空间的缩放,后者是3D空间的缩放。其语法规则:

// Level 1
transform: scale( <number> [, <number> ]? )

transform: scale3d(<number>, <number>, <number>)

其中也可以拆分scaleX(<number>)scaleY(<number>)scaleZ(<number>)三个函数。

在CSS Transforms Module Levle中scale属性的语法规则是:

// Level 2
scale: none | <number>{1,3}

scale属性可以接受1~3个值,每个值按照xyz的顺序来排列。如果只给出一个x值,那么y轴的值将默认为和x轴的值相同。当给scale属性值的个数不同,所代表的意思也不同:

  • scale属性只有一个或两个值,表示的是2D空间的缩放,相当于transformscale()函数
  • scale属性给出三个值,表示的是3D空间的缩放,相当于transformscale3d()函数

如果希望用scale属性来表达scaleX()scaleY()scaleZ()函数的话,则可以像下面这样来表述:

  • scale: <number> 1 1则与scaleX(<number>)等效
  • scale: 1 <number> 1则与scaleY(<number>)等效
  • scale: 1 1 <number>则与scaleZ(<number>)等效

因为在CSS变换中的缩放,当<number>的值为1表示既不放大,也不缩小。当<number>的值小于1表示在原来的基础上缩小,当<number>大于1时表示在原来的基础上放大。很多时候常把<number>称为缩放因子。

和上面一样,我们通过示例来验证。先来看transform: scale()scale如何实现2D空间的缩放:

:root {
    --scale: 1;
}

.old {
    transform: scale(var(--scale));
}

.new {
    scale: var(--scale);
}

前面也提到过了,不管是在scale()函数中还是scale属性中,只显式的设置了一个<number>的值,则表示x轴和y轴的值是相同的。

从示例的结果中我们不难发现scale(<number>)scale: <number>的效果是等同的:

在上面的示例基础上,我们稍做调整,意思就是 x轴和y轴的比例因子取不同的效果

:root {
    --scaleX: 1;
    --scaleY: 1;
}

.old {
    transform: scale(var(--scaleX), var(--scaleY));
}

.new {
    scale: var(--scaleX) var(--scaleY);
}

效果如下所示:

继续在xy轴的基础上加上z的值。这样一来就创建了3D空间的缩放,也就是scale3d():

:root {
    --scaleX: 1;
    --scaleY: 1;
    --scaleZ: 1;
}

.old {
    transform: scale3d(var(--scaleX), var(--scaleY), var(--scaleZ));
}

.new {
    scale: var(--scaleX) var(--scaleY) var(--scaleZ);
}

效果如下:

transform中可以在每个轴都有独立的缩放函数,比如scaleX(),但在scale属性中需要变个方式来构建类似scaleX()的功能,即将scale属性中的y轴和z轴的值都显式的设置为1

:root {
    --scale: 1;
}

.old {
    transform: scaleX(var(--scale));
}

.new {
    scale: var(--scale) 1 1;
}

效果是相同的:

对于scaleY()函数,可以用scale: 1 number 1来实现等同的效果:

同样的,scaleZ()函数可以用scale: 1 1 number来实现等同的效果:

skew()matrix()以及其他

在CSS Transforms Module Level 2中,到目前为止仅translaterotatescale提出来成为独立的CSS属性,但skew()matrix()等还是需要和transform属性配合使用。另外,在Level 2中也列出了transform-stylepeerspectiveperspective-originbackface-visibility等属性的描述。不过这里不做过多的阐述,如果你对这方面的感兴趣的话,可以阅读:

链式CSS Transforms

前面提到过链式的CSS Transforms,用的是roate()为例子。接下来我们回到文章开头的示例:

element {
    scale: 2;
    rotate: 30deg;
    translate: -50% -50%;
}

在同一个元素上使用了多个transform,我们把这种行为称为 链式CSS Transform。在CSS Transforms Module level中,我们会这样使用:

element {
    transform: scale(2) rotate(30deg) translate(-50%, -50%)
}

而且在transform属性中链式使用多个变换函数时,跟书写顺序有着很紧密的关系,比如上面的代码,如果我们换成:

element {
    transform: translate(-50%, -50%) rotate(30deg) scale(2)
}

效果会完全不同:

具体原因不在这里阐述,感兴趣的可以阅读:

这里我们更想验证的是:

element {
    scale: 2;
    rotate: 30deg;
    translate: -50% -50%;
}

transform的:

element {
    transform: scale(2) rotate(30deg) translate(-50%, -50%)
}

是不是具有相同的效果。因为这种链式的变换用法还是很常见的。我们在上面的示例基础上稍作修改:

从效果上来看还是不同的,咱们再来换换:

.old {
    scale: 2;
    rotate: 30deg;
    translate: -50% -50%;
}

.new {
    translate: -50% -50%;
    rotate: 30deg;
    scale: 2;
}

可以看到,最终的效果是相同的:

从上面两个示例我们可以得出:

CSS Transforms Module Level2中的translaterotatescale属性是没有链式一说,也没有顺序之分。而且多个属性同时使用时和transform链式写法有一定的差异。

小结

在上面主要探索了一下CSS Transforms Module Level 2中的translaterotatescale三个属性,以及这三个属性和transform对应的translate()rotate()scale()以及相关联函数的对比。从上面的验证我们不难发现,这些属性可以找到与之匹配的transform效果,不同的是和链式的transform使用有一定的差异,而且这三个属性在样式规则中没有书定顺序的要求。不同的书写顺序得到的效果是一致的。

到目前为止,CSS Transforms Module Level 2还是草案阶段,后面很有可能会有相关的变化。感兴趣的同学可以持续关注相面的更新。如果你在这方面有相关的经验和建议欢迎在下面的评论中与我们共享。