一文搞懂 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>
相关推荐
qq_589666053 小时前
TypeScript 完整入门教程
前端·javascript·typescript
用户059540174467 小时前
LLM对话记忆测试踩坑实录:手工回归30分钟,自动化后2分钟发现3个隐藏Bug
前端·css
Ashley的成长之路8 小时前
前端性能优化实战手册·第2篇:资源加载策略全解
前端·性能优化·资源加载·http/3·性能优化实战·资源加载优化
浅水壁虎8 小时前
vue基础(第二章 )
前端·javascript·vue.js
界面开发小八哥8 小时前
界面控件DevExtreme v26.1新版亮点——支持Angular 22
前端·javascript·angular.js·devexpress·ui开发·devextreme
Getflare8 小时前
前端 + UI 设计 + AI:这不是三个工种,是一个新三角能力模型(附自检清单)
前端·人工智能·ui
oil欧哟8 小时前
我做了一个 Vibe Coding 术语学习站:VibeHub
前端·ai·agent·独立开发·vibe coding
审小匠OpenCPAi9 小时前
银行流水核查怎么自动化?单边匹配、双向勾稽与图聚类异常检测的工程对比
java·前端·人工智能·python·审计
Revolution619 小时前
一个公共表格组件,是怎么一步步失控的
前端·前端工程化