如何组合元素变换?

4
在HTML5中,我希望为画布元素实现变换功能,以便用户可以对画布元素进行平移(移动),缩放(放大/缩小)和旋转。每种变换都可以使用不同的变换原点来完成。
第一个变换很容易实现:
function transform(el, value, origin) {
  el.style.Transform = value;
  el.style.MozTransform = value;
  el.style.msTransform = value;
  el.style.OTransform = value;
  el.style.webkitTransform = value;
  el.style.TransformOrigin = origin;
  el.style.MozTransformOrigin = origin;
  el.style.msTransformOrigin = origin;
  el.style.OTransformOrigin = origin;
  el.style.webkitTransformOrigin = origin;
}

transform(myCanvas, 'translate('+ dx +'px, ' + dy + 'px) ' +
                    'scale(' + zoom + ', ' + zoom + ') ' +
                    'rotate(' + angle + 'deg)',
                    cx + 'px ' + cy + 'px');

用户会对元素进行移动、缩放或旋转,但不是所有参数都会同时发生变化,因此一些转换参数将保持默认值 (dx=0, dy=0, zoom=1, angle=0)

在这样的转换之后,如果用户想进行另一个转换(以及另一个、另一个……),我该如何将(dx1、dy1、zoom1、angle1、cx1、cy1)(dx2、dy2、zoom2、angle2、cx2、cy2)合并,以获得最终值,然后再将其与新的转换参数结合?我无法向transform参数添加另一个转换,因为tranform-origin可能会有所不同。有没有公式可以将具有不同变换中心点的变换组合起来?


你尝试过将其可视化吗?在纸上画一个盒子。然后,考虑当你想要翻译/旋转/缩放盒子时会发生什么。再来一次。 - Rob W
@RobW - 我已经尝试了一个星期很多次 :-/ 我能够组合无限的移动(translate)和缩放(scale)变换,但一旦涉及旋转,我就迷失了... - Ωmega
我不确定线性代数标签是否适用于此问题。这更涉及将CSS属性组合而不是使用线性代数。 - TylerH
2个回答

9

根据CSS变换规范,您不必学习矩阵数学。

  1. 从单位矩阵开始。
  2. 通过“transform-origin”的计算X、Y和Z值进行平移。
  3. 依次使用“transform”属性中的每个变换函数进行乘法运算
  4. 通过“transform-origin”的计算负值X、Y和Z值进行平移

换句话说,transform-origin: A; transform: Btransform: translate(-A) B translate(A)相同。(变换从右到左应用,因此您想发生的第一件事放在最后。)

因此,使用上述规则消除transform-origin,现在您只有普通的变换可以连接。

示例:

  1. transform-origin: 5px 5px; transform: translate(10px, 40px)
  2. transform-origin: 25px 30px; transform: scale(2)
  3. transform-origin: 10px 10px; transform: rotate(30deg)

变成:

  1. transform: translate(-5px, -5px) translate(10px, 40px) translate(5px, 5px)
  2. transform: translate(-25px, -30px) scale(2) translate(25px, 30px)
  3. transform: translate(-10px, -10px) rotate(30deg) translate(10px, 10px)

现在可以组合它们,因为它们都同意没有原点。

transform: translate(-5px, -5px) translate(10px, 40px) translate(5px, 5px) translate(-25px, -30px) scale(2) translate(25px, 30px) translate(-10px, -10px) rotate(30度) translate(10px, 10px)

当然,如果你想的话,可以折叠连续的转换

transform: translate(-15px, 10px) scale(2) translate(15px, 20px) rotate(30度) translate(10px, 10px)

或者您可以挖出数学课本并计算最终变换矩阵

编辑:转换是从右向左应用的。


我们中的任何一个人都没有听清 Omega 想要什么。我认为问题在于已知 3 个转换,但它们的起源被之前的变换所改变。另外,我还没有使用过 CSS 的 transform,但你不应该先 减去 起点,然后再添加吗? - MaxArt
@MaxArt 我也认为负数应该先出现,但规范说不是这样的。也许这是一个规范上的错误。尝试两种方式,看哪一种更好。 - Raymond Chen
不错,我没想到在一行代码中可以使用多个同名函数。 - Rob W
1
@RobW 所有的函数名称都只是 matrix(...) 函数的简写。此外,在 CSS 转换规范本身中也有一个例子 transform-function-lists - Raymond Chen

1

你需要处理矩阵变换。

每个线性操作都可以用一个3x3的矩阵和一个3x1的向量来表示,你可以将它们应用于平面上的点。如果p是一个点,M是矩阵,q是另一个向量,那么每个线性变换都可以表示为Mp + q

如果你有一个2D的点,那么它的向量将是[x; y; 1](一个竖直向量),而矩阵可以有多种形式。

对于平移,矩阵只是单位矩阵。向量q是平移向量。

对于缩放,M就像

    [a 0 0]
M = [0 b 0]
    [0 0 1]

其中ab分别是xy的缩放因子。向量q为空。

对于旋转,例如一个角度a,您将得到:

    [cos(a) -sin(a) 0]
M = [sin(a)  cos(a) 0]
    [  0       0    1]

还有q再次为空。

也有用于倾斜的矩阵。因此,如果您必须应用三个连续的变换,则必须应用这些线性变换。您的问题是还必须处理原点,因此必须从p中减去向量o,然后应用M,添加q,然后再次添加o

假设您有这些变换(M1,q1,o1)和(M2,q2,o2)。当您应用第一个时,您会得到

p1 = M1 * (p - o1) + q1 + o1

然后您需要应用第二个转换:

p2 = M2 * (p1 - o2) + q2 + o2

最终你会得到:
p2 = M2 * (M1 * (p - o1) + q1 + o1 - o2) + q2 + o2

接下来是第三个(M3,q3,o3)。

一团糟?看起来是这样。但如果你知道矩阵的样子,事情就可以简化一些。

现在,做数学运算,并将其应用于transform: matrix(a, b, c, d, tx, ty)


在上一个公式中,我准确地写出了如何处理原点。请记住,虽然矩阵乘积不满足交换律,但它仍是线性的。因此,p2 = M2*M1*(p - o1) + M2*(q1+o1-o2) + q2+o2 - MaxArt
只是为了明确,您的组合变换具有M2xM1作为矩阵,o1作为原点,以及M2(q1+o1-o2) + q2+o2作为平移向量。 - MaxArt
你很聪明,但我不是。抱歉。我只有用于转换的矩阵(M1、M2等)和原点(o1、o2等)。我不想计算每个点(p),我想计算最终矩阵(M),它可以作为多个转换的组合使用。我有什么遗漏吗? - Ωmega
不,先生,您是正确的。但您需要计算矩阵和平移向量,然后在transform: matrix(...)中使用它们的值。我的建议是创建一个函数来组合两个线性变换,知道它们的类型(缩放、旋转、平移)和参数。 - MaxArt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接