svg 的 viewBox 与 preserveAspectRatio 属性探究

先来看一段简单的代码,我在 html 文件中直接使用 <svg> 标签进行绘制

html 复制代码
<!-- 代码片段 1.1 -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      svg {
        border: 1px solid red;
      }
    </style>
  </head>
  <body>
    <svg width="300" height="200" viewBox="0 0 120 80">
      <!-- 绘制一个边长为 100px 的正方形 -->
      <rect x="0" y="0" width="100" height="100"></rect>
    </svg>
  </body>
</html>

<svg> 标签内我定义了 3 个属性:

  • width/ height:用于指定 svg 的宽高。注意,代码片段 1.1 中,如果指定了 viewBox 但没有指定 widthheight
html 复制代码
<!-- 代码片段 1.1.1 -->
<body>
  <svg viewBox="0 0 120 80">
    <rect x="0" y="0" width="100" height="100"></rect>
  </svg>
</body>

则相当于只是指定了 svg 图形的长宽比,默认大小取决于该 svg 所在的 html 元素(代码片段 1.1.1 中即为 <body>,svg 的宽将等于页面宽度);

  • viewBox:简单地说是用来指定可见区域的。

代码片段 1.1 显示效果如下,红色边框为 svg 的画布区域:

得到的是一个宽高都超过了 100px 的矩形,而非边长为 100px 的正方形,其原因与 viewBox 属性有关,要想解释清楚,就需要先介绍下 svg 的坐标系统。

坐标系统

视口坐标系

canvas 类似,<svg> 元素也有一个坐标系统(网格系统),其以页面的左上角为原点 (0, 0),单位为 px,x 轴方向朝右,y 轴方向朝下,称为视口坐标系:

(图片截自 MDN)

用户坐标系与 viewBox

svg 还有个用户坐标系,在初始时与视口坐标系相同,但可以通过 viewBox 进行修改,用于指定 svg 的可见区域。viewBox 的值有 4 个参数,min-xmin-ywidthheight,前 2 个参数一般为 0,不为 0 时则需要进行移动;后 2 个为可见区域的宽高。我通过 ps 给代码片段 1.1 画了个示意图如下:

蓝色矩形代表 svg 的视口坐标系,原点在 (0, 0),宽高分别为 300px 和 200px,宽高比为 3 : 2。红色矩形为通过 viewBox 修改后的用户坐标系,宽高分别为 120px 和 80px,宽高比同样为 3 : 2。黄色正方形则是在视口坐标系下绘制的边长为 100px 的正方形。可见看到,红色矩形在黄色正方形上截取了一块由白色虚线框起来的区域。

宽高比一致

代码片段 1.1 中,视口与用户坐标系的宽高比一致,并且 viewBox 的前 2 个参数为 0,用户坐标系将拉伸占据整个视口:

如果遇上 viewBox 的前 2 个参数不为 0 的情况,比如都是 50

html 复制代码
<!-- 代码片段 1.1.2 -->
<svg id="mySvg1" width="300" height="200" viewBox="50 50 120 80">
  <rect x="0" y="0" width="100" height="100"></rect>
</svg>

则用户坐标系的左上角就移动到 (50, 50),注意,对于 rect 的绘制还是从 (0, 0) 开始:

然后一样是将用户坐标系拉伸占据整个视口:

最终在浏览器呈现的效果如下:

宽高比不相等

viewBox 的前 2 个参数为 0

再来看一个视口和用户坐标系宽高比不相等的情况:

html 复制代码
<!-- 代码片段 1.2 -->
<svg width="300" height="200" viewBox="0 0 200 100">
  <rect x="0" y="0" width="100" height="100"></rect>
</svg>

运行到浏览器的效果如下:

此时移动和缩放用户坐标系就与 <svg> 的另一个属性 preserveAspectRatio 有关了,其默认值为 xMidYMid meet。这一点可以通过打印 svg 对象查看属性得知:

html 复制代码
<!-- 代码片段 1.2.1 -->
<svg id="mySvg1" width="300" height="200" viewBox="0 0 200 100">
  <rect x="0" y="0" width="100" height="100"></rect>
</svg>

<svg
  id="mySvg2"
  width="300"
  height="200"
  viewBox="0 0 200 100"
  preserveAspectRatio="xMidYMid meet"
  >
  <rect x="0" y="0" width="100" height="100"></rect>
</svg>
<script>
  window.onload = function () {
    const s1 = document.getElementById('mySvg1')
    const s2 = document.getElementById('mySvg2')
    console.log(s1.preserveAspectRatio)
    console.log(s2.preserveAspectRatio)
  }
</script>

打印结果可以看到没有定义 preserveAspectRatio 属性的 svg 和定义了 preserveAspectRatio="xMidYMid meet" 的 svg 的打印结果是一样的:

并且通过查看原型上的属性,也能看到不同的值代表的含义:

xMidYMid 代表用户坐标系的 x 轴与 y 轴的中点要移动到与视口坐标系的 x 轴与 y 轴中点对齐,meet 代表缩放时要保持宽高比,直到有一个方向完全铺满视口坐标系,并且保证整个 viewBox(下图中的红色矩形 )在视图范围内可见(下图中的蓝色矩形):

viewBox 的前 2 个参数不为 0

如果视口和用户坐标系的宽高比不一样,并且 viewBox 的前 2 个参数也不为 0

html 复制代码
<!-- 代码片段 1.2.2 -->
<svg width="300" height="200" viewBox="50 50 200 100">
  <rect x="0" y="0" width="100" height="100"></rect>
</svg>

这种情况下,会先根据 viewBox 的后 2 个参数,也就是 widthheight,得到用户坐标系(左上角为 (0, 0))与截取的图形:

然后依旧是先让用户和视口坐标系的 x 轴 y 轴中心对齐,再进行保持宽高比例地拉伸,直到有一个方向铺满了视口坐标系(本例为 x 轴方向),最后按照拉伸的比例(本例为 svg 的宽 300 与 viewBox 的宽 200 之比,为 3 / 2),移动用户坐标系(本例为 50 * 3 / 2 = 75),当 min-xmin-y 为正数时,则代表向坐标轴的负方向移动:

代码片段 1.2.2 最终在浏览器呈现的效果如下:

相关推荐
疯狂的沙粒5 分钟前
Vue 前端大屏做多端屏幕适配时,如何让其自动适配多种不同尺寸的屏幕?
前端·javascript·vue.js
范小多9 分钟前
24小时学会Python Visual code +Python Playwright通过谷歌浏览器取控件元素(连载、十一)
服务器·前端·python
ooolmf9 分钟前
matlab2024读取温度01
java·前端·javascript
打工人小夏11 分钟前
前端vue3项目使用nprogress动画组件,实现页面加载动画
前端
一颗宁檬不酸12 分钟前
前端农业商城中产品产地溯源功能的实现
前端
李少兄20 分钟前
深入理解前端中的透视(Perspective)
前端·css
江公望29 分钟前
HTML5 History 模式 5分钟讲清楚
前端·html·html5
云和数据.ChenGuang36 分钟前
Zabbix Web 界面安装时**无法自动创建配置文件 `zabbix.conf.php`** 的问题
前端·zabbix·运维技术·数据库运维工程师·运维教程
码界奇点39 分钟前
Java Web学习 第15篇jQuery万字长文详解从入门到实战解锁前端交互新境界
java·前端·学习·jquery
前端老曹1 小时前
vue3 三级路由无法缓存的终极解决方案
前端·javascript·vue.js·vue