【问题标题】:CSS3 transform order matters: rightmost operation firstCSS3变换顺序很重要:最右边的操作优先
【发布时间】:2015-02-22 11:24:55
【问题描述】:

当我们使用 CSS3 transform: operation1(...) operation2(...) 时,先做哪一个?

完成的第一个操作似乎是最右边的操作。,即这里operation2operation1 之前完成。 只是为了确定,是真的吗?

注意:我在某些地方(答案,互联网上的文章)读到了一件事,而这恰恰相反,因此这里的问题。

【问题讨论】:

  • 既然你所有的例子都与你的想法和文档一致,你为什么怀疑?
  • 从左到右。检查this
  • 让我们一劳永逸地结束这种混乱(在阅读下面的其他帖子及其所有 cmets 之后)。在数学上,顺序也是颠倒的,不管有些人说的多么巧妙 a(b(c(...))) - 是的,在这种情况下,运算的顺序仍然是 c,b,然后是 a,你只需大声读出 a、b 和 c。首先是最里面的支架。现在,由于 CSS 不使用括号来封装每个转换,它只是说“abc”,确实很令人困惑,实际上它的意思是“a(b(c(...)))”,但它就是这样,因此, c 首先发生。在实践中,它们从右到左发生。故事结束。

标签: css transform css-transforms


【解决方案1】:

是的,第一个操作是最右边的。即这里operation2operation1之前完成。

MDN article 确实声明:

变换函数按从左到右的顺序相乘,这意味着复合变换按从右到左的顺序有效地应用

这是文档:http://www.w3.org/TR/css-transforms-1/


示例 1

这里先进行缩放,然后然后垂直平移100px(如果先平移,缩放会平移500px!)

#container { 
  	position: absolute; 
  	transform: translate(0,100px) scale(5); 
  	transform-origin: 0 0; }
<div id="container"><img src="https://i.stack.imgur.com/xb47Y.jpg"></img></div>

示例 2

这里首先完成翻译,然后然后进行缩放(缩放完成之后使翻译看起来像500px的翻译!)

#container { 
  	position: absolute; 
  	transform: scale(5) translate(0,100px); 
  	transform-origin: 0 0; }
<div id="container"><img src="https://i.stack.imgur.com/xb47Y.jpg"></img></div>

【讨论】:

  • 这个答案是错误的。正如其他答案所述,它是从左到右完成的。这个答案应该被删除。
  • @T3rm1 请运行代码sn-ps,你自己会发现是真的。
  • 这应该被删除或更新为从左到右。规范本身说转换是从左到右应用的:drafts.csswg.org/css-transforms-1/#transform-rendering
  • @JeffreyKlein 如果您运行我的示例 1,您将看到 scale(5) 运行 first。然后向南平移 100px。而不是相反。如果相反,即先平移,然后缩放 x5,那么我们会在顶部看到 100*5 = 500px 白色。事实并非如此。 所以我确认它是从右到左的,至少根据平面变换和此类操作的组合的数学定义。如果您不同意,请提供可运行代码 sn-p 的另一个答案,以便我们测试您的建议。
  • 也刚刚注意到the MDN article on CSS Transforms 中的这一行:“变换函数按从左到右的顺序相乘,这意味着复合变换按从右到左的顺序有效地应用。”
【解决方案2】:

从左到右执行变换。变换对应于矩阵运算,这些运算是从左到右执行的。

这背后有直觉,这不仅仅是规范中的规范规则(此处的第 3 点:https://drafts.csswg.org/css-transforms-1/#transform-rendering

这是一支笔试试:https://codepen.io/monfera/pen/YLWGrM

解释:

每个变换步骤都建立自己的坐标系。所以

transform: translateX(500px);

沿其父级的X轴建立一个新的500px坐标系,元素将在那里渲染。

同样,

background-color: blue;
transform: translateX(500px) rotate(60deg);

首先沿其父级的 X 轴(向右)建立一个 500px 的新坐标系,然后然后在那个(已翻译,但现在无关紧要)坐标系内执行回转。所以它将是一个向右 500px 的形状,并就地旋转(围绕所谓的transform-origin,在本地坐标系中解释,默认的 50% 50% 用于旋转意味着,围绕中心旋转的矩形,但它是一个侧面)。

倒序

background-color: orange;
transform: rotate(60deg) translateX(500px);

首先建立一个相对于父坐标系旋转 60 度的新坐标系,然后然后沿现在旋转坐标系的 X 轴平移 100px,方向实际上并不向右从文档(或用户)的全局角度来看。因此,在这种情况下,就好像您首先旋转了纸张,然后沿着纸张的一侧(从原点,在本例中为左上角)滑动了 500 个单位。

有关更高级的讨论,以及如何在两个方向上直观地理解它,请查看Composing Transformations - CSS 转换遵循后乘模型,因此请查找标题为“考虑转换”的页面作为转换局部坐标系”(插图似乎有点偏)

【讨论】:

  • 似乎我们对同一件事使用了不同的术语。当我们在数学或矩阵中谈论 f(g(x)) 时:A B X,其中 X 是一个向量和 A、B 变换的矩阵,应用于 X 的 first 事物是 B,而 then A 应用于结果。因此,当将A B 应用于 X 时,B 是第一个应用的变换
  • @Basj 让我们以蓝色为例。第一个矩阵是翻译矩阵,我们称之为A。第二个是轮换,我们称之为B。输入向量是x(矩阵用大写字母表示,向量用小写字母表示)。那么结果就是(A B) x,即。首先我们将 A 与 B 相乘,然后将结果与 x 相乘。左到右。如果有帮助,您可以使用您的方式,但问题是,它以什么顺序完成。我试图回答这个问题。即使我们忘记了规范,实现通常会将所有矩阵组合为一个,然后才将其与向量相乘。
  • 通过矩阵乘法的关联性,(A B) x = A (B x),所以我注意到“从左到右”的术语是模棱两可的。当您说“从左到右”时,您会说“我们首先将 A 与 B 相乘”。当我说“从右到左”时,我的意思是:“我们可以先将 B 应用于 x,然后将 A 应用于 B x 的结果”。我认为我们对“从左到右”或“从右到左”表达的不同理解都是正确的:)
  • 数学方面,当然。但是规范说明了操作(mmult)及其顺序(从左到右),所以它不是模棱两可的。实现需要遵循一些的顺序。我可以想到几个可能的原因,例如。有限的数值精度意味着结果可能会有所不同,具体取决于顺序。差异可能是不可忽略的,因此,如果没有规则,就会存在浏览器差异。但是可能的原因并不重要,因为您的问题是,首先完成哪个转换?”。规范:从左到右,如果是A B C x,则首先采用A , 相乘 (A B) 等。
  • 请在答案中添加可运行代码sn-p,以便我们在浏览器中测试哪个操作是第一个操作。
【解决方案3】:

这在其他答案和 cmets 中已经提到,但在我看来并没有得到足够的重视:简短的答案是两种方式都有效

这完全取决于您是考虑将坐标附加到元素(从左到右)还是根据初始元素位置(从右到左)固定到页面。

这里有一篇文章展示了与动画的区别(更容易理解):Chaining transforms

这是一个显示文章动画的 sn-p:

html, body { height: 100%; }
body {
  background: #aaa;
  color: #000;
  font-family: Calibri,Candara,Segoe,"Segoe UI",Optima,Arial,sans-serif;
  overflow: hidden;
  margin: 0;
}
.info {
  text-align: center;
  font-family: Consolas,monaco,monospace;
  font-size: 20px;
  font-weight: bold;
  margin-bottom: 4px;
  color: #fff;
}
.split { white-space: nowrap; }
.side {
  display: inline-block;
  width: 50%;
}
.label {
  text-align: center;
  font-size: 20px;
}
.container {
  position: relative;
  font-size: 50px;
  margin: .6em auto 0;
  width: 0; height: 0;
  transform: translateX(-1em);
}
.ltr .object {
  position: absolute;
  left: 0; top: 0;
  width: 1em; height: 1em;
  margin: -.5em 0 0 -.5em;
  background: rgb(114,34,34);
  animation: ltrObj 5s infinite;
}
@keyframes ltrObj {
  from, 10% { transform: rotate( 0deg) translateX(0em); }
  40%       { transform: rotate(45deg) translateX(0em); }
  70%, to   { transform: rotate(45deg) translateX(2em); }
}
.object.shadow {
  animation: none;
  opacity: .2;
}

.ltr .axes {
  position: absolute;
  left: .5em; top: .5em;
  width: 1em; height: 1em;
  color: #111;
  box-sizing: border-box;
  border-left: 2px solid;
  border-top: 2px solid;
}
.ltr .axes::before, .ltr .axes::after {
  content: '';
  position: absolute;
  width: .2em; height: .2em;
  box-sizing: border-box;
  border-left: 2px solid;
  border-top: 2px solid;
  transform-origin: top left;
}
.ltr .axes::before { top: 100%; left: 0; margin-left: -1px; margin-top: 1px; transform: rotate(225deg); }
.ltr .axes::after { top: 0; left: 100%; margin-top: -1px; margin-left: 1px; transform: rotate(135deg); }

.rtl .axes {
  position: absolute;
  left: 0; top: 0;
  width: 2.5em; height: 2.3em;
  color: #111;
  box-sizing: border-box;
  border-left: 2px solid;
  border-top: 2px solid;
}
.rtl .axes::before, .rtl .axes::after {
  content: '';
  position: absolute;
  width: .2em; height: .2em;
  box-sizing: border-box;
  border-left: 2px solid;
  border-top: 2px solid;
  transform-origin: top left;
}
.rtl .axes::before { top: 100%; left: 0; margin-left: -1px; margin-top: 1px; transform: rotate(225deg); }
.rtl .axes::after { top: 0; left: 100%; margin-top: -1px; margin-left: 1px; transform: rotate(135deg); }

.rtl .object {
  position: absolute;
  left: 0; top: 0;
  width: 1em; height: 1em;
  margin: -.5em 0 0 -.5em;
  background: rgba(100,0,0,0.8);
  animation: rtlObj 5s infinite;
}
@keyframes rtlObj {
  from, 10% { transform: rotate( 0deg) translateX(0em); }
  40%       { transform: rotate( 0deg) translateX(2em); }
  70%, to   { transform: rotate(45deg) translateX(2em); }
}

.helper-mask {
  position: absolute;
  left: 0; top: 0;
  width: 3em; height: 3em;
  overflow: hidden;
}
.helper {
  position: absolute;
  left: 0; top: -2em;
  width: 0; height: 2em;
  margin-top: 2px;
  box-sizing: border-box;
  border: 2px solid #00c;
  border-left: none;
  border-radius: 0 100% 0 0;
  transform-origin: bottom left;
  animation: helper 5s infinite;
}
@keyframes helper {
  from, 10% { width: 0em; transform: rotate( 0deg); }
  40%       { width: 2em; transform: rotate( 0deg);}
  70%, to   { width: 2em; transform: rotate(45deg);}
}
<div class="info">rotate(45deg) translateX(2em)</div>
<div class="split">
  <div class="side ltr">
    <div class="label">Left to Right</div>
    <div class="container">
      <div class="object shadow"></div>
      <div class="object">
        <div class="axes"></div>
      </div>
    </div>
  </div>
  <div class="side rtl">
    <div class="label">Right to Left</div>
    <div class="container">
        <div class="axes"></div>
        <div class="object"></div>
        <div class="helper-mask">
            <div class="helper"></div>
        </div>
    </div>
  </div>
</div>

无论实际实现是使用从左到右还是从右到左都无关紧要,在创建动画时两者都同样有效,只要记住区别即可。

【讨论】:

  • 这篇文章需要比现在更多的曝光。谢谢!附言基本上,这必须被标记为答案。
【解决方案4】:

它首先应用最左边的转换。

如上图所示,与第二次相比,第一次转换需要更长的距离。原因是第一个示例首先经过scale,然后根据其在 x 轴上的新宽度,采用translate 指定的距离。因为它现在更宽了,50% 将导致它需要更长的距离。 50%指定的度量取自身宽度的一半。

the site I cited from

【讨论】:

  • 这个话题经过长时间讨论(见其他答案的cmets)的结论是:这取决于我们对“从左到右”或“从右到左”的理解。这是一个术语问题。两者都是正确的,取决于我们是否谈论矩阵乘法(与每个变换相关的矩阵),或者“首先应用哪个函数”。 MDN 声明“变换函数按从左到右的顺序相乘,这意味着复合变换按从右到左的顺序有效地应用。”。这总结得很好。
【解决方案5】:

我刚刚使用 CSS 转换在 HTML 中创建了一个 3d 房间的演示。我为后墙制作了一个 200x200 DIV,将其留在那个位置。然后我从相同大小和位置开始制作左墙,然后添加

transform: translate3d(-100px,0px,100px) rotateY(90deg).

然后我做了一面右墙并添加了

transform: translate3d(100px,0px,100px) rotateY(90deg).

这正确地创建了房间。但这是 Safari 的第 13 版。本来我想先列出旋转步骤,但是墙在一个奇怪的位置。所以我看到了从右到左的行为。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-13
    • 1970-01-01
    • 1970-01-01
    • 2013-11-08
    • 1970-01-01
    相关资源
    最近更新 更多