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

相关推荐
腾讯TNTWeb前端团队1 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰4 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪4 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪4 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy5 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom6 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom6 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom6 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom6 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom6 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试