一文搞懂 viewBox 的坐标变换

什么是 viewBox

viewBox 表示了 svg 的可视区的范围,可以简单的理解为从 svg 中裁剪出来的某一块矩形区域内容,该内容是我们可见的区域,超出该区域的内容都被隐藏起来。在实际展示时,还会以着一定的比例,对该内容进行变换填充到实际的画布(容器)中去。viewBox 用(min-x,min-y,width,height)表示,其含义是在 svg 空间中的 (x,y)位置划出一个 width x height 的矩形区域。当没有指定 svg 的 viewBox 时,其默认值为 (0,0,当前容器的宽,当前容器的宽),实际渲染时无需进行任何转换。

html 复制代码
<html>
  <body style="display: flex">
    <div style="margin: auto">
      <svg width="300" height="150">
        <rect width="100" height="100"></rect>
        <rect x="300" width="100" height="100" fill="red"></rect>
      </svg>
    </div>
  </body>
</html>

上图绘制红,黑两个矩形,但是其可见的区域为 300x150 的矩形,所以第二个矩形被隐藏起来了。当 viewBox 为 (100,0,300,150)后,红色矩形出现,同时黑色矩形消失。

html 复制代码
<html>
  <body style="display: flex">
    <div style="margin: auto">
      <svg width="300" height="150" viewBox="100 0 300 150">
        <rect width="100" height="100"></rect>
        <rect x="300" width="100" height="100" fill="red"></rect>
      </svg>
    </div>
  </body>
</html>

此时从 svg 到实际容器的映射关系如下:

text 复制代码
 x1 = x - viewBox.x
 y1= y - viewBox.y

因此原先的两个矩形坐标(0,0), (300,0) 分别映射为 (-100,0), (300, 0)

注意由于此时的 viewBox 的宽高和实际容器的宽高是一致的,所以没有发生伸缩现象,只是单纯的坐标平移,即上述简单的坐标平移

svg 的伸缩变换

当 viewBox 与实际容器的宽高不一致将会发生伸缩变换,此时需要根据preserveAspectRatio属性来进行实际的坐标变换,没有指定preserveAspectRatio时,默认采用 preserveAspectRatio="xMidYMid meet",先看最简单的一种情况,不进行伸缩即 preserveAspectRatio="none"

html 复制代码
<html>
  <body style="display: flex">
    <div style="margin: auto">
      <svg width="300" height="150" viewBox="50 0 150 300" preserveAspectRatio="none">
        <rect width="100" height="100"></rect>
        <rect x="300" width="100" height="100" fill="red"></rect>
      </svg>
    </div>
  </body>
</html>

此时的变换关系如下:

text 复制代码
x1=(container.width/viewBox.width)*(x-viewBox.x)

y1=(container.height/viewBox.height)*(y-viewBox.y)

以黑色矩形的坐标为例子(0,0), (100,0), (100,100),(0,100)
变换后的坐标为:(-100,0),(100,0),(100,50),(-100,50)

结果如下图所示,黑色矩形的长度变成了原来的两倍,但是其中一半被遮挡住

preserveAspectRatio不为 none 时,将会对 svg 的内容按 viewBox 的宽高比例进行伸缩,以适应容器的宽高。preserveAspectRatio值形式如下preserveAspectRatio=[align meetOrSlice],第一个值为对齐方式,第二个值为伸缩方式。meet的伸缩原则是保证伸缩后的元素完全在容器内部,而slice则要求填充满容器,可能导致元素溢出 ,下面讨论默认情况下的变换关系,即preserveAspectRatio="xMidYMid meet",其余情况可以自行推导,过程是类似的。

text 复制代码
1. 先对原图按照比例viewBox的比例进行伸缩,使内容完全显示在容器内
2. 根据 align 的类型进行平移

确定线性变化的比例

容器的宽高为 300*150,viewBox 的宽高为150*300,viewBox 超出容器,因此需要进行伸缩变换,其变化比例取各自长宽比的较小值

text 复制代码
factorWidth =container.width /  viewBox.width = 300/150=2
factorHight =container.height / viewBox.height = 150/300 = 0.5

因此 factor = 0.5

对 viewBox 内部的各个坐标进行线性变换则得到如下的关系
x1= factor*(x-viewBox.x)
y1=factor*(y-viewBox.y)
经过线性变换后的viewBox区域变成了 `75*150`的矩形区域
对于其内部各个点的变换结果如下(上述两个红黑矩形框为例):
原始点:
     (0,0),(100,0),(100,100),(0,100)
     (300,0),(400,0),(400,100),(300,100)

变换后:
     (0,0),(50,0),(50,50),(0,50)
     (150,0),(200,0),(200,50),(150,50)

由于设置了变换后的对齐方式为 `xMidYMid` 因此还需要对线性变换后的矩形进行平移,该对齐方式表示的是居中对齐, 即 viewBox 变换后的中点与容器的中点对齐,其变换关系如下:

  平移的距离x = (container.width  - viewBox.width*factor)/2 = (300-75)/2=112.5
  平移的距离y = (container.height - viewBox.height*factor)/2 = (150-150)=0

最终的变换结果如下:
   (112.5, 0),(162.5, 0),(162.5, 50),(112.5, 50)
   (262.5, 0),(312.5, 0),(312.5, 50),(262.5, 50)
html 复制代码
<html>
  <body style="display: flex">
    <div style="margin: auto">
      <svg width="300" height="150" viewBox="0 0 150 300">
        <rect width="100" height="100"></rect>
        <rect x="300" width="100" height="100" fill="red"></rect>
      </svg>
    </div>
  </body>
</html>
相关推荐
吃杠碰小鸡7 分钟前
commitlint校验git提交信息
前端
虾球xz38 分钟前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇43 分钟前
HTML常用表格与标签
前端·html
疯狂的沙粒1 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员1 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐1 小时前
前端图像处理(一)
前端
程序猿阿伟1 小时前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
疯狂的沙粒1 小时前
对 TypeScript 中函数如何更好的理解及使用?与 JavaScript 函数有哪些区别?
前端·javascript·typescript
瑞雨溪1 小时前
AJAX的基本使用
前端·javascript·ajax
力透键背1 小时前
display: none和visibility: hidden的区别
开发语言·前端·javascript