为什么转换顺序很重要? rotate/scale 与 scale/rotate 的结果不同
Why does order of transforms matter? rotate/scale doesn't give the same result as scale/rotate
梳理完 SVG specification, and guides such as this and this 后,我仍在努力理解链接转换的确切工作原理。
精选相关语录
When you apply the transform attribute to an SVG element, that element
gets a "copy" of the current user coordinate system in use.
并且:
When transformations are chained, the most important thing to be aware
of is that, just like with HTML element transformations, each
transformation is applied to the coordinate system after that system
is transformed by the previous transformations.
并且:
For example, if you’re going to apply a rotation to an element,
followed by a translation, the translation happens according to the
new coordinate system, not the inital non-rotated one.
并且:
The sequence of transformations matter. The sequence the
transformation functions are specified inside the transform attribute
is the sequence they are applied to the shape.
代码
先缩放第一个矩形的当前坐标系,再旋转(注意顺序)。第二个矩形的当前坐标系旋转,然后缩放。
svg {
border: 1px solid green;
}
<svg xmlns="http://www.w3.org/2000/svg">
<style>
rect#s1 {
fill: red;
transform: scale(2, 1) rotate(10deg);
}
</style>
<rect id="s1" x="" y="" width="100" height="100" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg">
<style>
rect#s2 {
fill: blue;
transform: rotate(10deg) scale(2, 1);
}
</style>
<rect id="s2" x="" y="" width="100" height="100" />
</svg>
问题
我们知道,当我们进行链式变换时,会复制该元素当前使用的坐标系,然后按照指定的顺序应用变换。
当我们有一个已经缩放的用户坐标系,并且我们对其应用旋转时,矩形(如所见)有效地倾斜(注意改变的角度)。如果我们以相反的方式进行两个变换(旋转,然后缩放),则不会发生这种情况。
请高手指点一下缩放后的当前坐标系是如何旋转的,不胜感激。我试图从技术(内部工作)角度理解第一个矩形中发生倾斜的确切原因。
谢谢。
为了说明它是如何工作的,让我们考虑一个动画来展示缩放效果如何改变旋转。
.red {
width:80px;
height:20px;
background:red;
margin:80px;
transform-origin:left center;
animation: rotate 2s linear infinite;
}
@keyframes rotate {
from{transform:rotate(0)}
to{transform:rotate(360deg)}
}
<div class="container">
<div class="red">
</div>
</div>
正如您在上面看到的,旋转正在创建一个完美的圆形。
现在让我们缩放容器并查看差异:
.red {
width:80px;
height:20px;
background:red;
margin:80px;
transform-origin:left center;
animation: rotate 5s linear infinite;
}
@keyframes rotate {
from{transform:rotate(0)}
to{transform:rotate(360deg)}
}
.container {
display:inline-block;
transform:scale(3,1);
transform-origin:left center;
}
<div class="container">
<div class="red">
</div>
</div>
注意我们不再有一个圆,而是一个椭圆。就像我们拿了一个圆圈然后对它进行拉伸,这在我们的矩形内部产生了倾斜效果。
如果我们做相反的效果,从缩放效果开始,然后应用旋转,我们将不会有任何倾斜。
.red {
width:80px;
height:20px;
background:red;
margin:80px;
animation: rotate 2s linear infinite;
}
@keyframes rotate {
from{transform:scale(1,1)}
to{transform:scale(3,1)}
}
.container {
display:inline-block;
transform:rotate(30deg);
transform-origin:left center;
}
<div class="container">
<div class="red">
</div>
</div>
换句话说:应用旋转将在 X 轴和 Y 轴之间保持相同的比率,因此您在稍后进行缩放时不会看到任何不良影响,但仅缩放一个轴会破坏比率,因此我们的形状当我们尝试应用旋转时看起来很糟糕。
如果您想了解有关如何链接变换以及如何计算矩阵的更多详细信息,可以查看此 link:https://www.w3.org/TR/css-transforms-1/#transform-rendering。它与 HTML 元素有关,但正如 SVG 规范中所述,它是相同的。
相关部分如下:
Transformations are cumulative. That is, elements establish their local coordinate system within the coordinate system of their parent.
From the perspective of the user, an element effectively accumulates all the transform properties of its ancestors as well as any local transform applied to it
让我们做一些数学运算,看看这两种转换之间的区别。让我们考虑矩阵乘法,因为我们正在处理 2D 线性变换,为了简单起见,我们将在 ℝ² 上执行此操作1.
对于scale(2, 1) rotate(10deg)
,我们将有
|2 0| |cos(10deg) -sin(10deg)| |2*cos(10deg) -2*sin(10deg) |
|0 1| x |sin(10deg) cos(10deg) | = |1*sin(10deg) 1*cos(10deg) |
现在,如果我们将此矩阵应用于 (Xi,Yi)
,我们将获得 (Xf,Yf)
,如下所示:
Xf = 2* (Xi*cos(10deg) - Yi*sin(10deg))
Yf = Xi*sin(10deg) + Yi*cos(10deg)
请注意 Xf
如何有一个额外的乘数,这是造成偏斜效应的罪魁祸首。就像我们改变了行为或 Xf
并保留了 Yf
现在让我们考虑rotate(10deg) scale(2, 1)
:
|cos(10deg) -sin(10deg)| |2 0| |2*cos(10deg) -1*sin(10deg) |
|sin(10deg) cos(10deg) | x |0 1| = |2*sin(10deg) 1*cos(10deg) |
然后我们将有
Xf = 2*Xi*cos(10deg) - Yi*sin(10deg)
Yf = 2*Xi*sin(10deg) + Yi*cos(10deg)
我们可以将 2*Xi
视为 Xt
,我们可以说我们旋转了 (Xt,Yi
) 元素,该元素最初是根据 X 轴缩放的。
1CSS 也使用仿射变换(如平移)所以使用 ℝ²(笛卡尔坐标)是不够的执行我们的计算,所以我们需要考虑ℝℙ²(齐次坐标)。我们之前的计算将是:
|2 0 0| |cos(10deg) -sin(10deg) 0| |2*cos(10deg) -2*sin(10deg) 0|
|0 1 0| x |sin(10deg) cos(10deg) 0| = |1*sin(10deg) 1*cos(10deg) 0|
|0 0 1| |0 0 1| |0 0 1|
在这种情况下什么都不会改变,因为仿射部分是 null 但是如果我们有一个翻译与另一个转换相结合(例如:scale(2, 1) translate(10px,20px)
)我们将有以下:
|2 0 0| |1 0 10px| |2 0 20px|
|0 1 0| x |0 1 20px| = |0 1 20px|
|0 0 1| |0 0 1 | |0 0 1 |
和
Xf = 2*Xi + 20px;
Yf = Yi + 20px;
1 = 1 (to complete the multiplication)
Temani Afif 解释它的方式遵循每个变换跨越的坐标系。您从视口开始,每个连续的坐标系都派生出来并位于 canvas 上的不同位置。这些坐标系可能不是笛卡尔坐标系 (a "stretched universe")。它们在 DOM 树中从外到内构造,当链接到属性中时,从左到右。
但是你可以想象同样的变换也在相反的方向上,从里到外:首先你在它的笛卡尔用户空间坐标系中画一个矩形,然后你通过一系列的缩放、旋转等等来变换它, 直到在视口坐标系中绘制它时,它被扭曲成其他东西。
但是如果你从第二种方式来看,属性中的链式转换需要从右到左处理:transform: scale(2, 1) rotate(10deg)
表示取一个矩形,首先 将其旋转 10 度,然后 然后 在水平方向缩放旋转的矩形。
简而言之,这两个是等价的:
- 如果您在变换后的坐标系中绘制图形,请通过对这些坐标系从左到右[=30]应用变换来构建坐标系=].
- 如果您在原始坐标系中绘制转换后的图形,请通过对图形应用转换来构建图形从右到左.
梳理完 SVG specification, and guides such as this and this 后,我仍在努力理解链接转换的确切工作原理。
精选相关语录
When you apply the transform attribute to an SVG element, that element gets a "copy" of the current user coordinate system in use.
并且:
When transformations are chained, the most important thing to be aware of is that, just like with HTML element transformations, each transformation is applied to the coordinate system after that system is transformed by the previous transformations.
并且:
For example, if you’re going to apply a rotation to an element, followed by a translation, the translation happens according to the new coordinate system, not the inital non-rotated one.
并且:
The sequence of transformations matter. The sequence the transformation functions are specified inside the transform attribute is the sequence they are applied to the shape.
代码
先缩放第一个矩形的当前坐标系,再旋转(注意顺序)。第二个矩形的当前坐标系旋转,然后缩放。
svg {
border: 1px solid green;
}
<svg xmlns="http://www.w3.org/2000/svg">
<style>
rect#s1 {
fill: red;
transform: scale(2, 1) rotate(10deg);
}
</style>
<rect id="s1" x="" y="" width="100" height="100" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg">
<style>
rect#s2 {
fill: blue;
transform: rotate(10deg) scale(2, 1);
}
</style>
<rect id="s2" x="" y="" width="100" height="100" />
</svg>
问题
我们知道,当我们进行链式变换时,会复制该元素当前使用的坐标系,然后按照指定的顺序应用变换。
当我们有一个已经缩放的用户坐标系,并且我们对其应用旋转时,矩形(如所见)有效地倾斜(注意改变的角度)。如果我们以相反的方式进行两个变换(旋转,然后缩放),则不会发生这种情况。
请高手指点一下缩放后的当前坐标系是如何旋转的,不胜感激。我试图从技术(内部工作)角度理解第一个矩形中发生倾斜的确切原因。
谢谢。
为了说明它是如何工作的,让我们考虑一个动画来展示缩放效果如何改变旋转。
.red {
width:80px;
height:20px;
background:red;
margin:80px;
transform-origin:left center;
animation: rotate 2s linear infinite;
}
@keyframes rotate {
from{transform:rotate(0)}
to{transform:rotate(360deg)}
}
<div class="container">
<div class="red">
</div>
</div>
正如您在上面看到的,旋转正在创建一个完美的圆形。
现在让我们缩放容器并查看差异:
.red {
width:80px;
height:20px;
background:red;
margin:80px;
transform-origin:left center;
animation: rotate 5s linear infinite;
}
@keyframes rotate {
from{transform:rotate(0)}
to{transform:rotate(360deg)}
}
.container {
display:inline-block;
transform:scale(3,1);
transform-origin:left center;
}
<div class="container">
<div class="red">
</div>
</div>
注意我们不再有一个圆,而是一个椭圆。就像我们拿了一个圆圈然后对它进行拉伸,这在我们的矩形内部产生了倾斜效果。
如果我们做相反的效果,从缩放效果开始,然后应用旋转,我们将不会有任何倾斜。
.red {
width:80px;
height:20px;
background:red;
margin:80px;
animation: rotate 2s linear infinite;
}
@keyframes rotate {
from{transform:scale(1,1)}
to{transform:scale(3,1)}
}
.container {
display:inline-block;
transform:rotate(30deg);
transform-origin:left center;
}
<div class="container">
<div class="red">
</div>
</div>
换句话说:应用旋转将在 X 轴和 Y 轴之间保持相同的比率,因此您在稍后进行缩放时不会看到任何不良影响,但仅缩放一个轴会破坏比率,因此我们的形状当我们尝试应用旋转时看起来很糟糕。
如果您想了解有关如何链接变换以及如何计算矩阵的更多详细信息,可以查看此 link:https://www.w3.org/TR/css-transforms-1/#transform-rendering。它与 HTML 元素有关,但正如 SVG 规范中所述,它是相同的。
相关部分如下:
Transformations are cumulative. That is, elements establish their local coordinate system within the coordinate system of their parent.
From the perspective of the user, an element effectively accumulates all the transform properties of its ancestors as well as any local transform applied to it
让我们做一些数学运算,看看这两种转换之间的区别。让我们考虑矩阵乘法,因为我们正在处理 2D 线性变换,为了简单起见,我们将在 ℝ² 上执行此操作1.
对于scale(2, 1) rotate(10deg)
,我们将有
|2 0| |cos(10deg) -sin(10deg)| |2*cos(10deg) -2*sin(10deg) |
|0 1| x |sin(10deg) cos(10deg) | = |1*sin(10deg) 1*cos(10deg) |
现在,如果我们将此矩阵应用于 (Xi,Yi)
,我们将获得 (Xf,Yf)
,如下所示:
Xf = 2* (Xi*cos(10deg) - Yi*sin(10deg))
Yf = Xi*sin(10deg) + Yi*cos(10deg)
请注意 Xf
如何有一个额外的乘数,这是造成偏斜效应的罪魁祸首。就像我们改变了行为或 Xf
并保留了 Yf
现在让我们考虑rotate(10deg) scale(2, 1)
:
|cos(10deg) -sin(10deg)| |2 0| |2*cos(10deg) -1*sin(10deg) |
|sin(10deg) cos(10deg) | x |0 1| = |2*sin(10deg) 1*cos(10deg) |
然后我们将有
Xf = 2*Xi*cos(10deg) - Yi*sin(10deg)
Yf = 2*Xi*sin(10deg) + Yi*cos(10deg)
我们可以将 2*Xi
视为 Xt
,我们可以说我们旋转了 (Xt,Yi
) 元素,该元素最初是根据 X 轴缩放的。
1CSS 也使用仿射变换(如平移)所以使用 ℝ²(笛卡尔坐标)是不够的执行我们的计算,所以我们需要考虑ℝℙ²(齐次坐标)。我们之前的计算将是:
|2 0 0| |cos(10deg) -sin(10deg) 0| |2*cos(10deg) -2*sin(10deg) 0|
|0 1 0| x |sin(10deg) cos(10deg) 0| = |1*sin(10deg) 1*cos(10deg) 0|
|0 0 1| |0 0 1| |0 0 1|
在这种情况下什么都不会改变,因为仿射部分是 null 但是如果我们有一个翻译与另一个转换相结合(例如:scale(2, 1) translate(10px,20px)
)我们将有以下:
|2 0 0| |1 0 10px| |2 0 20px|
|0 1 0| x |0 1 20px| = |0 1 20px|
|0 0 1| |0 0 1 | |0 0 1 |
和
Xf = 2*Xi + 20px;
Yf = Yi + 20px;
1 = 1 (to complete the multiplication)
Temani Afif 解释它的方式遵循每个变换跨越的坐标系。您从视口开始,每个连续的坐标系都派生出来并位于 canvas 上的不同位置。这些坐标系可能不是笛卡尔坐标系 (a "stretched universe")。它们在 DOM 树中从外到内构造,当链接到属性中时,从左到右。
但是你可以想象同样的变换也在相反的方向上,从里到外:首先你在它的笛卡尔用户空间坐标系中画一个矩形,然后你通过一系列的缩放、旋转等等来变换它, 直到在视口坐标系中绘制它时,它被扭曲成其他东西。
但是如果你从第二种方式来看,属性中的链式转换需要从右到左处理:transform: scale(2, 1) rotate(10deg)
表示取一个矩形,首先 将其旋转 10 度,然后 然后 在水平方向缩放旋转的矩形。
简而言之,这两个是等价的:
- 如果您在变换后的坐标系中绘制图形,请通过对这些坐标系从左到右[=30]应用变换来构建坐标系=].
- 如果您在原始坐标系中绘制转换后的图形,请通过对图形应用转换来构建图形从右到左.