css - 为什么变换的顺序很重要? SVG旋转/缩放不会产生与缩放/旋转相同的结果




css3 css-transforms (2)

Temani Afif解释它的方式遵循每个转换跨越的坐标系。 从视口开始,每个连续坐标系都是派生的,并且位于画布上不同的位置。 这些坐标系可能不是笛卡尔坐标(“拉伸的宇宙”)。 它们从外部构建在DOM树中,当从属性链接到左侧到右侧时。

但你可以想象同样的变换也是在相反的方向,从内到外:首先你在它的笛卡尔用户空间坐标系中绘制一个矩形,然后通过一系列比例,旋转等来转换它,直到绘制它为止在视口坐标系中,它被扭曲为其他东西。

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

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

  • 如果在 变换坐标系中 绘制grafic,则通过 从左到右 对这些坐标系应用变换来构造坐标系。
  • 如果在原始坐标系中绘制 变换的grafic ,则通过将变换应用于 从右到左 的grafic来构造grafic。

在梳理了 SVG规范 以及此类和此类指南之后,我仍然在努力理解链接变换的工作方式。

选定的相关行情

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

和:

当转换被链接时,最重要的是要注意,就像HTML元素转换一样,在通过先前的转换转换系统之后,每个转换都应用于坐标系。

和:

例如,如果要对元素应用旋转,然后进行平移,则根据新坐标系进行平移,而不是初始非旋转坐标系。

和:

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

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

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-rendering ://www.w3.org/TR/css-transforms-1/#transform-rendering。 它是关于HTML元素的,但正如SVG规范中所述,它是相同的。

以下是相关部分:

转化是累积的。 也就是说,元素在其父元素的坐标系内建立它们的局部坐标系。

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

让我们做一些数学运算,看看两种变换之间的区别。 让我们考虑矩阵乘法,因为我们正在处理2D线性变换,为简单起见,我们将在ℝ²上执行此操作。

对于 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轴的情况下缩放的。

1 CSS也使用仿射变换(如翻译),所以使用ℝ²(笛卡尔坐标)不足以执行我们的计算,所以我们需要考虑ℝℙ²(齐次坐标)。 我们之前的计算将是:

 |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|

在这种情况下,没有什么会改变,因为仿射部分为 但如果我们将翻译与另一个转换相结合(例如: 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) 




css-transforms