前言
SVG 以其奇异的语法和难以辨认的字母和数字组合,一直让我望而却步。直到最近觉得可以尝试在博客中用 SVG 绘制一些有趣的图案,所以还是查阅相关资料学习了一下,本文是我学习到的 SVG 相关知识的总结。
基本概念
SVG(可缩放矢量图形) 是一种基于 XML 语法的图像格式,SVG 是对图像的形状描述,所以它本质上是文本文件,且不管放大多少倍都不会失真。
一个简单的 SVG 文档由 <svg>
元素和基本的形状元素构成,<svg>
元素的基本构成如下所示:
<svg
viewBox="0 0 10 10"
width="10"
height="10"
></svg>
<svg>
元素上最关键的属性就是 viewBox
, width
和 height
,viewBox 描述了用户坐标系的位置和大小,而 width 和 height 决定了 <svg>
元素在文档中显示的大小。
SVG(以及大多数其他计算机图形)的坐标系与数学、图形等的坐标系有点不一样。SVG 的坐标系统是:以左上角为 (0, 0) 坐标点,x 轴正方向是向右,y 轴正方向是向下,坐标默认以像素为单位。
学习 SVG 需要理解的关键概念之一是多个坐标系的存在,并且它们之间相互转换的机制通常是引起混淆的主要原因。理解这些坐标系之间的相互关系和如何进行转换,是掌握 SVG 的关键,因为它们影响着图形的位置、大小和展示方式。
对于这 SVG 容易造成混淆的机制,我想了个“画布和墙”的比喻,不一定准确,但是希望能对大家理解有一定帮助。
画布和墙
想象你面前有一块无限大小的神奇画布,你可以在画布上的任何位置绘制所要的内容。但是画布的正前方被一面巨大的墙遮挡,墙壁延伸到与画布完全相同的宽度和高度,刚好覆盖了画布的每个部分。不过墙上开了一个可调节大小的窗口(视口),你可以通过这个窗口看到画布上的一小部分内容。
神奇画布还有一个功能,它会根据你绘制区域的大小(viewbox)与窗口大小的对比自动移动画布的位置(缩放),以适应窗口的尺寸。当绘制区域大于窗口尺寸时,画布会后移使画布远离窗口前的你,所以绘制内容会变小。反之当绘制区域小于窗口尺寸时,则前移放大内容占满窗口。在其它情况,你还可以平移画布并决定通过窗口显示该区域的哪个区域以及该区域将显示多少。
虽然说你可以在画布的任何位置绘制,但是有时为了省事,你可能会决定只在面前和窗口一样大的区域上进行绘制,这样通过窗口看到的内容就是绘制的全部内容。
视口
墙壁中的窗口,正式名称被称为视口(viewport),视口这个概念也出现其它领域中,比如浏览器视口,虚拟现实 (VR) 视口和相机取景器等场景,它基本上指通过一个窗口或特定范围来显示较大空间的其中一部分内容。
我们可以通过 <svg>
的 width
和 height
属性来定义视口的大小。当然,你也可以不给视口设置宽和高,这就会交给用户代理程序去决定视口的大小,在 chrome 浏览器中一般默认大小是 300px * 150px。
如果我们只设置了 width
和 height
属性,没有设置 viewBox
属性,此时视口大小和绘制区域大小是完全重合的。我们这时候直接在 <svg>
上绘制的图形,可以视为相对于在视口的坐标系上绘制。
<svg width="20" height="20">
<circle cx="10" cy="10" r="10" fill="#e5e7eb"/>
</svg>
示例定义了在一个宽 20 个单位、高 20 个单位的视口上,绘制了一个以视口的 (10, 10) 点 (视口的中心点) 为圆心,半径为 10 个单位画圆,效果如下所示。
viewBox
<svg>
上的 viewBox
属性就是“画布和墙”中所说的绘制区域,我们在 <svg>
中绘制的所有 SVG 形状都是在 viewBox
上绘制的。viewBox
的定义如下:
viewBox="min-x min-y width height"
viewBox
的字面意思是视图盒子,可以把 viewBox
理解为在无限大小的画布上确立了一个矩形的绘制区域,只有出现在这个矩形区域里面的 SVG 内容才能被看到,前两个数字定义了矩形区域的左上角坐标,后两个数字定义了矩形区域的右下角坐标。
现在我们同时定义 <svg>
元素的视口和 viewBox 的大小,视口的宽为 20 个单位、高为 20 个单位,viewBox 被设置为 0 0 10 10
所以它的左上角坐标为 (0, 0),右下角坐标为 (10, 10),在 viewBox 两个坐标共同确立的矩形绘制区域上面画了一个半径为 5 的圆。
<svg
width="20"
height="20"
viewBox="0 0 10 10"
fill='#e5e7eb'
>
<circle cx="5" cy="5" r="5" fill='#1e293b' />
</svg>
假设 viewBox 不会进行缩放,那么它在视图中可能会如下面这个例子所示,一个 10*10(你绘制的任何形状都将限制在这 100 单位的坐标空间内)大小的矩形区域在视口的左上角。
但是根据 SVG (可缩放矢量图形) 的名称可知,缩放是 SVG 的一个关键特性,viewBox 会根据某些规则来进行缩放以适应视口的大小。
原来 10*10
大小的 viewBox 保持原来宽高比扩大了 2 倍,这意味着 viewBox 坐标系中的 1 个单位实际被映射成了 20/10 = 2 个单位,这就是为什么在 viewBox 上面绘制的半径为为 5 的圆,其半径在缩放后变成了 10 个单位。
preserveAspectRatio
上面的例子中 SVG 的宽高比正好和 viewBox 的宽高比是一样的,都是 1:1,所以 viewBox 缩放的效果如此丝滑。但是如果 viewBox 的宽高比和 SVG 视窗的宽高比不同,那么在拉伸 viewBox 来适应视口的时候,就可能导致 SVG 图形发生扭曲。
这种宽高比不同的情况,我们可以指示 SVG 如何进行缩放,通过使用 <svg>
元素的 preserveAspectRatio
属性来执行此操作,移动神奇画布的功能就依赖于这个属性。preserveAspectRatio 的定义如下:
preserveAspectRatio="<align> [<meetOrSlice>]"
preserveAspectRatio
的属性采用两个空格分隔的值。align
告诉 viewBox 如何在视口内对齐,该值本身由两部分组成。meetOrSlice
告诉 viewBox 如何保留宽高比(可选)。
align
指定对齐方式的 align
由两部分组成。第一部分指定 x 轴方向对齐方式,第二部分指定 y 轴方向对齐方式。以下是 x 和 y 对齐值的列表:
值 | 含义 |
---|---|
none | 通过拉伸 viewBox 来适应整个视窗,不管宽高比 |
xMin | viewBox 和 viewport 左边缘对齐 |
xMid | 默认值,viewBox 和 viewport x 轴中心对齐 |
xMax | viewBox 和 viewport 右边缘对齐 |
YMin | viewBox 和 viewport 上边缘对齐 |
YMid | 默认值,viewBox 和 viewport y 轴中心对齐 |
YMax | viewBox 和 viewport 下边缘对齐 |
其中带 x 的值可以作为 align
的第一部分,带 y 的值作为第二部分,x 的值和 y 的值组合一下再加上一个特立独行的 none
,总共就 10 种对齐方式。
meetOrSlice
preserveAspectRatio
属性的第二部分的值支持 meet
和 slice
,其中 meet
是默认值。
值 | 含义 |
---|---|
meet | 默认值,保留宽高比并按比例小方向的缩放 viewBox 以适合视口 |
slice | 保留宽高比并按比例大的方向放大 viewBox,裁剪掉 viewBox 超过视口的任何部分 |
meet 和 slice 的值我是这么理解的,meet 有"遇见、相遇"的意思,表示 viewBox 会尽力在视口中展示它的全部内容,好让大家可以更全的看到 viewBox 上的内容。而 slice 则有 "部分、裁剪"的意思,所以表示 viewBox 会尽力放到最大,可能只能看到其部分内容,超出视口的部分会被裁剪。
例子🌰
让我们直接通过直观的例子来看各种 preserveAspectRatio
设置的效果。
以下例子均使用下面的 <svg>
,其中 width、height 和 preserveAspectRatio 会根据不同例子而修改,其它的属性都是一致的。 viewBox
用一个虚线矩形区域给可视化了出来,方便大伙理解,实际是没有的。
<svg width="300" height="200" viewBox="0 0 50 50" preserveAspectRatio="xMidYMid meet">
<circle cx="25" cy="25" r="25" fill="none" stroke="black" stroke-width="0.5"></circle>
</svg>
首先是一个展示 viewBox 在 x 轴方向对齐效果的示例。其中 preserveAspectRatio 的值除了 x 方向是可变,y 方向固定为 Ymin, meetSlice 固定为 meet。
我们研究下各个方向的宽高比,y 轴方向的宽高比为(200/50 = 4)小于 x 轴方向的宽高比(300 / 50 = 6)。因为缩放模式是 meet,所以 viewBox 将在比例小的方向即 y 轴方向进行缩放至适合视口大小。x 轴在这种情况下会出现较多的空余空间(可以想象成神奇画布已经沿着 y 轴扩大到和窗口一样高的大小,但是窗口的其它部分没有被画布给覆盖),因此可以通过 preserveAspectRatio 指定 viewBox 在 x 轴上的对齐方式。
下面是展示 viewBox 在 y 轴方向对齐效果的示例。其中 preserveAspectRatio 的值除了 y 方向是可变,x 方向固定为 xMin, meetSlice 固定为 meet。
这和前一个示例一样,但是 width 和 height 分别为 200、300,因此沿 x 轴的宽高比 (200 / 50 = 4) 小于 y 轴的宽高比 (300 / 50 = 6)。viewBox 将在 x 轴方向填充视口,但不会在 y 轴方向填充。
现在这个是展示 preserveAspectRatio 的第二部分 meetOrSlice
属性值的示例。其中 meetSlice 的值是可变,x 方向固定为 xMin, y 方向固定为 yMin。
前面两个 demo 展示的都是 meet,所以这里不多说。说下 slice 的表现,可以知道比较大的宽高比是 x 轴方向 (300 / 50 = 6),所以 viewBox 中的所有内容会扩大为原来的 6 倍。此时 x 轴方向刚好填充满视口,而 y 轴方向被裁剪了一部分内容。
总结
本文介绍了 SVG 的基本概念,包括 ViewBox、Viewport 以及坐标系转换。关键的 preserveAspectRatio 属性控制着 SVG 在不同视口大小下的展示方式。理解这些概念对于创建响应式、交互式图形至关重要。建议明确设置 ViewBox 以保持图形比例,正确选择对齐方式。通过实践并结合 CSS 和 JavaScript,SVG 可以创建出更加生动、交互性强的图形效果。