为什么变换顺序很重要?旋转/缩放不会给出与缩放/旋转相同的结果



在梳理了SVG规范以及诸如this和this之类的指南之后,我仍然很难准确理解链接转换是如何工作的。

选定的相关报价

将transform属性应用于SVG元素时,该元素获取正在使用的当前用户坐标系的"副本"。

和:

当转换被链接时,最重要的是要注意是的,就像HTML元素转换一样变换应用于坐标系之后的坐标系由以前的转换转换。

和:

例如,如果要对元素应用旋转,然后是翻译,翻译根据新的坐标系,而不是最初的非旋转坐标系。

和:

转换的顺序很重要。序列转换函数在transform属性中指定是它们应用于形状的顺序。

代码

缩放第一个矩形的当前坐标系,然后旋转(注意顺序)。旋转第二个矩形的当前坐标系,然后缩放。

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轴之间的比例相同,这样以后进行缩放时不会看到任何不良影响,但仅缩放一个轴会破坏比例,因此当我们尝试应用旋转时,我们的形状看起来很糟糕。


如果您想了解有关变换如何链接以及矩阵如何计算的更多详细信息,可以查看此链接:https://www.w3.org/TR/css-transforms-1/#transform-渲染。它是关于HTML元素的,但正如SVG规范中所说的一样。

以下是相关部分:

转换是累积的。也就是说,图元在其父图元的坐标系内建立其局部坐标系。

从用户的角度来看,一个元素有效地积累了其祖先的所有变换属性,以及应用于它的任何局部变换


让我们计算一下,看看这两种转换之间的区别。让我们考虑矩阵乘法,因为我们处理的是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解释它的方式遵循每个变换所跨越的坐标系。从视口开始,每个连续的坐标系都会派生出来,并位于画布上不同的位置。这些坐标系可能不是笛卡尔坐标系(一个"拉伸的宇宙")。它们在DOM树中从外向内构建,当链接在属性中时,从左到右。

但你可以想象,同样的变换也会朝着相反的方向,从内到外:首先,你在笛卡尔用户空间坐标系中绘制一个矩形,然后通过一系列缩放、旋转等方式进行变换,直到在视口坐标系中绘图时,它被扭曲成其他东西。

但如果你从第二个角度来看,属性中的链式转换需要从右到左进行处理:transform: scale(2, 1) rotate(10deg)意味着取一个矩形,首先将其旋转10度,然后在水平方向上缩放旋转的矩形。

简而言之,这两者是等价的:

  • 如果在变换后的坐标系中绘制图形,请通过从左到右对这些坐标系应用变换来构建坐标系
  • 如果在原始坐标系中绘制变换的图形,请通过从右到左对图形应用变换来构造图形

最新更新