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 最终在浏览器呈现的效果如下:

相关推荐
编程零零七2 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
(⊙o⊙)~哦4 小时前
JavaScript substring() 方法
前端
无心使然云中漫步4 小时前
GIS OGC之WMTS地图服务,通过Capabilities XML描述文档,获取matrixIds,origin,计算resolutions
前端·javascript
Bug缔造者4 小时前
Element-ui el-table 全局表格排序
前端·javascript·vue.js
xnian_5 小时前
解决ruoyi-vue-pro-master框架引入报错,启动报错问题
前端·javascript·vue.js
麒麟而非淇淋5 小时前
AJAX 入门 day1
前端·javascript·ajax
2401_858120536 小时前
深入理解MATLAB中的事件处理机制
前端·javascript·matlab
阿树梢6 小时前
【Vue】VueRouter路由
前端·javascript·vue.js
随笔写7 小时前
vue使用关于speak-tss插件的详细介绍
前端·javascript·vue.js
史努比.7 小时前
redis群集三种模式:主从复制、哨兵、集群
前端·bootstrap·html