SVG坐标系旋转、倾斜和角度问题

5

我以为我对SVG有相当不错的了解,包括viewportviewBox用户坐标系

在下面的第一个示例中,我们使用与viewport相同宽高比的viewBox。正如预期的那样,用户坐标系的旋转不会扭曲任何角度。

在第二个示例中,我们将viewBox设置为与viewport不同的宽高比。换句话说,在将viewBox映射到viewport时,形状的宽高比不会保持不变。底部右侧角没有因此缩放而扭曲,这是有道理的,因为坐标系原点位于(0,0)。

然而,在第二个示例中,当我们旋转用户坐标系时,底部右侧角被扭曲。而这种情况在第一个示例中并没有发生。

编辑1:为了明确,问题与最后一个示例中的底部右侧角有关。在旋转之前,但在用viewBox进行拉伸后,该角度为90%。但在旋转后,它不再是90%。

为什么非均匀缩放的三角形旋转时会失去其角度?

示例一(均匀缩放)

body {
  height: 500px;
}

svg {
  width: 400px;
  height: 400px;
  border: 1px solid red;
}
<svg id="s1" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 200 200" preserveAspectRatio="none">
    <style>
      polygon {
        transform: translate(100px, 0px);
        animation: 2s ease-in 1s 1 normal forwards rotate-down;
        fill: green;
      }
      
      @keyframes rotate-down {
        0% {
          transform: translate(100px, 0px) rotate(0deg);
        }
        100% {
          transform: translate(100px, 0px) rotate(45deg);
        }
      }
    </style>
    <polygon points="100,100 100,0 0,100" />
  </svg>

例子二(非均匀缩放)

body {
  height: 500px;
}

svg {
  width: 600px;
  height: 400px;
  border: 1px solid red;
}
<svg id="s1" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 200 400" preserveAspectRatio="none">
    <style>
      polygon {
        transform: translate(100px, 0px);
        animation: 2s ease-in 1s 1 normal forwards rotate-down;
        fill: green;
      }
      
      @keyframes rotate-down {
        0% {
          transform: translate(100px, 0px) rotate(0deg);
        }
        100% {
          transform: translate(100px, 0px) rotate(45deg);
        }
      }
    </style>
    <polygon points="100,100 100,0 0,100" />
  </svg>


编辑2(添加图片以澄清):

下面我们看到的三角形是在 viewBox 被添加(因此进行了缩放和平移),但是在旋转之前。右下角的角度为90度。

enter image description here

下面我们看到的三角形是在 viewBox 被添加(因此进行了缩放和平移) 以及 在旋转之后。右下角的角度不再是90度。

enter image description here


编辑3:

我最终得出了结论。

以下是一个解释详细信息并链接相关资源的答案。


你在哪里看到了扭曲的角度?这个三角形只是被拉伸了。你希望如何同时拉伸三角形并保持90度的角度?拉伸会改变角度(至少对于矩形而言)。 - Mailosz
1
@Magnus 但正是拉伸才能做到这一点。 - Robert Longson
1
@enxaneta我完全理解preserveAspectRatio的作用,以及如何在当前用户坐标系(UCS)中使用viewBox属性来缩放图形。这不是我的困惑所在。我原以为是先应用视口框,然后将其他变换应用于由视口框创建的修改后的UCS副本。我已经多次阅读了规范的这部分内容,因此我的理解也来自此处。 - Magnus
1
“transform” 属性可以在元素内部建立一个新的用户坐标系,但不会覆盖当前的坐标系。它仍然受到在其外部应用的其他变换的影响。例如,无论 SVG 内发生了什么其他变换,所有内容都会受 viewBox 变换的影响。 - Paul LeBeau
1
不要编辑添加解决方案/答案..请回答您自己的问题 - Temani Afif
显示剩余26条评论
3个回答

2

希望这个例子能够向您展示正在发生的事情。

将鼠标悬停在SVG上,可以看到是拉伸使角度发生变化。

body {
  height: 500px;
}

svg {
  width: 200px;
  height: 400px;
  border: 1px solid red;
  transition: 1s width;
}

svg:hover {
  width: 600px;
}
<svg id="s1" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 200 400" preserveAspectRatio="none">
    <style>
      polygon {
        transform: translate(100px, 0px) rotate(45deg);
        fill: green;
      }
    </style>
    <polygon points="100,100 100,0 0,100" />
  </svg>


谢谢Paul。为了缩小我的问题范围,我创建了以下笔记本,在其中将viewBox替换为transform: scale(2,1)(具有相同的效果):https://codepen.io/magnusriga/pen/EOqqxb 。然后我们继续添加translate(50px, 20px),最后是rotate(45deg)。我们可以逐步进行吗?首先,创建初始用户坐标系(UCS)的副本并进行缩放,宽度加倍,高度不变。2)然后,我们将整个坐标系向右移动50px并向下移动20px。由于之前应用了缩放,因此在视口中实际上是100px和20px。(1/2) - Magnus
  1. 然后我们添加45度的旋转。不知何故,这在x轴和y轴上应用方式不同。也许沿着x轴的坐标旋转两倍于y轴,由于与之前的平移发生的相同原理。
- Magnus
你把它搞反了。你可以将变换想象为从后往前应用。因此,首先多边形被旋转45度,然后向右移动50个单位,向下移动20个单位,最后在水平方向上缩放2倍。 - Paul LeBeau
谢谢Paul。你知道规范中哪里说明了这种行为吗?这个流行的SVG指南似乎表明相反的情况:https://www.sarasoueidan.com/blog/svg-transformations/#nested-and-chained-transformations。实际上,这个指南也说明了完全相反的情况:http://tutorials.jenkov.com/svg/svg-transformation.html#combining-transformations(“变换函数在transform属性内指定的顺序是应用于形状的顺序。”) - Magnus
正如Temani所建议的那样,我的编辑应该成为自己的答案。很抱歉来回修改。 - Magnus
显示剩余10条评论

2
我最终找到了答案。
下面这个问题是在我确定实际问题后发布的,它解释了为什么坐标转换会表现出某种行为: 在那个问题的回答中,@TemaniAfif 展示了如何计算最终的变换矩阵并将其应用于图形元素的坐标,以将其从视口坐标系映射到最终用户坐标系。
简而言之,在应用变换时,我们实际上是将当前用户坐标系复制一份,然后相对于我们从中复制的当前用户坐标系进行平移。在 SVG 中,初始的用户坐标系(在 viewBox 或任何变换之前)与初始的视口坐标系相同。
链式/嵌套变换是从左到右/从外到内应用于坐标系,以达到可以在其中映射图形元素的最终坐标系。注意,嵌套变换与在一个元素上链接变换具有相同的效果。
这到底是如何工作的?好吧,每个变换都有一个预定义的仿射变换矩阵,与 CSS/SVG 无关。有几篇维基百科文章展示了这些矩阵,比如: 为了将元素的坐标映射到最终用户坐标系,我们将矩阵相乘,从左到右(在源代码中编写的顺序)以达到最终变换矩阵。
请注意,由于我们按照它们在源代码中编写的顺序相乘变换矩阵,并且由于 AxB 与 BxA 在乘法时不同,所以变换在源代码中的书写顺序很重要。
最后,我们将元素的 x 和 y 坐标乘以此最终变换矩阵,以查看每个坐标从视口坐标系映射到最终用户坐标系。
对于那些有兴趣的人来说,可能更容易的是不去想上述内容,而是仅在脑海中想象链式/嵌套变换是应用于元素本身(而不是用户坐标系),从右到左/从内到外(即与应用于坐标系的顺序相反)进行。
无论你是在脑海中想象你将坐标系从左到右变换然后映射到图形元素,还是通过将变换从右到左应用于元素本身来变换,最终结果都是相同的。
相关规范

注意:

对于这个问题,无论是应用于SVG元素还是HTML元素,变换机制都是相同的。


-1

看起来你认为viewBox是一种在计算SVG图像时应用的变换方法,就像其他方法一样,但事实并非如此。你在这里体验到的是应用于整个SVG元素的转换。要应用此转换,浏览器需要计算SVG对象,因此所有内部SVG转换已经被应用。

这与缩放光栅图像的方式完全相同:

polygon {
  fill: transparent;
  stroke-width: 4px;
  stroke: black;
}
Base raster image:<br>
<img src="https://istack.dev59.com/ZA16O.webp">

<br>Stretched raster image:<br>
<img style="height: 150px; width: 300px" src="https://istack.dev59.com/ZA16O.webp">

<br>Base SVG:<br>
<svg viewBox="0,0,160,160" style="height: 120px" preserveAspectRatio="none">
<polygon points="10,10 150,10 80,150"/>
</svg>

<br>Stretched SVG:<br>
<svg viewBox="0,0,160,160" preserveAspectRatio="none" style="height: 120px; width: 300px;">
<polygon points="10,10 150,10 80,150"/>
</svg>

只有在SVG被转换后才会被绘制,因此它们不会失去质量。


SVG规范实际上说(这里),所有应用于SVG元素的变换都是这样工作的。


我已经理清了我的困惑。如果有兴趣,答案现在在原帖中。 - Magnus
viewBox与问题无关,但既然您提到了它,我必须指出viewBox和transforms(单个,链接或嵌套)都会创建当前用户坐标系的副本,并对其进行修改。直到通过仿射变换矩阵的点积计算出最终变换矩阵后,图形元素才映射到此坐标系中。 - Magnus
viewBox的效果与对svg元素本身进行变换相同。我将在单独的评论中发布规范的相关部分。 - Magnus
‘viewBox’属性的存在会导致对视口坐标系应用变换,如“计算SVG视口等效变换”中所述。最后提到的一节显示了viewBox所暗示的变换,链接在此:https://www.w3.org/TR/SVG/coords.html#ComputingAViewportsTransform。 - Magnus
请注意,在某些情况下,用户代理将需要提供一个翻译转换和一个缩放转换。例如,在最外层的svg元素上,如果“viewBox”属性指定<min-x>或<min-y>的非零值,则需要进行翻译变换。 - Magnus
显示剩余3条评论

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