以下是由 Ana Tudor 撰写的客座文章。 Ana 总是能很好地深入研究我们在网络上进行图形处理背后的数学原理。 在这种情况下,这格外有用,因为有几种方法可以处理 SVG 变换,并且它们需要一些数学思维,尤其是为了获得最佳的跨浏览器支持而将一种类型转换为另一种类型。 以下是 Ana 的内容。
就像 HTML 元素一样,SVG 元素也可以使用变换函数进行操作。 然而,许多事情在 SVG 元素上的工作方式与在 HTML 元素上的工作方式不同。
首先,SVG 元素上的 CSS 变换在 IE 中不起作用。 当然,如果我们只需要对元素应用 2D 变换,可以使用 SVG 变换属性 来替代 IE。
更新:从 2018 年 4 月 30 日发布的 EdgeHTML 17 开始,Edge 支持 SVG 元素上的 CSS 变换。
但是,如果我们使用 transform
属性方法,变换函数的所有参数都是数字,这意味着我们无法再控制和组合单位。 例如,我们无法在平移函数中使用 %
值(虽然 %
值在 Firefox 中也不会对 CSS 变换起作用,无论是 transform-origin
值 还是 translate()
参数),并且所有旋转或倾斜角度值都以度为单位,我们无法使用 CSS 中可用的其他单位。
%
值用于 transform-origin
,但它们相对于 SVG,而不是相对于我们设置了 transform-origin
的元素,就像在 Chrome 中一样。 Firefox 在这种情况下似乎表现正确。另一个问题是,JavaScript 特性检测 在 IE 中失败(通过 JS 读取 CSS transform
值将返回我们已在样式表中设置的 transform
的矩阵等效项)。 这意味着我们需要另一种方法来检查 IE,或者我们在所有情况下都使用 transform
属性(这感觉总体而言工作量更少)。
HTML 元素和 SVG 元素之间工作的不同之处在于元素的本地坐标系。 每个元素,无论是 HTML 元素还是 SVG 元素,都有一个本地坐标系。
对于 HTML 元素,此坐标系起源于元素的 50% 50%
点。
对于 SVG 元素,假设我们在元素本身或 <svg>
元素中的任何祖先上都没有应用任何变换,那么坐标系起源于 SVG 画布的 0 0
点。
如果 SVG 元素的 50% 50%
点与 SVG 画布的 0 0
点不重合,那么不同的坐标系起源将导致不同的 rotate
、scale
或 skew
变换结果。
为了更好地理解这一点,让我们看看变换函数是如何工作的。
变换函数的工作原理
我们需要了解关于变换的一件事是,它们在应用于嵌套元素时具有累积效应。 这意味着应用于具有后代的元素的变换也会影响所有后代,以及它们自己的坐标系和对这些后代的任何变换的结果。 为简单起见,我们始终假设在以下情况下,我们的元素没有任何具有应用变换的祖先。 我们还假设我们的元素没有任何后代。
平移
平移将元素的所有点沿相同方向移动相同的距离。 平移保留了平行性、角度和距离。 可以将其解释为移动元素坐标系的起源——当发生这种情况时,任何位置相对于该起源描述的元素(元素本身及其任何后代)也会发生移动。 其结果不取决于坐标系的位置。

上图展示了 HTML 情况(左)与 SVG 情况(右)。 褪色的版本是初始版本(在应用平移之前)。 应用 translate transform
会将我们的元素及其坐标系一起移动。 如果有后代,它也会将所有后代移动。
正如我们已经知道的,两者之间的区别在于坐标系的位置。 对于 HTML 情况,坐标系的起源位于元素的 50% 50%
点。 对于 SVG 情况,它位于 SVG 画布的 0 0
点(我们假设在 <svg>
元素中的任何可能的祖先上都没有应用任何变换)。 但是,在平移中,坐标系相对于元素的位置不会影响元素的最终位置。
对于 HTML 和 SVG 元素,使用 CSS 变换时,我们有三个可用于 2D 的平移函数:translateX(tx)
、translateY(ty)
和 translate(tx[, ty])
。 前两个分别只作用于 x
和 y
方向(由元素的坐标系给出)。 请注意,如果在平移之前应用了其他变换,那么 x
和 y
方向可能不再是水平和垂直的了。 第三个平移函数将元素沿 x
轴移动 tx
,沿 y
轴移动 ty
。 在这种情况下,ty
是可选的,如果未指定,则默认为零。
SVG 元素也可以使用 transform
属性进行平移。 在这种情况下,我们只有一个 translate(tx[ ty])
函数。 在这里,值也可以用空格分隔,而不仅仅是用逗号分隔,就像类似的 CSS transform
函数一样。 因此,在 1
个 SVG 用户单位等效于 1px
的非常简单的情况下,以下两种平移 SVG 元素的方法是等效的
• 使用 CSS 变换
rect {
/* doesn't work in IE/ older Edge */
transform: translate(295px, 115px);
}
• 使用 SVG 变换属性
<!-- works everywhere -->
<rect width='150' height='80' transform='translate(295 115)' />
SVG transform
属性和 CSS transform
属性将合并。
连续的 translate()
变换是累加的,这意味着我们可以将一个链条写成 translate(tx1, ty1) translate(tx2, ty2)
,就像 translate(tx1 + tx2, ty1 + ty2)
一样。 请注意,这只有在两个平移是连续的,它们之间没有其他类型的 transform
链的情况下才成立。 translate(tx, ty)
的反向平移是通过另一个平移 translate(-tx, -ty)
完成的。
旋转
2D 旋转会将元素及其任何后代围绕一个固定点(在 transform
之后位置保持不变的点)移动。 最终结果取决于此固定点的位置。 从同一个元素开始,两个角度相同的旋转围绕两个不同的点将产生不同的结果。 就像平移一样,旋转不会扭曲元素,并保留平行性、角度和距离。
连续的 rotate()
变换围绕同一个固定点是累加的,就像平移一样,这意味着 rotate(a1) rotate(a2)
等效于 rotate(a1 + a2)
(但前提是我们的两个旋转是连续的,它们之间没有任何其他类型的 transform
)。
rotate(a)
的反向旋转是通过围绕同一个固定点进行相同角度的反向旋转 rotate(-a)
完成的。

上图展示了 HTML 情况(左)与基本 SVG 情况(右)。 褪色的版本是初始版本(在应用旋转之前)。 应用旋转会将元素及其坐标系围绕固定原点移动,如果我们的元素有后代,它也会对后代执行相同的操作。
在 HTML 情况下,元素坐标系的起源位于元素的 50% 50%
点,因此所有内容都围绕此点旋转。 然而,在 SVG 情况下,起源位于 SVG 画布的 0 0
点(我们假设在任何可能的祖先上都没有应用任何变换),导致所有内容都围绕该点移动。
在 CSS transform
属性的情况下,2D 旋转函数非常简单:只需要 rotate(angle)
即可。 angle
值可以用度 (deg
)、弧度 (rad
)、圈数 (turn
) 或梯度 (grad
) 表示。 我们也可以使用 calc()
值(例如,类似 calc(.25turn - 30deg)
的内容),但这 目前只在 Chrome 38+/ Opera 25+ 中有效。
更新:Firefox 59+ 也支持使用 calc()
作为 rotate()
函数的角度值。
如果我们使用正角度值,那么旋转是顺时针旋转的(反之,负角度值会给我们一个逆时针旋转)。
在 SVG transform
属性的情况下,旋转函数略有不同——rotate(angle[ x y])
。 angle
值与类似的 CSS 变换函数的工作方式相同(正值表示顺时针旋转,负值表示逆时针旋转),但它必须是一个无单位的度值。 可选的无单位 x
和 y
参数指定围绕其旋转元素(及其坐标系)的固定点的坐标。 如果两者都省略,则固定点为坐标系的起源。 只指定 angle
和 x
参数会使值无效,不会应用任何 transform
。
就像在 translate()
函数中一样,参数可以用空格分隔或逗号分隔。
请注意,x
和 y
参数的存在并不意味着坐标系原点会移动到该点。坐标系,就像元素本身(以及它可能具有的任何后代)一样,只是围绕 x y
点旋转。
这意味着我们有两种方法可以旋转 SVG 元素(结果可以在上一图的右侧看到)
• 使用 CSS 变换
rect {
/* doesn't work in IE/ early Edge */
transform: rotate(45deg);
}
• 使用 SVG 变换属性
<!-- works everywhere -->
<rect x='65' y='65' width='150' height='80' transform='rotate(45)' />
我们也可以在 CSS 中指定 transform-origin
值来模拟 x
和 y
参数的使用。长度值相对于元素的坐标系,但百分比值相对于元素本身,所以它们看起来很适合我们想要的内容。但是,我们应该牢记一些事情。
首先,CSS transform-origin
和 rotate()
函数中指定的固定点 *并不相同*。举一个非常简单的例子,比如只围绕 SVG 元素的 50% 50%
点旋转,这并不重要。考虑以下两种情况
rect {
transform: rotate(45deg);
/* doesn't work as intended in Firefox
* % values are taken relative to the SVG, not the element
* which actually seems to be correct */
transform-origin: 50% 50%;
}
<rect x='65' y='65' width='150' height='80'
transform='rotate(45 140 105)' />
<!-- 140 = 65 + 150/2 -->
<!-- 105 = 65 + 80/2 -->
它们都在 Chrome 中以相同的方式旋转元素,如以下图所示

这显示了两者之间的区别。使用 CSS 时,元素的坐标系首先从 SVG 画布的 0 0
点移动到元素的 50% 50%
点。然后,元素被旋转。使用 SVG 变换属性时,元素及其坐标系只是围绕 rotate()
函数的第二和第三个参数指定的点旋转,该点坐标我们已经计算过,使其位于元素的 50% 50%
点。元素坐标系原点仍然在元素外部,并且该原点将影响依赖于它的任何后续变换。
为了更好地理解这一点,让我们在第一个旋转之后再链接另一个旋转,使元素在相反方向上旋转 45°
rect {
transform: rotate(45deg) rotate(-45deg);
transform-origin: 50% 50%; /* Chrome, Firefox behaves differently */
}
<rect x='65' y='65' width='150' height='80'
transform='rotate(45 140 105) rotate(-45)' />
<!-- 140 = 65 + 150/2 -->
<!-- 105 = 65 + 80/2 -->

如上图所示,使用 CSS 变换并将 transform-origin
设置为 50% 50%
时,这两个旋转相互抵消,但使用 SVG transform
属性时,我们围绕其旋转元素的固定点在每次旋转时都会有所不同——对于第一次旋转,它是元素的 50% 50%
点,对于第二次旋转,它是元素坐标系的原点。在这种情况下,我们需要使用 rotate(-45 140 105)
而不是 rotate(-45)
来反转旋转。
但是,这并不改变这样一个事实,即 *我们只有一个 transform-origin
(因为元素的坐标系只有一个原点),但使用 SVG transform
属性时,我们可以应用多次旋转,每一次都围绕不同的点旋转元素*。因此,如果我们想首先围绕矩形的右下角旋转 90°
,然后围绕其右上角再旋转 90°
,那么使用 SVG 变换属性很容易——我们只需为每次旋转指定一个不同的固定点即可。
<rect x='0' y='80' width='150' height='80'
transform='rotate(90 150 160) rotate(90 150 80)'/>
<!--
bottom right:
x = x-offset + width = 0 + 150 = 150
y = y-offset + height = 80 + 80 = 160
top right:
x = x-offset + width = 0 + 150 = 150
y = y-offset = 80
-->

但如何使用 CSS 变换来获得相同的效果呢?第一次旋转很简单,因为我们可以将 transform-origin
设置为 right bottom
,但第二次旋转呢?如果我们只是在第一次旋转之后将其链接起来,它只会围绕相同的固定点(right bottom
)旋转元素 90°
。
我们需要三个链接的变换才能围绕固定点旋转元素,无论其 transform-origin
在哪里。第一个变换是 translate(x, y)
变换,它将元素坐标系原点移动到与我们要围绕其旋转所有内容的固定点一致的位置。第二个是实际的旋转。最后,第三个是 translate(-x, -y)
——第一个平移的反转。
在这种情况下,我们的代码将是
rect {
/* doesn't work as intended in Firefox
* % values are taken relative to the SVG, not the element
* which actually seems to be correct */
transform-origin: right bottom; /* or 100% 100%, same thing */
transform:
rotate(90deg)
translate(0, -100%) /* go from bottom right to top right */
rotate(90deg)
translate(0, 100%);
}
下图显示了它是如何逐步工作的

transform-origin
的第二个问题是,只有长度值在 Firefox 中有效。百分比和关键字无效,因此我们必须将它们替换为长度值。此外,在 translate()
变换中使用的百分比值在 Firefox 中也不起作用。
更新:百分比值现在在 Firefox 中也可用作 transform-origin
值,但它们的行为与在 Chrome 中不同。此外,Firefox 在这种情况下似乎是正确的,所以不要使用这种方法!
缩放
缩放会将元素坐标系原点到元素任何点(以及它可能具有的任何后代)的距离按指定方向的相同比例进行更改。除非缩放因子在所有方向上都相同——在这种情况下,我们有 **统一**(或 **各向同性**)缩放——否则元素的形状不会被保留。
(-1, 1)
范围内的缩放因子会使元素收缩,而此范围之外的缩放因子会将其放大。负缩放因子还会执行围绕元素坐标系原点的点反射,除了大小修改之外。如果只有一个缩放因子不同于 1
,那么我们就有了 **方向** 缩放。
缩放 transform
的结果取决于坐标系原点的位置。从同一个元素开始,两个相同比例的缩放变换对于不同的原点会产生不同的结果。

上图显示了 HTML 案例(左)与 SVG 案例(右)。在这两种情况下,我们都使用 sx
的缩放因子沿 x
轴缩放元素,使用 sy
的因子沿 y
轴缩放元素。不同的是元素坐标系原点的位置,在 HTML 案例中位于元素的 50% 50%
点,在 SVG 案例中位于 SVG 画布的 0 0
点(我们假设 <svg>
元素内部的元素的任何可能祖先都没有任何变换)。
使用 CSS transform
*属性* 时,我们有三个可用于二维的缩放函数:scale(sx[, sy])
、scaleX(sx)
和 scaleY(sy)
。第一个缩放函数将元素沿 x
轴缩放 sx
,沿 y
轴缩放 sy
。sy
参数是可选的,如果未指定,则假定它等于 sx
,从而使缩放各向同性。sx
和 sy
始终是无单位的值。另外两个函数分别只作用于 x
和 y
方向(由元素的坐标系给出)。scaleX(sx)
等于 scale(sx, 1)
,而 scaleY(sy)
等于 scale(1, sy)
。如果在缩放之前应用了另一个变换,则 x
和 y
方向可能不再是水平和垂直的了。
在 SVG transform
*属性* 的情况下,我们只有一个 scale(sx[ sy])
函数。同样,这里的值也可以用空格分隔,而不仅仅是用逗号分隔,就像在类似的 CSS transform
函数中一样。
因此,对于 SVG 元素,以下两种缩放方法是等效的
• 使用 CSS 变换
rect {
/* doesn't work in IE/ early Edge */
transform: scale(2, 1.5);
}
• 使用 SVG 变换属性
<!-- works everywhere -->
<rect x='65' y='65' width='150' height='80' transform='scale(2 1.5)' />
它们都产生相同的结果,如图 #7 的右侧所示。但如果我们想要在将此精确的缩放函数应用于 HTML 元素时获得相同的效果呢?好吧,我们可以像旋转一样做到这一点。
使用 CSS 变换,我们可以选择在 SVG 元素上设置适当的 transform-origin
,或者在缩放前后链接平移——首先,我们将坐标系平移,使其原点位于 SVG 元素的 50% 50%
点,然后应用缩放,然后反转第一个平移。使用 SVG transform
属性,我们只有链接变换的选择。因此,我们上面的情况的代码将是
• 使用具有 transform-origin
的 CSS 变换(不要这样做)
rect {
/* doesn't work in IE/ early Edge */
transform: scale(2, 1.5);
/* doesn't work as intended in Firefox
* % values are taken relative to the SVG, not the element
* which actually seems to be correct */
transform-origin: 50% 50%;
}
• 使用链接的 CSS 变换
rect {
/* doesn't work in IE/ early Edge */
transform: translate(140px, 105px)
scale(2 1.5)
translate(-140px, -105px);
}
• 使用链接的 transform
函数作为 SVG transform
属性的值
<rect x='65' y='65' width='150' height='80'
transform='translate(140 105) scale(2 1.5) translate(-140 -105)'/>
<!-- works everywhere -->
以下演示说明了链接方法是如何工作的(单击播放 ► 按钮开始)
查看 CodePen 上 Ana Tudor (@thebabydino) 的示例 在 SVG 元素上链接以相对于特定点进行缩放.
关于缩放,还需要记住的一点是,两个连续的 scale()
变换 scale(sx1, sy1) scale(sx2, sy2)
可以写成 scale(sx1*sx2, sy1*sy2)
,而反转 scale(sx1, sy1)
变换是通过 scale(1/sx1, 1/sy1)
变换完成的。如果所有缩放因子的绝对值都等于 1
,那么该缩放就是其自身的逆。
倾斜
沿轴倾斜元素会将元素的每个点(除了正好位于倾斜轴上的点)在该方向上移动一个距离,该距离取决于倾斜角和该点到倾斜轴的距离。这意味着只有沿倾斜轴的坐标会发生变化,而沿另一个轴的坐标保持不变。
与平移或旋转不同,倾斜会扭曲元素,将正方形变成不等边平行四边形,将圆形变成椭圆形。它不保留角度(对于角度为 α
的倾斜,矩形元素的 90°
角变为 90° ± α
)或任何不平行于倾斜轴的线段的长度。但是,元素的面积是保留的。
与平移或旋转不同,倾斜不是加性的。沿轴倾斜元素一个角度 α1
,然后沿同一个轴再倾斜另一个角度 α2
*不等同于* 沿该轴倾斜一个角度 α1 + α2
。
下面的演示说明了倾斜是如何工作的——更改角度和/或轴以查看它如何影响初始正方形。
查看 CodePen 上 Ana Tudor (@thebabydino) 的示例 倾斜变换是如何工作的.
倾斜角是变换应用后轴线最终位置与初始位置之间的角度(不是沿着我们倾斜元素的轴线)。[0°, 90°]
区间内的正倾斜角会将与不变坐标符号相同的数值添加到发生变化的坐标(沿着倾斜轴线的坐标)的初始值,而 [-90°, 0°]
区间内的负值则会添加一个符号与固定坐标相反的数值。
如果我们沿 x
轴进行倾斜,那么对于我们元素的任何点,该点的 y
坐标保持不变,而 x
坐标会根据倾斜角和固定 y
坐标发生变化,变化量为 d
(此演讲 在第 15 分钟左右解释了如何计算 d
的值)。顶边和底边(以及任何其他平行于 x
轴的线段)长度保持不变,而左右两边随着我们增大倾斜角而变长,在 ±90°
角的情况下变为无穷大。一旦超过该值,它们就开始变短,直到我们达到 ±180°
角,此时它们恢复到初始长度。
请注意,使用 (90°, 180°]
区间内的角度 α
进行倾斜的结果等同于使用角度 α - 180°
进行倾斜的结果(最终将落在 (-90°, 0°]
区间内)。此外,使用 (-180°, -90°]
区间内的角度 α
进行倾斜的结果等同于使用角度 α + 180°
进行倾斜的结果(最终将落在 [0°, 90°)
区间内)。
如果我们沿 y
轴进行倾斜,那么对于我们元素的任何点,该点的 x
坐标保持不变,而 y
坐标会根据倾斜角和固定 x
坐标发生变化,变化量为 d
。右边和左边(以及任何其他平行于 y
轴的线段)长度保持不变,而上下两边随着我们增大倾斜角而变长,在 ±90°
角的情况下变为无穷大。一旦超过该值,它们就开始变短,直到我们达到 ±180°
角,此时它们恢复到初始长度。
与缩放一样,倾斜操作的结果取决于元素坐标系原点的位置。从同一个元素开始,沿相同轴线进行两次相同角度的倾斜变换,对于不同的原点将产生不同的结果。

上图显示了 HTML 情况(左)与 SVG 情况(右)。在这两种情况下,我们都沿 x
轴对元素进行相同角度的倾斜。不同之处在于元素坐标系原点的位置,在 HTML 情况下,它位于元素的 50% 50%
点,而在 SVG 情况下,它位于 SVG 画布的 0 0
点。我们假设在 SVG 情况下,<svg>
元素内部的所有可能的元素祖先都没有进行任何变换。
为了简单起见,我们关注元素的其中一个点:右上角。在这两种情况下,y
坐标都保持不变——该点不会垂直移动,只会水平移动。但是,我们看到,在水平方向上,此角在 HTML 情况下向左移动(x
轴的负方向),而在 SVG 情况下向右移动(x
轴的正方向)。并且,右下角在 HTML 和 SVG 情况下都沿着倾斜方向向右移动。那么,这是如何工作的呢?
好吧,如前所述,在沿 x
轴进行倾斜时,任何点的 y
坐标都保持不变,而我们在初始 x
坐标的基础上添加一个 d
值,该值取决于倾斜角和固定 y
坐标。如果倾斜角在 [0°, 90°]
区间内,则该 d
值与固定坐标 y
的符号相同(相对于元素的局部坐标系),如果倾斜角在 [-90°, 0°]
区间内,则该 d
值与固定坐标 y
的符号相反。
在两种情况下,我们的角度都是 60°
,因此右上角 y
坐标的符号才是造成这里差异的原因。在 HTML 情况下,元素坐标系原点位于元素的 50% 50%
点,由于 y
轴向下指向,因此元素右上角的 y
坐标为负。然而,在 SVG 情况下,元素坐标系原点位于 SVG 画布的 0 0
点,因此元素右上角的 y
坐标为正。这意味着在 HTML 情况下,我们在右上角的初始 x
坐标基础上添加一个负值,导致它向左移动,而在 SVG 情况下,我们在右上角的初始 x
坐标基础上添加一个正值,导致它向右移动。
无论我们使用 CSS 变换还是 SVG 变换属性对 SVG 元素进行倾斜,我们都有两个可用的函数:skewX(angle)
和 skewY(angle)
。第一个函数沿 x
轴倾斜元素,而第二个函数沿 y
轴倾斜元素。
对于 CSS transform
属性,angle
是带有单位的值。它可以用度数 (deg
)、弧度 (rad
)、圈数 (turn
)、梯度 (grad
) 表示,甚至可以使用 calc()
组合这些单位(但请记住,此时在 Blink 浏览器中,使用带角度单位的 calc()
仅有效)。
使用 SVG transform
属性倾斜元素时,我们的 angle
值始终是无单位的度数值。
这意味着我们有两种等效的方法来倾斜 SVG 元素(结果可以在上图右侧看到)。
• 使用 CSS 变换
rect {
transform: skewX(60deg); /* doesn't work in IE/ early Edge */
}
• 使用 SVG 变换属性
<!-- works everywhere -->
<rect x='65' y='65' width='150' height='80' transform='skewX(60)' />
如果我们想要与对 HTML 元素应用此确切的倾斜函数时获得的效果相同的效果,我们可以使用三种方法来实现,就像缩放一样。
• 使用具有 transform-origin
的 CSS 变换(不要这样做)
rect {
/* doesn't work in IE/ early Edge */
transform: skewX(60deg);
/* doesn't work as intended in Firefox
* % values are taken relative to the SVG, not the element
* which actually seems to be correct */
transform-origin: 50% 50%;
}
• 使用链接的 CSS 变换
rect {
/* doesn't work in IE/ early Edge */
transform: translate(140px, 105px)
skewX(60deg)
translate(-140px, -105px);
}
• 使用链接的变换函数作为 SVG 变换属性的值
<!-- works everywhere -->
<rect x='65' y='65' width='150' height='80'
transform='translate(140 105) skewX(60) translate(-140 -105)' />
以下演示说明了链接方法的工作原理。
查看 CodePen 上 Ana Tudor (@thebabydino) 的 在 SVG 元素上进行链接以相对于特定点进行倾斜 演示。
缩短链
好了,链接变换可以完成工作。我们可以旋转、缩放和倾斜 SVG 元素,它们的运行方式与 HTML 元素相同。并且,如果我们将链接的变换用作 SVG 属性的值,我们甚至可以在 IE 中获得我们想要的结果。但是,这太难看了!难道没有更简单的方法吗?
好吧,如果我们从将 50% 50%
点放置在 SVG 画布的 0 0
点的 SVG 矩形开始,我们可以从链中删除一个平移,将旋转代码缩减为
<rect x='-75' y='-40' width='150' height='80'
transform='translate(140 105) rotate(45)'/>
<!-- 75 = 150/2, 40 = 80/2 -->
查看 CodePen 上 Ana Tudor (@thebabydino) 的
在 SVG 元素上进行链接以相对于特定点进行旋转 #1 演示。
我们也可以通过适当选择 <svg>
元素上的 viewBox
属性来去除第一个平移,该元素包含我们的矩形。viewBox
属性有四个用空格分隔的组件。前两个指定 SVG 画布左上角的 x
和 y
坐标(以用户单位表示),后两个指定其 width
和 height
(以用户单位表示)。如果没有指定 viewBox
属性,则左上角的坐标将为 0 0
。
下面,您可以看到没有指定 viewBox
的 <svg>
元素与指定了 viewBox='-140 -105 280 210'
的 <svg>
元素之间的区别。

viewBox
的 <svg>
元素与指定了 viewBox
的 <svg>
元素回到我们的示例,如果我们将 viewBox
设置为 SVG 画布的 0 0
点位于我们想要放置矩形 50% 50%
点的位置,我们的代码将变为
<svg viewBox='-140 -105 650 350'>
<rect x='-75' y='-40' width='150' height='80' transform='rotate(45)'/>
</svg>
查看 CodePen 上 Ana Tudor (@thebabydino) 的 设置适当的 `viewBox` 以相对于特定点进行旋转 #1 演示。
实际应用
将 SVG 画布的 0 0
点以及我们可能想要的任何其他元素都置于中间,可以更轻松地使用变换,因为它使 SVG 画布的 0 0
点与我们要变换的元素的 50% 50%
点重合。以下演示(单击以播放/暂停)显示了三个四角星,它们最初位于中间,然后旋转、平移、倾斜和缩放,无需设置 transform-origin
或在链中添加额外的平移。
查看 CodePen 上 Ana Tudor (@thebabydino) 的 SVG Stars – 最终 演示。
让我们逐步了解这个演示的工作原理。
星形本身是一个具有八个点的多边形。下面的演示展示了它们相对于 SVG 画布的原点 (0 0
) 的位置。将鼠标悬停在代码中的 x,y
对或点本身,查看哪个对应哪个。
查看 CodePen 上 Ana Tudor (@thebabydino) 的 4 角星——点 演示。
我们有三个这样的星星。我们不会重复三次多边形的代码——而是将它放在 <defs>
内,并在后面三次 <use>
它。
<svg viewBox='-512 -512 1024 1024'>
<defs>
<polygon id='star' points='250,0 64,64 0,250 -64,64 -250,0 -64,-64 0,-250 64,-64'/>
</defs>
<g>
<use xlink:href='#star'/>
<use xlink:href='#star'/>
<use xlink:href='#star'/>
</g>
</svg>
我们做的第一件事是从 0
到 1
缩放我们的星星。
use {
animation: ani 4s linear infinite;
}
@keyframes ani {
0% { transform: scale(0); }
25%, 100% { transform: scale(1); }
}
这将给我们以下结果。
查看 CodePen 上 Ana Tudor (@thebabydino) 的 SVG Stars – 步骤 #1 演示。
接下来我们要做的就是给我们的关键帧动画添加一些旋转。但我们希望每颗星星都有不同的旋转——比如随机角度加上根据星星索引计算出的特定角度。这意味着我们不能对所有三个星星使用相同的关键帧 `animation`;我们需要三个不同的动画。这也有助于我们使填充不同。
$n: 3;
$α: 360deg/$n;
$β: random($α/1deg)*1deg;
@for $i from 1 through $n {
$γ: $β + ($i - 1)*$α;
use:nth-of-type(#{$i}) {
fill: hsl($γ, 100%, 80%);
animation: ani-#{$i} 4s linear infinite;
}
@keyframes ani-#{$i} {
0% { transform: scale(0); }
25% { transform: scale(1); }
50%, 100% { transform: rotate($γ); }
}
}
您可以在下面看到此代码的结果
查看笔:SVG 星星 - 第 2 步 由 Ana Tudor (@thebabydino) 在 CodePen 上。
下一步是平移和缩小我们的星星
@keyframes ani-#{$i} {
0% { transform: scale(0); }
25% { transform: scale(1); }
50% { transform: rotate($γ); }
75%, 100% {
transform: rotate($γ) translate(13em) scale(.2);
}
}
查看笔:SVG 星星 - 第 3 步 由 Ana Tudor (@thebabydino) 在 CodePen 上。
我们快到了!我们只需要倾斜我们的星星,并使用一个 scale `transform` 来校正它们在倾斜后的尺寸。
@keyframes ani-#{$i} {
0% { transform: scale(0); }
25% { transform: scale(1); }
50% { transform: rotate($γ); }
75% {
transform: rotate($γ) translate(13em) scale(.2);
}
83% {
transform: rotate($γ) translate(13em) scale(.2)
skewY(30deg) scaleX(.866);
}
91% {
transform: rotate($γ) translate(13em) scale(.2)
skewY(60deg) scaleX(.5);
}
100% {
transform: rotate($γ) translate(13em) scale(.2)
skewY(90deg) scaleX(0);
}
}
在这里,我添加了多个新的关键帧以提高精度。虽然倾斜角线性变化,但校正比例因子没有——它的值是倾斜角的余弦,如下一张图所示,余弦函数的图形在 `0°` 和 `90°` 之间不是直线。

然而,这个纯 CSS 演示在 Firefox 中有点错误,在 IE 中根本无法使用,因为没有 IE 版本支持 SVG 元素的 CSS 变换。如果我们使用 SVG 变换属性并使用 JavaScript 动画其值变化,我们可以解决所有这些问题。您可以在下面看到 JavaScript 版本(点击启动)。
查看笔:SVG 星星 - 第 3 步 由 Ana Tudor (@thebabydino) 在 CodePen 上。
哇,Ana 解释得真好!
正如你所演示的,动画可能非常具有挑战性,因为浏览器之间存在差异。像你一样,我也得出结论,唯一能够很好地完成它的方法是使用 JS。有趣的是,这样做使运动看起来更流畅,特别是在 iOS 设备上,与 CSS 相比。
好奇:你是否见过 GSAP 在解决这些问题方面做了什么?例如,transform-origin 困境:https://css-tricks.org.cn/svg-animation-on-css-transforms/
再次感谢你将这个关于 SVG 变换的深入分析整理在一起。
是的,我记得那篇文章,首先是因为它是在我开始写这篇文章的时候出现的(现在已经用 GitHub 检查过了,告诉我 10 月 30 日,不到 2 周...是的,我写作很烂 :P ),其次是因为那是我第一次看到 `getBBox()`,我后来发现它在定位文本方面非常有用。
我更像是一个技术宅,所以我从未在图形编辑器中创建过 SVG,我想我只使用过一次现成的 SVG。
我主要通过 JavaScript 生成它们,因为我可能想要一些更复杂的东西,而手动编写会比我能承受的更疯狂。这通常意味着所有内容都是按照规则生成的(即使我在那里引入了随机性,它仍然不会像马一样——抱歉,克里斯,你在 SVG 演示中提到的马在我的脑子里挥之不去),很多时候,它会涉及许多非常相似的组件,我只需要在 `<defs>` 中创建一次,定位在 SVG 的中间,然后 `use` 一些次数,并使用变换将其定位到正确的位置。因此,对于几何形状之类的东西,我甚至在将相应的 `use` 元素添加到父 SVG 之前,就已经了解了它们所有尺寸以及我将对其应用的变换,但有时我也会需要一些文本,在这种情况下,我真的很需要 `getBBox()`,因为我事先不知道文本的尺寸,需要根据这些尺寸计算位置,以便它被放置在我想要的位置。
在得到使用该函数的想法之前,文本一直是我最大的 SVG 噩梦,所以也要感谢你!:)
Ana,干得漂亮。
(如果一张图片值 1k 字,那么动画解释的价值是多少?)
很棒的文章!
很棒的文章,很棒的解释,谢谢。
Ana,超级棒的文章,我已经使用 GSAP 来解决 SVG 动画的 transform-origin 问题以及其他一些问题有一段时间了。
实际上,理解内部机制并确定这个问题存在的原因以及我们如何在不依赖插件的情况下自己解决这些问题,这是很好的。
GSAP 很棒,但如果我们只用它来修复具有非常简单的变换动画的 svg 的 transform-origin,它可能有点笨重。
这是我婚礼的网站 http://yennyetygustavo.com/,我使用 SVG 图片和 GSAP 动画。Firefox 搞乱了我的 transform origin,使用一个小的 js 函数我能够解决它。
不错,Gustavo。我只想指出你使用的是一个非常旧版本的 GSAP,它早于我们实现的所有 SVG 解决方法。如果你更新到最新版本,它将自动处理所有 transform-origin 问题,包括在 Firefox 中...我们在这篇文章中解释了它:https://css-tricks.org.cn/svg-animation-on-css-transforms/
如果演示按照 SVG 中发生的顺序从右到左应用变换,那么它们会更具说明性。