4.4 CSS 2D 变换
4.4.1 从基本的变换方法说起
/* 位移 */
transform: translate(0, 0);
/* 旋转 */
transform: rotate(0deg);
/* 缩放 */
transform: scale(1);
/* 斜切 */
transform: skew(0deg);
1.translate()位移
以自身坐标为基准,进行水平方向或垂直方向的位移,语法如下:
/* 往右偏移10px,往下偏移20px */
transform: translate(10px, 20px);
/* 往右偏移10px */
transform: translateX(10px);
/* 往下偏移20px */
transform: translateY(20px);
注意:
- 其中,translate()函数中的第二个值可以省略,省略后表示垂直方向的偏移大小是 0。因此,translate(10px)等同于 translate(10px, 0),也等同于 translateX(10px)。大家千万不要被 scale()函数的语法给误导,translate(10px)不是 translate(10px, 10px)的简写
- 位移的方向和文档流的顺序没有任何关系,也就是即使祖先元素设置 direction:rtl,translateX(10px)依然表示往右偏移。
位移变换最不可替代的特性就是设定百分比偏移值,因为 CSS 世界中就没有几个属性的百分比值是相对于自身尺寸计算的,示例 如下:
/* 往左偏移自身宽度的一半,往上偏移自身高度的一半 */
transform: translate(-50%, -50%);
居中定位:
.dialog {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
2.rotate()旋转
旋转 45 度 (顺时针):
transform: rotate(45deg);
- 角度(deg):角度范围为 0 ~ 360 度,角度为负值可以理解为逆时针旋转。例如,−45deg 可以理解为逆时针旋转 45 度。
- 百分度(grad):一个梯度,或者说一个百分度表示 1/400 个整圆。因此 100gads 相当于 90deg,它和 deg 单位一样支持负值,负值可以理解为逆时针方向旋转。
- 弧度(rad):1 弧度等于 180/π 度,或者大致等于 57.3 度。1.5708rad 相当于 100gads 或是 90deg,如图 所示。
- 圈数(turn):这个很好理解,1 圈表示 360 度,平时体操或跳水中出现的“后空翻 720 度”,也就是后空翻两圈的意思。于是有等式 1turn=360deg、2turn=720deg 等。
下面的 CSS 代码均表示顺时针旋转 45 度:
transform: rotate(45deg);
transform: rotate(50gads);
transform: rotate(0.7854rad);
transform: rotate(0.25turn);
注意:实际开发的时候,只需要使用 deg 单位就好了,没必要 “炫技”使用其他角度单位
3.scale()缩放
缩放变换也支持 和 两个方向,因此,下面的语法都属于 2D 变换的语法:
/* 水平放大2倍,垂直缩小1/2*/
transform: scale(2, 0.5);
/* 水平放大2倍 */
transform: scaleX(2);
/* 垂直缩小1/2*/
transform: scaleY(0.5);
注意:
缩放变换不支持百分比值,仅支持数值,因此 scale(200%,50%)是无效的。
缩放变换支持负值。如果我们想要实现元素的水平翻转效果,可以设置 transform:scaleX(-1);想要实现元素的垂直翻转效果,可以设置 transform:scaleY(-1)。如果水平缩放和垂直缩放的大小一样,我们可以只使用一个值,例如,transform:scale(2)表示将元素水平方向和垂直方向的尺寸放大到现有尺寸的 2 倍。
4.skew()斜切
斜切变换也支持 和 两个方向,例如:
/* 水平切斜10度,垂直切斜20度 */
transform: skew(10deg, 20deg);
/* 水平切斜10度 */
transform: skewX(10deg);
/* 垂直切斜20度 */
transform: skewY(20deg);
注意:
所有包含 X 和 Y 字符的变换函数都不区分大小写,例如 skewX(10deg)写作 skewx(10deg)是正确,translateX(10px)写作 translatex(10px)也是正确的,不过我们约定俗成字符 X 和 Y 都是大写。skew(10deg)可以看成 skew(10deg, 0)的简写,效果等同于 skewX(10deg)。
旋转是 360 度一个轮回,斜切则是 180 度一个轮回。元素处于 90 度或者 270 度斜切的时候是看不见的,因为此时元素的尺寸在理论上是无限的。对浏览器而言,尺寸不可能是无限的,因为没办法表现出来!于是这种情况下的尺寸为 0,所以元素在 90 度或者 270 度斜切的时候是不会影响祖先元素的滚动状态的。如果读者对这句话不理解,把 skewY()函数的角度调成 89 度或者 271 度就知道什么意思了。skew()函数支持所有角度单位值。
怎么样绘制以下:
4.4.2 transform 属性的若干细节特性
1.盒模型尺寸不会变化
页面中的元素无论应用什么 transform 属性值,该元素盒模型的尺寸和位置都不会有任何变化。这和 position:relative 相对定位偏移的行为有些类似。
transform: scale(2);
虽然视觉尺寸放大了 2 倍,但是,并不会推开旁边的元素,只会在视觉上重叠与覆盖。
2.内联元素无效
内联元素(不包括替换元素)是无法应用 transform 变换的,且不支持所有变换特性。例如:
span {
transform: translateX(99px);
}
3.锯齿或虚化的问题
在应用旋转或者斜切变换的时候,元素边缘会表现出明显的锯齿,文字会明显虚化。这个现象主要出现在桌面端浏览器上,而且这个问题是没有办法避免的,因为显示器的密度跟不上。
目前大部分桌面显示器还都是 1 倍屏,显示的最小单元是 1px×1px,你可以理解为显示器屏幕是由一个个 1px×1px 大小的格子组成的。如果像素点旋转 45 度,那么这个正方形像素点的端点和边必然就会穿过其他的格子,如图所示
于是,有一个问题出现了,显示器没有能力显示小于 1px×1px 的图形,于是,要么裁剪像素点(锯齿),要么使用算法进行边缘模糊计算(虚化)。因此,要想解决 transform 变换锯齿和虚化的问题,只要把我们的显示器换掉就可以了。换成一个高清屏,类似 iMac 那种 5KB 显示屏,这个现象就没了。因为这类屏幕密度足够高,0.2px×0.2px 的元素都可以细腻渲染。
4.不同顺序不同效果
<p><img src="1.jpg" class="transform-1" /></p>
<p><img src="1.jpg" class="transform-2" /></p>
p {
width: fit-content;
border: solid deepskyblue;
}
.transform-1 {
transform: translateX(40px) scale(.75);
}
.transform-2 {
transform: scale(.75) translateX(40px);
}
5.clip/clip-path 前置剪裁
一个元素应用 transform 变换之后,同时再应用 clip 或者 clip-path 等属性,此时很多人会误认为剪裁的是应用变换之后的图形,实际上不是的,剪裁的还是变换之前的图形,也就是先剪裁再变换。例如:
img {
width: 128px;
height: 96px;
transform: scale(2);
clip-path: circle(48px at 64px 48px);
}
如果是先执行 transform 再执行 clip-path,则最终剪裁的圆的半径应该还是 circle()函数中的 48px,即最终剪裁的圆的直径是 96px;如果是先执行 clip-path 再执行 transform,则最终剪裁的圆的直径应该是 192px,在各个浏览器中实际渲染的结果都是直径为 192px 的圆,如图所示。
由此可以证明,transform 和 clip-path 同时用的时候,是先执行 clip-path 剪裁,另外一个剪裁属性 clip 也是类似的。
6.动画性能优秀
CSS 高性能动画三要素指的是绝对定位、opacity 属性和 transform 属性。因此,同样的动画效果,优先使用 transform 属性实现。例如,元素移动动画应使用 transform 属性,而不是 margin 属性。
4.4.3 元素应用 transform 属性后的变化
1.创建层叠上下文
和 opacity 属性值不是 1 的元素类似,如果元素的 transform 属性值不是 none,则会创建一个新的层叠上下文。
2.固定定位失效
想要实现固定定位效果,可以应用 position:fixed 声明。大部分情况下,最终的样式表现是符合预期的,但是,如果父元素设置了 transform 变换,则固定定位效果就会失效,样式表现就会类似于绝对定位。例如:
3.改变 overflow 对绝对定位元素的限制
4.改变绝对定位元素的包含块
过去绝对定位元素的包含块是第一个 position 属性值不为 static 的祖先元素,现在 transform 属性值不为 none 的元素也可以作为绝对定位元素的包含块。例如:
4.4.4 深入了解矩阵函数 matrix()
transform 变换还支持矩阵函数 matrix()。无论是位移、旋转、缩放还是斜切,其变换的本质都是应用矩阵函数 matrix()进行矩阵变换。所谓矩阵变换,就是套用矩阵公式,把原先的坐标点转换成另外一个坐标点的过程。其语法如下:
transform: matrix(a, b, c, d, e, f);
matrix()函数的 6 个参数对应的矩阵如图:
矩阵转换公式示意:
其中, ax+cy+e 表示变换后的水平坐标, bx+dy+f 表示变换后的垂直坐标。
通过一个简单的例子来快速了解一下,假设矩阵参数如下:
transform: matrix(1, 0, 0, 1, 30, 30); /* a=1, b=0, c=0, d=1, e=30, f=30 */
随便选取一个点坐标,例如(0, 0),即 x = 0, y=0 于是,矩阵计算后的 x 坐标就是 ax+cy+e =30
矩阵计算后的 y 坐标就是 bx+dy+f =30
也就是点坐标从(0, 0)变成了(30, 30)。请读者好好想象一下,原来(0, 0)的位置,经过矩阵变换后就移到了(30, 30)的位置,是不是等同于往右方和下方各偏移了 30px ?
实际上 transform:matrix(1, 0, 0, 1, 30, 30) 等同于 transform:translate (30px, 30px)
注意: translate()、rotate()等函数都是需要单位的,而 matrix()函数中的参数的单位是省略的。
1.位移:translate()函数
位移变换函数 translate(x, y)中的 x 和 y 分别对应 matrix()函数中的 e 和 f 两个参数,语法示意如下:
transform: matrix(a, b, c, d, 水平偏移距离, 垂直偏移距离);
只要关心最后 2 个参数就可以了,至于前面 4 个参数,它们和位移变化没有关系。
2.缩放:scale()函数
transform: matrix(1, 0, 0, 1, 30, 30); /* a=1, b=0, c=0, d=1, e=30, f=30 */
其中参数 a 表示 x 轴缩放大小,参数 d 表示 y 轴缩放大小。
套用矩阵计算公式计算下就明白了,假设原始坐标是 (x, y),缩放比例是 s ,则:
可以看到最终的坐标就是原始坐标按缩放比例缩放的结果,表明参数 a 和参数 d 确实是和缩放相关的矩阵参数。我们不妨测试一下:

3.旋转:rotate()函数
旋转要比位移和缩放难一些,需要用到三角函数的知识。假设旋转角度为 ,则矩阵计算方法和参数如下:
matrix(cosθ, sinθ, -sinθ, cosθ, 0, 0)
因此,元素旋转 60 度也可以使用下面的矩阵函数表示:
transform: matrix(0.5, 0.866025, -0.866025, 0.5, 0, 0);
/* 等同于 */
transform: rotate(60deg);
4.斜切:skew()函数
斜切用到了三角函数 tan,对应的是 b 和 c 两个参数,需要注意的是 b 参数表示的是 轴的斜切,而后面的参数 c 才是 轴的斜切。计算公式如下:
matrix(1, tan(θy), tan(θx), 1, 0, 0)
5.汇总说明
位移变换使用的是矩阵参数 e 和 f;
缩放变换使用的是矩阵参数 a 和 d;
旋转变换使用的是矩阵参数 a、b、c 和 d;
斜切变换使用的是矩阵参数 b 和 c。
仔细查看上面的结论,不难发现同时使用不同的变换就会有参 数冲突的问题,假如想要同时使用旋转变换和缩放变换,各个参数 值该如何使用矩阵表示呢?使用空格分开表示即可,例如:
transform: matrix(0.5, 0.866, -0.866, 0.5, 0, 0) matrix(3, 0, 0, 1, 0, 0);
可以看到,使用 matrix()函数表示实在太麻烦了,参数很多,还要计算和记忆。因此日常开发中我们使用的都是更加语义化的快捷函数,例如上面的矩阵表示实际上可以写作:
transform: rotate(60deg) scale(3, 1);
但是,这并不表示 matrix()函数一无是处,举两个应用场景例子。
- 跨语言的图形变换处理。矩阵的计算是各个语言通用的,在跨语言、跨设备处理的时候,matrix()函数就很有用。例如,在 Web 中呈现医学影像的图形,后台数据库存储的往往就是矩阵变换坐标,此时有了 matrix()函数,就可以直接呈现了。
- 前面提到运行 transform: scale(.75)translateX(40px)最后元素的水平偏移大小是 30px,这让不少开发者“踩了坑”,如果使用 matrix()函数表示,就不会有这样的问题出现了:
transform: matrix(0.75, 0, 0, 0.75, 40, 0);
6.3D 矩阵变换
最后,关于 3D 矩阵变换再多说一两句。3D 矩阵变换不再是 3×3,而是 4×4,其计算方式和 2D 变换一致,只是上升了 1 个维度,计算复杂度增加了。这里简单示意下 3D 缩放效果的矩阵计算公式,如图:
用代码表示就是:
transform: matrix3d(sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, 0, 0, 0, 1);
4.4.5 常常被遗忘的 transform-origin 属性
变换默认是相对于元素的中心点进行的,这个中心点是由 transform-origin 属性决定的。
在 IE9 浏览器中的 transform-origin 属性支持 2 个属性值,在 IE10+浏览器中则支持 3 个属性值,分别表示 x 轴、 y 轴和 z 轴的变换中心点,初始值是 50%、50%和 0,其中需要注意以下几点。
z 轴只能是数值。
x 轴和 y 轴支持百分比值、数值和关键字属性值(left |center | right | top | bottom)。
关键字属性值自带方位,因此 轴关键字写在前面也是正确的,例如:
/* x轴 | y轴 */ transform-origin: right top; /* y轴 | x轴 */ transform-origin: top right;
center 关键字可省略,例如:
transform-origin: bottom center;
/* 可以写作 */
transform-origin: bottom;
同样,如果看到单个值语法,则另外一个省略的值就是 50%,例如
transform-origin: 20px;
/* 等同于 */
transform-origin: 20px 50%;
transform-origin 属性在实际项目开发中主要用在下面两个场景。
模拟现实世界物体的运动。例如,我以前做项目的时候实现过蜡烛火焰摆动的效果,该效果需要设置火焰元素的 transform-origin 属性值为 bottom,这样火焰摆动的时候才自然。又如实现钟摆运动的动画效果,需要设置 transform-origin 属性值为 top,这样钟摆顶部就会固定,下方会摆动。
布局与定位。例如,在 Chrome 浏览器中想要实现 10px 大小的字符,可以先设置 12px 大小的字符,然后缩放一下:
.text { transform: scale(0.83333); }
但是这样做有一个问题,transform 变换的时候元素的原始位置是保留的,这会导致元素缩小后原本设定的间距变大,此时可以使用 transform-origin 属性优化一下。如果元素是左对齐的,则可以设置:
transform-origin: left;
如果元素是右对齐的,则可以设置:
transform-origin: right;
transform-origin 属性作用原理
transform-origin 属性的作用原理就是改变 transform 变换的中心点坐标。transform 变换默认的中心点坐标为(0, 0),如图:
设置变换中心点位于左上角:
transform-origin: 0 0;
现在的中心点坐标就不是(0, 0)了,而是(140, -145)(这是因为色块尺寸为 280px×290px),如图:
总结一下,所有 transform 变换本质上都是坐标点位置的矩阵变换,transform-origin 属性变化后,所有点的坐标都会发生变化,这导致最终的矩阵变换计算结果也发生变化。
4.4.6 scale()函数缩放和 zoom 属性缩放的区别
除了 Firefox 浏览器不支持 zoom 属性,其他所有浏览器都支持,且支持时间非常早,移动端也可以放心使用。zoom 属性的正式语法如下:
zoom: normal | reset | <number> | <percentage>;
从语法中可以看出 zoom 属性支持以下属性值。
- 百分比值。zoom:50%,表示缩小到原来的一半。
- 数值。zoom:0.5,表示缩小到原来的一半。
- normal 关键字。zoom:normal 等同于 zoom:1,是默认值。
- reset 关键字。zoom:reset,表示用户按 Ctrl 和 − 或 Ctrl 和+进行文档缩放的时候,元素不跟着缩小与放大。不过,这个关键字兼容性很糟糕,仅被 Safari 浏览器支持
过对比 zoom 属性缩放和 scale()函数缩放的不同之 处:
标准和非标准区别。zoom 属性是一个非标准属性,虽然 MDN 文档建议不要在非生产环境使用,但是根据我的判断,浏览器日后绝无可能放弃对 zoom 属性的支持。
坐标系不同。zoom 属性缩放的中心坐标是相对于元素的左上角,且不能修改。transform 变换中的 scale()函数缩放默认的中心坐标是元素的中心点。
占据的尺寸空间表现不同。zoom 属性缩放会实时改变元素占据的尺寸空间。例如,一个图片原始尺寸是 128px×96px,则应用下面的 CSS 代码后,图片占据的尺寸就会变成 256px×192px,该图片周围的元素会被推开,并会触发重绘和重计算,因此 zoom 属性缩放的性能比 scale()函数缩放的性能差。
img { zoom: 2; }
如果图片使用的是 scale()函数缩放,则占据的尺寸还是原先的 128px×96px。
元素应用 zoom 属性不会出现应用 transform 属性后的个变化。元素应用 zoom 属性不会创建层叠上下文,不会影响 fixed 元素的定位和 overflow 属性对绝对定位的溢出隐藏,也不会改变绝对定位元素的包含块
总而言之,zoom 属性就是一个普普通通的改变元素比例的 CSS 属性
4.4.7 了解全新的 translate、scale 和 rotate 属性
最新的 CSS Transforms Level 2 规范针对位移、缩放和旋转定义了全新的 CSS 属性。例如,位移可以直接使用 translate 属性,该属性支持 1 ~ 3 个值,分别表示 x 轴、 y 轴和 z 轴,语法如下:
translate: 50%;
translate: 10px 20px;
translate: 50% 105px 5rem;
缩放可以直接使用 scale 属性,支持 1 ~ 3 个值,分别表示 x 轴、 y 轴和 z 轴,语法如下:
scale: 1;
scale: 1 0.5;
scale: 1 2 3;
旋转可以直接使用 rotate 属性,语法相对复杂些:
rotate: 45deg;
/* 指定旋转轴 */
rotate: x 90deg;
rotate: y 0.25turn;
rotate: z 1.57rad;
/* 矢量角度值 */
rotate: 1 1 1 90deg;
下面将逐一讲解上述语法中的元素: rotate:45deg 等同于 transform:rotate(45deg),是一 个 2D 旋转变换;
rotate:x 90deg 等同于 transform:rotateX(90deg);
rotate:y .25turn 等同于 transform:rotateY(.25turn);
rotate:z 1.57rad 等同于 transform:rotateZ(1.57rad);
rotate:1 1 1 90deg 等同于 transform:rotate3D(1, 1, 1, 90deg)。
写下这段文字内容的时候,只有 Firefox 浏览器支持这个新特性,想要在实际项目中使用这个新特性,还需要一段时间,大家了解即可。最后,没有 skew 属性