一文搞懂 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>
相关推荐
子兮曰5 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰5 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万5 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝5 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋5 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁5 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95275 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大5 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师5 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学5 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端