上篇文章我介绍了如何创建和使用 svg, 本文将进一步介绍如何使用 svg 绘制如下所示的图形,及相关知识点。(之后的文章会以这次绘制的静态图形为基础实现 MG 动画效果)
svg 元素
本案例选择直接在 html 中使用 <svg>
元素来绘制图形:
<!-- 代码片段 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 -->
<svg width="100%" height="600" viewBox="0 0 800 600">
<!-- 绘制内容 -->
</svg>
</body>
</html>
在代码片段 1 中,我通过给 <svg>
设置 viewBox
,并让 width
为 100%
以实现 svg 内容能够跟随页面大小自适应缩放(如果你不清楚原理可以去看看关于 viewBox 的介绍)。
绘制十字形
下面开始绘制位于图形中间的十字形。直接在 <svg>
里使用对应的元素,就能生成相应的图形,然后可以通过属性来定义图形的位置和大小颜色等。比如十字形我使用的是 <line>
来生成两根交叉的线条,属性 x1
、y1
和 x2
、y2
用来定义线条的两个端点坐标。
组合元素的容器 <g>
因为两根线条的线宽 stroke-width
和颜色 stroke
是一样的,所以我把相同的属性定义在了用来组合元素的容器 <g>
元素上,然后让作为其子元素的 <line>
继承:
<!-- 代码片段 1.1 -->
<svg width="100%" height="600" viewBox="0 0 800 600">
<!-- 十字 -->
<g stroke-width="8" stroke="#FAB748">
<!-- 横 -->
<line x1="380" y1="300" x2="420" y2="300"></line>
<!-- 竖 -->
<line x1="400" y1="280" x2="400" y2="320"></line>
</g>
</svg>
十字效果如下:
使用 css
在 SVG 中,有些属性,像 stroke-width
、stroke
等属于 Presentation Attributes,除了可以直接在元素上通过属性定义,还可以直接使用 css 设置,比如:
<!-- 代码片段 1.1.1 -->
<head>
<style>
.cross {
stroke-width: 8;
stroke: red;
}
</style>
</head>
<body>
<svg width="100%" height="600" viewBox="0 0 800 600">
<g class="cross" stroke-width="8" stroke="#FAB748">
<!-- 省略 -->
</g>
</svg>
</body>
得到的会是一个红色的十字,说明定义在 <head>
里的 <style>
的样式优先级高于通过元素属性定义的样式:
css 也可以定义在之后会介绍的 <defs>
中:
<!-- 代码片段 1.1.2 -->
<svg width="100%" height="600" viewBox="0 0 800 600">
<defs>
<style>
.cross {
stroke: skyblue;
}
</style>
</defs>
<!-- 十字 -->
<g class="cross" stroke-width="8" stroke="#FAB748">
<!-- 省略 -->
</g>
</svg>
这样得到的就是蓝色十字,并且定义在 <defs>
里的 <style>
的优先级高于定义在 <head>
中的:
还可以直接在元素上通过 style
定义,并且优先级最高:
<!-- 代码片段 1.1.3 -->
<svg width="100%" height="600" viewBox="0 0 800 600">
<!-- 十字 -->
<g style="stroke: goldenrod" stroke-width="8">
<!-- 省略 -->
</g>
</svg>
绘制半圆
绘制半圆的方法不止一种,我选择的是直接使用路径元素 <path>
。为了让半圆位于十字的下方,我们需要在定义十字的前面定义好半圆:
<!-- 代码片段 1.2 -->
<svg width="100%" height="600" viewBox="0 0 800 600">
<!-- 中间的圆 -->
<!-- 紫色大半圆 -->
<path d="M 400 384 A 84 84 0 1 0 400 216" fill="#8052E8"></path>
<!-- 蓝色大半圆 -->
<path d="M 400 384 A 84 84 0 1 1 400 216" fill="#26AAD6"></path>
<!-- 紫色小半圆 -->
<path d="M 400 328 A 28 28 0 1 1 400 272" fill="#8052E8"></path>
<!-- 蓝色小半圆 -->
<path d="M 400 328 A 28 28 0 1 0 400 272" fill="#26AAD6"></path>
<!-- 十字 -->
</svg>
效果如下:
我们以位于十字下方的紫色小半圆的绘制为例,解释 <path>
的用法。它有一个基本属性 d
,用来设置路径点的位置,d
的值是 "命令 + 参数" 的序列,命令都是区分大小写的,大写代表绝对定位,小写表示从上一个点开始计算的相对定位:
- 其必须以
M
(Move To) 命令开头,指示解析器从哪个点开始绘制,M 400 328
即表示先移动到 (400, 328) 这个点,也就是圆弧的起点; A 28 28 0 1 1 400 272
,A
命令为弧形命令,有 7 个参数:- 前 2 个参数分别是 x 轴半径(rx)和 y 轴半径(ry),因为是半圆,所以应该是相等的,我设置为 28;
- 第 3 个参数为旋转角度(x-axis-rotation,值为正数时顺时针旋转),当 rx 和 ry 相等时设置无效,所以为 0;
- 第 4 个参数(large-arc-flag)有两个可选值,0 代表取小角度弧线,1 代表取大角度弧线;
- 第 5 个参数(sweep-flag)也是有两个可选值,0 代表逆时针,1 代表顺时针;
- 最后 2 个参数为圆弧终点的 x、y 坐标,因为起点在 (400, 328),半径为 28,所以终点 x 为 400,y 为 328 - 28 * 2 = 272;
fill
作为通用的属性,给画好的半圆填充颜色,否则默认填充为黑色。
d
属性支持的命令还有很多,比如绘制水平线的 H/h
等,从阿里图标库下载的图标,几乎都是使用 <path>
绘制,可以看出 <path>
的强大。
绘制矩形
现在来绘制位于圆形上下两端的 4 个小正方形:
定义重复元素 <defs>
可以看到,如果我们绘制好了一端的小正方形,在另一端只需要改一下定位是可以复用的,所以我们在 <defs>
内定义好正方形:
<!-- 代码片段 1.3 -->
<svg width="100%" height="600" viewBox="0 0 800 600">
<defs>
<!-- 蓝色正方形 -->
<rect id="blueRect" width="42" height="42" fill="#26AAD6"></rect>
<!-- 绿色正方形 -->
<rect
id="greenRect"
width="34"
height="34"
fill="none"
stroke-width="8"
stroke="#51E88D"
></rect>
</defs>
<!-- 省略 -->
</svg>
正方形使用绘制矩形的 <rect>
来绘制,width
和 height
定义了宽高,蓝色正方形为实心的,所以用 fill
填充,绿色正方形为空心的,所以使用 stroke
描边。
引用元素 <use>
在 <defs>
里定义的可复用元素是看不到的,需要使用 <use>
来引用并显示:
<!-- 代码片段 1.3.1 -->
<svg width="100%" height="600" viewBox="0 0 800 600">
<defs>
<!-- 定义正方形 -->
</defs>
<!-- 显示正方形 -->
<!-- 左上角蓝色 x = 400 - 42; y = 300 - 84 - 42 -->
<use x="358" y="174" href="#blueRect"></use>
<!-- 右下角蓝色 x = 400; y = 300 + 84 -->
<use x="400" y="384" href="#blueRect"></use>
<!-- 右上角绿色 x = 400 + 4; y = 300 - 84 - (42 - 4) -->
<use x="404" y="178" href="#greenRect"></use>
<!-- 左下角绿色 x = 400 - 42 + 4; y = 300 + 84 + 4 -->
<use x="362" y="388" href="#greenRect"></use>
</svg>
引用时通过 x
、y
设置位置,href
的值为引用对象的 id 或 url。<defs>
里除了可以定义基本图形,也可以定义组合图形(<g>
)或是样式(<style>
)等。在 <use>
上设置属性 width
和 height
是无效的,除非引用的元素具有 viewBox
属性,比如是另一个 <svg>
或是下面介绍的 <symbol>
元素。
symbol 元素
这里顺便介绍下和 <defs>
类似的 <symbol>
。<defs>
元素本身是没有专有属性的,使用时一般也不会添加属性。而 <symbol>
则提供了 viewBox
、x
、y
、width
和 height
等属性:
<!-- 代码片段 1.3.2 -->
<body>
<svg>
<symbol id="myRect" viewBox="0 0 100 100">
<rect width="100" height="100"></rect>
</symbol>
</svg>
<svg>
<use href="#myRect" width="50" height="50"></use>
</svg>
<svg width="50" height="50">
<use href="#myRect"></use>
</svg>
</body>
通常是在一个 <svg>
内使用 <symbol>
定义好要复用的内容,这些内容不会显示。然后在别的 <svg>
内使用 <use>
引用并显示,并且可以通过给 <svg>
或 <use>
设置宽高来实现对复用图形的缩放。
<symbol>
常见的应用场景是定义图标,比如阿里图标库的图标就可以通过 symbol 引用的方式来使用:
绘制三角形
我使用前面介绍过的 <path>
绘制位于中心圆两侧的三角形,用到了 2 个新命令:
l
(Line to)用于绘制线段,其参数就是线段终点的坐标,因为是小写的,所以采用的是相对定位;Z
用于闭合路径,即从当前点绘制一条直线到路径起点,不区分大小写。
<!-- 代码片段 1.4 -->
<svg width="100%" height="600" viewBox="0 0 800 600">
<!-- 省略 -->
<!-- 亮黄色三角形 -->
<g fill="#FFED5D">
<!-- 左 -->
<path d="M 316 300 l 0 -84 l -84 84 Z"></path>
<!-- 右 -->
<path d="M 484 300 l 0 84 l 84 -84 Z"></path>
</g>
<!-- 橘色三角形 -->
<g fill="#FAB748">
<!-- 左 -->
<path d="M 316 300 l 0 84 l -84 -84 Z"></path>
<!-- 右 -->
<path d="M 484 300 l 0 -84 l 84 84 Z"></path>
</g>
</svg>
至此,图形绘制成果如下:
剪切
接着绘制如下图箭头所指的具有剪切效果的 4 个小三角形:
先在 <defs>
内使用 <clipPath>
定义好作为剪切轮廓的三角形,此处三角形的绘制采用的是用于绘制多边形的 <polygon>
,它的属性 points
的值为各个点的坐标,最后一个点会自动与第一个点连线闭合:
<!-- 代码片段 1.5 -->
<defs>
<!-- 用于剪切的三角形 -->
<!-- 左上 -->
<clipPath id="cut-off-left-top">
<polygon points="316 216, 358 216, 358 174" />
</clipPath>
<!-- 右上 -->
<clipPath id="cut-off-right-top">
<polygon points="442 216, 484 216, 442 174" />
</clipPath>
<!-- 左下 -->
<clipPath id="cut-off-left-bottom">
<polygon points="316 384, 358 384, 358 426" />
</clipPath>
<!-- 右下 -->
<clipPath id="cut-off-right-bottom">
<polygon points="442 384, 484 384, 442 426" />
</clipPath>
</defs>
然后以左上角的小剪切三角形为例说明如何应用剪切图形:
<!-- 代码片段 1.5.1 -->
<!-- 左上角剪切三角 -->
<g clip-path="url(#cut-off-left-top)" fill="#FAB748">
<g transform="rotate(45, 337, 195)">
<rect x="316" y="162" width="50" height="6"></rect>
<rect x="316" y="174" width="50" height="6"></rect>
<rect x="316" y="186" width="50" height="6"></rect>
<rect x="316" y="198" width="50" height="6"></rect>
<rect x="316" y="210" width="50" height="6"></rect>
<rect x="316" y="222" width="50" height="6"></rect>
</g>
</g>
我使用 <rect>
定义了多个矩形,并把它们放入 <g>
内归为一组:
然后通过给形变属性 transform
赋值 rotate(45, 337, 195)
,使得它们一起顺时针旋转了 45°,旋转中心点坐标为 (337, 195):
再在外面包裹上一个 <g>
,通过 clip-path
引用代码片段 1.5 定义好的三角形作为剪切轮廓并使用 fill
上色:
绘制圆形
绘制位于四周的小圆比较简单,在 <defs>
内使用 <circle>
定义好圆形,r
为半径:
<!-- 代码片段 1.6 -->
<defs>
<!-- 四个角的蓝色圆 -->
<circle id="blueCircle" r="20" fill="#26AAD6"></circle>
</defs>
然后进行引用:
<!-- 代码片段 1.6.1 -->
<!-- 四周小圆 -->
<!-- 左上 x = 400 - 84 - 84; y = 300 - 84 - 84 -->
<use x="232" y="132" href="#blueCircle"></use>
<!-- 左下 y = 300 + 84 + 84 -->
<use x="232" y="468" href="#blueCircle"></use>
<!-- 右上 x = 400 + 168 -->
<use x="568" y="132" href="#blueCircle"></use>
<!-- 右下 -->
<use x="568" y="468" href="#blueCircle"></use>
绘制图片
使用 <image>
元素绘制图片,注意是通过 href
属性引用,而不是像 html 中的 <img>
使用 src
:
<!-- 代码片段 1.7 -->
<image x="214" y="286" href="../imgs/juejin.png" width="40"></image>
通过 x
,y
定义图片位置,如果不写则值默认为 0;width
指定图片的宽度,高度会按照图片原宽高比自动调整,如果宽高均不设置则默认为原图大小。
绘制文字
最后在右下角使用 <text>
签个名,在 <text>
内还可以使用 <tspan>
对部分文字设置单独的样式:
<!-- 代码片段 1.8 -->
<text x="600" y="580" font-size="12" fill="#26AAD6">
by:
<tspan font-size="14">亦黑迷失</tspan>
</text>
SVG 的优缺点
至此,绘制完毕。回顾绘制过程,不难发现 svg 的优点有很多,比如绘制都是声明式地直接使用对应的元素,并且可以通过 css 和 js 修改,也就更利于创建动画。放大浏览器的页面缩放比例,可以看到图形并不会像使用 canvas 绘制的图形那样出现失真:
查看源代码,可以看到所有使用 svg 绘制的代码,这也就利于 SEO:
当然 svg 也有缺点,比如当 svg 很复杂时,DOM 变得复杂,渲染就会变得比较慢。