SVG 的 viewBox 和 preserveAspectRatio 是理解 SVG 缩放的核心。下面用"容器 vs 画布"的比喻,配合实例和计算过程,帮你彻底搞懂。


一、核心概念:viewport vs viewBox

viewport(视口) = 你看到的"窗口"大小 → 由 width/height 决定
viewBox(画布) = 你画图的"坐标系" → 由 viewBox="min-x min-y w h" 决定

关键比喻:

  • viewport = 相框(物理尺寸)

  • viewBox = 照片(逻辑内容)

  • 浏览器的工作 = 把"照片"塞进"相框"里

<!-- 相框 400×200,照片 40×20 → 宽高比相同,完美适配 -->
<svg width="400" height="200" viewBox="0 0 40 20">
  <rect x="10" y="5" width="20" height="10" fill="red"/>
</svg>

当宽高比不一致时,问题就来了:

<!-- 相框 600×300(2:1),照片 40×30(4:3)→ 比例冲突! -->
<svg width="600" height="300" viewBox="0 0 40 30">
  <rect x="10" y="5" width="20" height="15" fill="red"/>
</svg>

这时候 preserveAspectRatio 就登场了。


二、preserveAspectRatio 语法

preserveAspectRatio="<alignment> <meetOrSlice>"

1. alignment(对齐方式)— 9 种组合

可以拆成 X 轴对齐 + Y 轴对齐,类似 CSS 的 background-position:


xMin(左)xMid(中)xMax(右)
YMin(上)xMinYMinxMidYMinxMaxYMin
YMid(中)xMinYMidxMidYMid(默认)xMaxYMid
YMax(下)xMinYMaxxMidYMaxxMaxYMax

2. meetOrSlice(缩放规则)— 2 种

值行为CSS 等价说明
meet(默认)等比缩放,完整显示 viewBoxbackground-size: contain可能留白,但内容不裁切
slice等比缩放,填满 viewportbackground-size: cover不留白,但内容可能被裁切

另外还有一个特殊值 none:不保持宽高比,直接拉伸填满(会变形)。


三、图解计算过程(重点!)

用这个例子来推演:

<svg width="600" height="300" viewBox="0 0 40 30">
  <rect x="10" y="5" width="20" height="15" fill="red"/>
</svg>

第一步:算缩放倍数

  • 宽方向:600 ÷ 40 = 15 倍

  • 高方向:300 ÷ 30 = 10 倍

第二步:根据 meet/slice 选倍数

meet(取小值 = 10 倍)

viewBox 放大后:40×10 = 400 宽,30×10 = 300 高
viewport 尺寸:  600 宽,300 高

→ 高度刚好填满,宽度只占了 400/600,左右各有 100px 留白

┌─────────────────────────────────────────┐  ← viewport 600×300
│  留白100  │████████████████████│  留白100  │
│           │   viewBox 400×300   │           │
│           │   (等比放大10倍)     │           │
└─────────────────────────────────────────┘

slice(取大值 = 15 倍)

viewBox 放大后:40×15 = 600 宽,30×15 = 450 高
viewport 尺寸:  600 宽,300 高

→ 宽度刚好填满,高度超出了 450-300 = 150px 被裁掉

┌─────────────────────────────────────────┐  ← viewport 600×300
│█████████████████████████████████████████│
│█████████████████████████████████████████│  ← viewBox 600×450
│█████████████████████████████████████████│     (等比放大15倍)
│█████████████████████████████████████████│     下方150px被裁掉
└─────────────────────────────────────────┘

第三步:根据 alignment 决定位置

以 xMinYMin slice 为例:左上角对齐,裁掉右下部分

以 xMidYMid slice 为例:居中对齐,上下各裁 75px

以 xMaxYMax slice 为例:右下角对齐,裁掉左上部分


四、实际代码对比

<!-- 1. 默认:xMidYMid meet → 居中,完整显示,左右留白 -->
<svg width="600" height="300" viewBox="0 0 40 30" style="border:1px solid blue">
  <rect x="10" y="5" width="20" height="15" fill="red"/>
</svg>

<!-- 2. none → 拉伸变形,填满 -->
<svg width="600" height="300" viewBox="0 0 40 30" preserveAspectRatio="none" style="border:1px solid blue">
  <rect x="10" y="5" width="20" height="15" fill="red"/>
</svg>

<!-- 3. xMinYMin slice → 左上对齐,裁掉右下 -->
<svg width="600" height="300" viewBox="0 0 40 30" preserveAspectRatio="xMinYMin slice" style="border:1px solid blue">
  <rect x="10" y="5" width="20" height="15" fill="red"/>
</svg>

<!-- 4. xMidYMid slice → 居中,上下裁切 -->
<svg width="600" height="300" viewBox="0 0 40 30" preserveAspectRatio="xMidYMid slice" style="border:1px solid blue">
  <rect x="10" y="5" width="20" height="15" fill="red"/>
</svg>

五、常见误区 & 最佳实践

误区 1:不设 viewBox

<!-- 没有 viewBox,SVG 退化为"位图",缩放直接硬拉伸失真 -->
<svg width="400" height="200">
  <circle cx="50" cy="50" r="30"/>
</svg>

误区 2:width/height 与 viewBox 冲突

<!-- 显式设置 width/height 可能导致跨浏览器解析不一致 -->
<!-- 推荐:只设 viewBox,width/height 用 CSS 控制 -->
<svg viewBox="0 0 40 30" style="width:100%; height:auto">

最佳实践速查

场景推荐设置
图标(完整显示)xMidYMid meet(默认即可)
背景图(填满容器)xMidYMid slice
响应式 SVG只设 viewBox,CSS 设 width:100%; height:auto
禁止变形永远不要设 preserveAspectRatio="none"

六、一句话总结

viewBox 定义"画什么",viewport 定义"在哪显示",preserveAspectRatio 定义"怎么塞进去"。

  • meet = contain = 完整显示,可能留白

  • slice = cover = 填满容器,可能裁切

  • none = 拉伸变形(一般不用)