SVG视图框(viewBox):平移和缩放的精确顺序

10
我很难从技术角度理解 viewBox 上的 min-xmin-y 是如何工作的(不使用比喻语言)。

我已经花了很多时间查阅以下两个有用的资源:

根据SVG 1.1规范

  

“viewBox”属性的值是由四个数字(,,和,之间可以用空格和/或逗号分隔)组成的列表,这些数字指定了用户空间中应该被映射到给定元素所建立的视口边界的矩形,考虑到“preserveAspectRatio”属性。

并且:

  

“viewBox”属性的效果是用户代理自动提供适当的转换矩阵,将用户空间中指定的矩形映射到指定区域(通常是视口)的边界。

并且:

(注意:在某些情况下,用户代理将需要提供一个翻译转换,以及一个比例变换。例如,在最外层的svg元素上,如果‘viewBox’属性指定除0和1之外的值,则需要一个平移变换或缩放变换。)

因此,我的期望是定义一个viewBox与以下步骤相同:

  1. 首先缩放视窗框,使其填充视口(假设视口和viewBox具有相同的纵横比)。
  2. 然后平移视窗框,依据min-xmin-y viewBox属性将其放置在视口中。

如果我们查看Sara的两个示例,从这里开始,似乎并不是这样发生的。

在她的第一个示例中(<svg width="800" height="600" viewbox="100 100 200 150">...</svg>),它看起来像:

  1. 根据min-x / min-y在视口中放置viewBox
  2. 将视口框缩放到与视口相同的大小
  3. 将viewBox原点平移(移动)以与视口原点重合

然而,在她的第二个示例中(<svg width="800" height="600" viewbox="-100 -100 400 300">...</svg>),它看起来是一个完全不同的顺序:

  1. viewBox缩放到与视口相同的大小。
  2. viewBox原点以与min-xmin-y相反方向的某种方式移动。它不与视口原点重合-这与第一个示例不同。
  3. 因此,我认为我并不完全理解它,因为从技术上讲,它在两种情况下应该以相同的方式工作。

    最后,在Sara的示例中,我不明白为什么蓝色坐标系(用户坐标系)本身不会在视口坐标系中移动到(100,100)或(-100,-100)。我认为viewBox应该平移缩放用户坐标系?


    编辑:

    根据这个SO答案min-xmin-y确实遵循我的第一组步骤。 viewBox原点根据min-xmin-y放置在视口中,然后被翻译,使其原点位于视口原点上方。然后它被(之前或之后)缩放以填充视口。

    如果是这样,我很难理解为什么Sara的示例中蓝色用户坐标系不总是以其原点位于视口原点之上结束。毕竟,viewBox应该修改用户坐标系。


1
SVG 2规范对计算SVG视口等效变换进行了正式定义。 - ccprog
@ccprog 非常有趣,我已经研究了一段时间。这是否意味着,由viewBox创建的新用户坐标系始终紧密地围绕任何后代图形元素(假设图形的x/y坐标为0)? - Magnus
不,这个算法定义了两个矩形:一个在无限画布中(vb-值),另一个在渲染器/浏览器中(e-值),然后将画布中的每个图形内容绘制在渲染矩形中,使得 _矩形_“适合”,而不考虑内容。图形内容可以在画布的任何地方,因此可能出现在渲染器的任何地方。只有之后,所有渲染矩形之外的内容都被裁剪掉,里面的内容才是可见的。 - ccprog
@ccprog 更简单地说,将vb-x / y设置为(100, 100)并不意味着用户坐标系统始于视口的100,100。相反,当viewBox矩形映射到视口时,用户坐标系统只是附带(固定在其内容周围),在映射完成后任意结束。 - Magnus
@ccprog 百分之百同意。那正是我的困惑所在。啊,我很高兴终于搞清楚了。非常感谢您的深入见解和帮助! - Magnus
显示剩余3条评论
3个回答

14
< p >< strong >坐标系中< code >viewBox原点在x轴上的偏移量(min-x=70px

<svg width="400" height="400" viewBox="70px, 0, 400px, 400px">

enter image description here

在图中,用户坐标的原点向右移动了70px,从而沿水平轴将整个矩形视区viewBox (400 x 400px)向右移动。
发生这种情况时,捕获位于viewBox下方的SVG文档片段的图像,然后将具有原点(0,0)在左上角的固定用户视口区域与捕获的片段对齐。
图形的坐标被重新计算,最后向左移动70px。形式上,在固定的视口视图区域应用viewBox时,SVG文档的片段向左移动。

enter image description here

现场演示

视图框架原点在两个轴上的偏移量

min-x=70px, min-y="70px"

<svg width="400" height="400" viewBox="70px, 70px, 400px, 400px">

为了清晰起见,在图片底部添加另一个红色矩形 - 6

enter image description here

将原点转移到viewBox后,一个长宽为400 × 400 px的矩形SVG文档片段从原点(70.70)开始计算宽度和高度进入viewBox。

进行图像捕获。接下来,viewBox的原点(70,70)与视口的原点(0,0)合并。图形的坐标被重新计算。

enter image description here

因此,红色矩形5和6变得完全可见。不属于此区域的所有内容都被剪切掉。例如,彩色圆圈1、2和4的部分区域。 演示

使用viewBox进行缩放

SVG文档片段的比例取决于宽高比:viewportviewBox

如果viewport/viewBox= 1,那么比例尺寸为1

如果viewport/viewBox与1不同,则比例尺寸将向增大或减小的方向改变。

enter image description here

增加规模如何解释下面的图形?
一个像素的{{viewBox}}会拉伸到两个像素的{{viewport}}。

enter image description here

实时演示

缩小 svg 图像 1:2

<svg width="400" height="400" version="1.1" viewBox="0 0 800 800">

视口宽高比/视图框宽高比 = 1/2

enter image description here

viewBox 捕获了一个矩形片段 800 x 800 像素,即 SVG 视口的整个范围 400 x 400 像素,并在视口的右侧和底部各增加了 400 像素

enter image description here

这意味着viewBox中的两个像素被压缩成viewport中的一个像素。因此,SVG图像减小了一半。

在线演示


谢谢,亚历山大。非常生动形象的解释。我实际上想了解viewBox和viewport与用户坐标系和视口坐标系的关系,通过与@ccprog在OP评论中的讨论,我最终弄清楚了。简而言之,用户坐标系最初与视口坐标系相同。然后,在SVG画布上绘制所谓的viewBox矩形,其外部的所有内容都被裁剪掉。随后,将viewBox矩形映射到适合于视口矩形的大小。1/2 - Magnus
该映射过程会导致用户坐标系(以及其中的所有可见图形)被平移(移动)和缩放。需要牢记的是,UCS实际上并不是由viewBox定义的。UCS仍然与其中的图形相关联,就像最初一样。它只是由于viewBox映射而与图形一起平移和缩放。为什么理解UCS的位置很重要?因为所有对后代元素的转换都应用于该UCS的副本,而不是元素本身(就像在HTML中一样)。 - Magnus
例如,在您的第一幅插图中,UCS 实际上会在视口之外(左侧)70px。它仍然像应用 viewBox 之前一样围绕着图形。可以说 viewBox 修改 了 UCS,但它并没有 定义 它。 - Magnus
@Magnus 谢谢您提出关于SVG最基本定义的问题 - 使用viewBoxviewport。您正在寻找对这个问题非常深入的理解。 在我看来 - 您走在了正确的道路上。我已经准备了一些更有趣的关于viewBox主题的插图。 如果您不介意,我会在一个独立的答案中发布它们。 因为第一个回答太长了。 - Alexandr_TT
1
好的,亚历克斯,请开始。期待着这个。 - Magnus
2
Alexandr,我授予你SVG铜徽章,以表彰你为社区所做的出色工作! - Bharata

3

enter image description here

  1. 在这张图片中,一个灰色的矩形是无限的SVG画布

  2. 绿色的矩形是用户在其显示器上看到的视口

  3. 黄色的矩形是虚拟的viewBox区域,用户通过它查看viewport

viewBox可以沿着无限svg画布的坐标轴移动,如正方向x-min> 0y-min> 0,以及负方向的-x-min-y-min

图像处理svg

  • 接下来是捕获位于viewBox下的SVG画布的片段。
  • 在下一步中,视口的坐标系与viewBox的坐标系对齐。然后将由viewBox图像捕获的片段传回viewport
  • 这里有一个协商过程,可能有多个选项:

    1. 如果min-x = 0min-y = 0,则viewport的宽度和高度分别等于viewBox的宽度和高度,则片段图像不移动或缩放。
    2. 如果将viewBox向右移动- min-x> 0,则图像向左移动。很明显,通过在viewport右侧捕获图像,然后与原点组合,我们从而将图像向左移动。
    3. 如果将viewBox向下移动到viewports下方- min-y> 0,则图像将上移。

基于此,有一种想法可以实现水平和垂直视差,而无需使用CSSJavaScript。要做到这一点,只需沿着SVG画布移动viewBox,如下图所示。单击“开始”按钮。

<svg version="1.1"   xmlns="http://www.w3.org/2000/svg"
 xmlns:xlink="http://www.w3.org/1999/xlink"
   width="600" height="360" viewBox="0 0 600 360"   >
  <title> Explanation horizontal of parallax viewBox </title>
  <desc> animate the horizontal parallax  by modifying a coordinate of the viewBox </desc>
 <defs>
<g id="canvas-svg" stroke-width="2px"> 
  <g id="canvas-frame1">
   <rect id="v-port1" x="25" y="200" width="110" height="110" stroke="skyblue"   fill="yellowgreen" /> 
 <text id="t-port1" x="75" y="255" style="font-size: 16pt;">1 </text>
 <text  x="26" y="303" > 0 </text>
 </g>      
  <g id="canvas-frame2">  
  <rect id="v-port2" x="135" y="200" width="110" height="110" stroke="skyblue"  fill="dodgerblue" /> 
  <text id="t-port2" x="185" y="255" style="font-size: 16pt;">2 </text>
  <text  x="136" y="303" > 1168 </text>
 </g>    
  <g id="canvas-frame3">  
  <rect id="v-port3" x="245" y="200" width="110" height="110" stroke="skyblue"  fill="crimson"  /> 
  <text id="t-port3" x="295" y="255" style="font-size: 16pt;">3 </text>
  <text  x="246" y="303" > 2336 </text>
  </g>
      <g id="canvas-frame4">  
  <rect id="v-port4" x="355" y="200" width="110" height="110" stroke="skyblue"  fill="orange" /> 
  <text id="t-port4" x="405" y="255" style="font-size: 16pt;">4 </text>
  <text  x="356" y="303" > 3504 </text>
     </g>
       <g id="canvas-frame5">  
  <rect id="v-port5" x="465" y="200" width="110" height="110" stroke="skyblue" stroke-width="1px" fill="yellow" /> 
  <text id="t-port5" x="515" y="255" style="font-size: 16pt;">5 </text>
  <text  x="466" y="303" > 4672 </text>
       </g>   
 </g>
 
 </defs>
 
  <g id="first-rect">
   <rect  x="25" y="25" width="110" height="110" stroke="skyblue" stroke-width="1px" fill="yellowgreen" /> 
 <text  x="75" y="85" style="font-size: 16pt;">1 </text>
 <text  x="26" y="135" > 0 </text>
 </g>      


  <desc>The SVG canvas is infinite in size. In our example, user a viewport of SVG is in the leftmost position.</desc>  
<use xlink:href ="#canvas-svg" x="0" y="0"> </use>
   
<desc> viewBox is moved along canvas SVG</desc>
 <g id="viewBox1">
 <rect id="v-box" x="25" y="200" width="110" height="110" stroke="skyblue" stroke-width="5px" fill="none" />
  <text id="t-port1" x="45" y="225" style="font-size: 16pt; fill:blue;">viewBox </text>   
 <animateTransform attributeName="transform" type="translate" begin="startButton.click+0.5s" end="stopButton.click" dur="20s" from="0 0" to="440 0" repeatCount="indefinite" restart="whenNotActive" fill="freeze"/>
 </g> 
 

<desc> The image moves to the left viewport</desc>
<use xlink:href ="#canvas-svg" x="0" y="0">
    <animateTransform attributeName="transform" type="translate" begin="startButton.click+0.5s" end="stopButton.click" dur="20s" from="0 -170" to="-440 -170" repeatCount="indefinite" restart="whenNotActive" fill="freeze" />
  </use>

<desc> Grey background image of the canvas SVG</desc>
 <g fill="#E5E5E5" stroke="#E5E5E5">
 <rect  x="135" y="0" width="465" height="195"    />   
  <rect  x="0" y="0" width="25" height="195"    />   
  <rect  x="0" y="0" width="135" height="30"    />   
  <rect  x="25" y="135" width="135" height="60" />   
  <rect  x="0" y="315" width="600" height="85"  />   
  <rect  x="0" y="195" width="25" height="120"  />
  <rect  x="575" y="195" width="25" height="120" />
 </g> 
  
  <g stroke-width="1px" stroke-dasharray = "5 5"> 
   <line x1="25" y1="140" x2="25" y2="195" stroke="blue"  />
 <line x1="135" y1="140" x2="135" y2="195" stroke="blue" stroke-width="1px"  />
  </g>  
   <g style="font-size: 16pt; fill:blue;">
 <text  x="45" y="170"  > viewport </text> 
  <text  x="15" y="20" style="font-size: 14pt;"> display the user's  </text>
     <text  x="230" y="90" style="font-size: 40pt; fill:#1E90FF"> canvas SVG </text> 
   </g> 

<g id="startButton">
 <rect  x="520" y="325" rx="8" ry="8" width="60" height="20" fill="#58AE2A" />
 <text  x="550" y="340" font-size="16" font-weight="bold" font-family="Arial" text-anchor="middle" 
 fill="white" >Start</text>
</g>
        <g id="stopButton">
   <rect  x="450" y="325" rx="8" ry="8" width="60" height="20" fill="#1E90FF" />
   <text  x="480" y="340" font-size="16" font-weight="bold" font-family="Arial" text-anchor="middle" 
   fill="white" >Stop</text>
  </g> 

</svg>


大不错,Alex。我觉得应该更明确地阐述视口坐标系与用户坐标系(又称为当前坐标系/用户空间中的坐标系),以避免让未来的读者混淆。这两个坐标系确实是唯一的坐标系。图形绘制是在用户坐标系中完成的。viewBox和变换会为元素创建当前坐标系的副本,然后对其进行变换。 - Magnus
1
否则,解释得非常好。我期待着深入研究你的视差代码。我将标记您之前的答案为接受,因为我们所有的评论都应该完整地呈现画面。 - Magnus

2
我总是混淆viewBoxviewport。因此,我会尽可能避免使用它们。我不完全明白您是要为浏览器还是SVG设置变换矩阵。因此,我也会尽量避免这样做。 viewBox属性向浏览器提供有关SVG图形的大小和坐标原点的信息。它定义了SVG的可见窗口。只有窗口内的部分才会显示出来。
让我们看一个例子:
<svg width="800" height="600" viewbox="100 100 200 150">

这段代码告诉浏览器,它应该绘制一个SVG图形,其尺寸为800像素x 600像素 - 在浏览器的坐标系统中。因此,在浏览器DOM中,SVG组件将具有该大小。

然后,viewbox属性告诉浏览器,SVG图形的相关/可见部分大小为200pt x 150pt(在SVG坐标系统中)。因此,浏览器知道它需要应用400%的缩放来将SVG坐标转换为浏览器坐标。

此外,viewbox属性告诉浏览器,SVG坐标系中的点(100,100)将是可见SVG图形窗口的左上角。因此,浏览器会相应地进行平移。

所有在SVG坐标系中具有较小xy值的内容都将被剪切,即不可见,因为它位于窗口之外并且在浏览器为SVG创建的空间之外。同样,SVG坐标300(100 + 200)右侧和坐标250(100 + 150)下方的所有内容都将在窗口之外且不可见。


谢谢,Codo!听起来我的第一步是正确的。它将新用户坐标系(UCS)映射到视口坐标系(VCS)。换句话说:1)根据min-y/x将UCS放置在视口内,然后平移(包括其包含的图形),使其原点位于VCS原点之上。2)然后缩放UCS,以填充整个VCS(假设纵横比相同)。这样听起来对吗? - Magnus
我不太理解UCS和VCS,所以我无法确定它是否正确。 - Codo

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