SVG 原理及使用

329 阅读8分钟

SVG 图形可以像 HTML 那样手动编写 ……


SVG 是使用 XML 来描述二维图形和绘图程序的语言,也就是使用 XML 格式定义的可伸缩矢量图形。它的主要应用场景是用来绘制 icon 和绘制动画。

对于 Web 而言,与其他图像格式相比,使用 SVG 的主要优势在于:

  • SVG 与 JPEG、GIF 等图像比起来,尺寸更小,且可压缩性更强。
  • SVG 是矢量图,可以伸缩,适用各种分辨率
  • SVG 文件是纯粹的 XML,图像中的文本是可选的,同时也是可搜索的(很适合制作地图)
  • SVG 可以与 CSS 结合,获得更强的扩展性;并可设置 class,通过 class 赋予 css 样式

一 基本概念及标签

1. viewport / viewBox / preserveAspectRatio

示例代码如:

<svg width="400" height="100" viewBox="0 0 40 10" style="border: 1px solid #000000">
  <rect x="10" y="4" width="10" height="5" style="stroke: #000000; fill:none;"/>
</svg>

效果:

viewport 是 svg 图像的可见区域,也就是 <svg width="400" height="100"> 中指定的 width / height。

viewBox 是用于在画布上绘制 svg 图形的坐标系统。svg 绘图并不是按照 viewport 来绘制的,而是按照 viewBox 绘制,也就是说 viewport 是一个容器,而 viewBox 才是画板。

绘制的具体内容会按照 viewport 与 viewBox 的比值放大或缩小。 如上示例等同于:

<svg width="400" height="100" viewBox="0 0 400 100" style="border: 1px solid #000000">
  <rect x="100" y="40" width="100" height="40" style="stroke: #000000; fill:none;"/>
</svg>

如果 viewBox 不指定,默认与 viewport 相同, 即:

<!-- 两者相同 -->
<svg width="400" height="100">
<svg width="400" height="100"  viewBox="0 0 400 100">

preserveAspectRatio 用于当 viewport 和 viewBox 宽高比不相同时,指定这个坐标系在viewport 中是否完全可见,同时也可以指定它在viewport 坐标系统中的位置。

viewport 和 viewBox 宽高比不相同时, viewBox 的缩放会不按比例强制拉伸,导致图像变形,preserveAspectRatio 就是用来设置 viewBox 在拉伸时需要保持的宽高比。

preserveAspectRatio 语法:

preserveAspectRatio=<align> <meetOrSlice>

align 参数控制 viewBox 是否强制进行均匀的缩放,主要控制 viewBox 的位置,主要可分为 x、y 两个部分, 每个部分有三个值,分别是min、mid、max。

取值说明
xMinviewBox的最小x值对齐viewport的左边
xMidviewBox的x轴中点对齐viewport的x轴中点
xMaxviewBox的最大x值对齐viewport的右边
YMinviewBox的最小y值对齐viewport的顶边
YMidviewBox的Y轴中点对齐viewport的Y轴中点
YMaxviewBox的最大y值对齐viewport的底边

meetOrSlice 参数的取值:

取值说明
meet保持宽高比并将viewBox缩放为适合viewport的大小
slice保持宽高比并将所有不在viewport中的viewBox剪裁掉
none不保存宽高比。缩放图像适合整个viewBox,可能会发生图像变形

示例:

<svg width="400" height="100" viewBox="0 0 40 10" preserveAspectRatio="xMidYMid meet" style="border: 1px solid #000000">
  <rect x="10" y="4" width="10" height="5" style="stroke: #000000; fill:none;"/>
</svg>

2. 常用形状标签及属性

常用标签:

标签说明
rect用来创建矩形,以及矩形的变种
circle圆, cx / cy 指定圆点的 x 和 y 坐标, 默认均为0;r 指定圆半径
ellipse椭圆
line线条, 属性 x1 / y1 / x2 / y2 分别定义线条的开始与结束
polygon不少于三个边的图形, 属性 points 定义多边形每个角的 x 和 y 坐标
polyline折线,仅包含直线的形状,用 points 属性定义
path路径

常用属性:

标签属性说明
x图像距离左侧的位置
y图像距离顶端的位置
rx / ry水平半径 / 垂直半径(椭圆,或者矩形的圆角)
width图像宽
height图像高
style用来定义 CSS 属性

对于动画属性见下文。

style除一般 css 属性外,有以下几个常用属性,这些属性也可以直接写在标签内:

style属性说明
fill填充颜色
fill-opacity填充颜色透明度
stroke边框的颜色
stroke-width边框的宽度
stroke-opacity边框的颜色透明度
filter滤镜

对于<path>标签,指定路径的参数比较复杂,对于复杂的图形,建议使用 SVG 编辑器来创建:

// 所有命令均允许小写字母。大写表示绝对定位,小写表示相对定位。
M = moveto
L = lineto
H = horizontal lineto
V = vertical lineto
C = curveto
S = smooth curveto
Q = quadratic Belzier curve
T = smooth quadratic Belzier curveto
A = elliptical Arc
Z = closepath

示例如:

<!-- 开始于位置 250 150,到达位置 150 350,然后从那里开始到 350 350,最后在 250 150 关闭路径 -->
<svg width="100%" height="100%">
  <path d="M250 150 L150 350 L350 350 Z" />
</svg>

二 常用组件库及实践

组件库说明
defs用于嵌入可在 SVG 映像内重用的定义。在<defs>元素中定义的形状不会显示在SVG图像中,须被<use>元素引用才能显示
g将 SVG 形状分组在一起,分组后,您可以像变形单个形状一样变换整个形状。在引用 g 元素之前,须设置其 id
use复用 SVG 文档中其他位置的 SVG 图形,通过 x 和 y 属性指定在何处显示
symbol与 g 标签类似,但 symbol 可以拥有一个独立的 viewBox,以使图形的复用更灵活方便

示例:

<!-- 图形集合 -->
<svg viewBox="0 0 100 100">
  <defs>
    <g id="test">
      <circle r="5" cx="20" cy="25" fill="red" />
      <circle r="5" cx="20" cy="50" fill="blue" />
    </g>
    <g id="test2">
      <line x1="40" y1="25" x2="90" y2="25" stroke-width="8" stroke="blue">
    </g>
    <symbol id="test3">
      <line x1="40" y1="25" x2="90" y2="25" stroke-width="8" stroke="blue">
    </symbol>
  </defs>
</svg>

<!-- 可在另一个文件中引入定义的图形, use 标签的 href 须指向定义的 id -->
<svg width="30" height="30" viewBox="0 0 100 100" style="color: yellow">
  <use herf="#test"></use>
</svg>

<!-- 如果图形使用symbol标签定义的,则引用时不需要再次指定 viewBox -->
<svg width="30" height="30" style="color: yellow">
  <use herf="#test3"></use>
</svg>

Vue 封装 svg 组件示例:

// Icon 组件: Icon.vue
<template>
  <div class="icon-wrapper" :style="{...style}">
    <svg class="icon">
      <use :href="iconName"></use>
    </svg>
  </div>
</template>

<script>
export default {
  name: 'Icon',
  props: {
    name: String,
    prefix: {
      type: String,
      default: 'icon'
    },
    style: Object
  },
  setup(ctx) {
    const iconName = `#${ctx.prefix}${ctx.name}`
    return {
      iconName
    }
  }
}
</script>

// 导出 Icon 组件 并全局注册: Icon/index.js
import Icon from './Icon.vue'

export default function(Vue) {
  Vue.component(Icon.name, Icon)
}

// 入口文件use
import Icon from './components/Icon/index'
export default function(Vue) {
  Vue.use(Icon)
}

// 后续导入svg 库,便可以使用
<Icon name="xxx" :style="{}"></Icon>

对于 svg 库,可自行在Iconfont 创建(建议选择symbol方式),生成 js 文件,然后全局引入;或者复制 svg 内容粘贴到本地 defs 标签内然后引用。

三 svg 动画

1.transform 变换

与 css 的 transform 类似, 结合 keyframes 与 animation 实现动画:

transform 属性说明
translate平移
rotate旋转
skewX / skewY斜切
scale缩放
matrix复杂变形(矩阵)

matrix 可以实现其他四种的所有功能,matrix 的参数分为两组,六个数值,其中一三五一组,二四六一组,其坐标换算如:

<svg width="500" height="200" viewBox="0 0 500 200">
  <rect x="0" y="0" width="100" height="50" transform="matrix(2,1,-1,2,50,0)">
</svg>

第一组数值为 firstArr = [2, -1, 50]; 第二组数值为 secondArr = [1,2,0]。

上述代码为一个矩形,其四个顶点坐标为:[0,0], [100,0], [100,50], [0,50]。

matrix 根据旧坐标换算出新坐标,换算公式为:

newX = firstArr[0] * oldX + firstArr[1] * oldY + firstArr[2]
newY = secondArr[0] * oldX + secondArr[1] * oldY + secondArr[2]

得到的结果为:[50,0], [250,100], [200, 200], [0, 100],根据新的坐标画出新图形。

对于其他图形也是如此,找四个点的坐标进行换算,也可以通过新旧坐标反算出 matrix。如:

svg_matrix1.png

得出 a, b, c, d, e, f 的结果为:matrix(0,-1,1,0,0,440)

示例:

<div>
  <svg width="220" height="220" viewbox="0 0 220 220">
    <circle cx="110" cy="110" r="85" stroke-width="25" stroke="#D1D3D7" fill="none"></circle>
    <circle
      class="circle"
      cx="110"
      cy="110"
      r="85"
      stroke-width="25"
      stroke="#00A5E0"
      fill="none"
      transform="matrix(0,-1,1,0,0,220)"
    />
  </svg>
</div>

<style>
  .circle {
    animation: circle 5s linear infinite;
  }

  @keyframes circle {
    from {
      stroke-dasharray: 0 1069;
    }
    to {
      stroke-dasharray: 1069 0;
    }
  }
</style>

注:stroke-dasharray, stroke-dashoffset

2. SMIL

SMIL 允许我们通过 HTML 标签实现动画效果,包含以下标签:

SMIL 标签说明
set设置一个属性值指定时间
animate随时间动态改变属性
animateColor随着时间的推移颜色转换(已废弃,可通过animate实现)
animateTransform动画上一个目标元素变换属性,从而使动画控制平移,缩放,旋转或倾斜放
animateMotion使元素沿着动作路径移动

标签内的常用属性:

属性说明
attributeName要变化的元素属性名称,可以是 SVG 标签上的属性,如width/height,也可以是CSS属性,如opacity
attributeType三个固定参数 CSS / XML / auto, 用来表明定义在 attributeName 上面的属性
from动画的起始值
to动画的结束值
by动画的相对变化值(优先级低于 to)
values用分号分隔的一个或多个值,可以看出是动画的多个关键值点(优先级高于from/to/by)
begin动画开始的时间, 具体时间值 ‘h’‘min’‘s’‘ms’这些,默认单位是’s’, 也可是相对时间如:xx.end/circle.click 等。
end动画结束的时间
dur动画执行的时长
calcMode, keyTimes, keySplines设置动画执行的快慢
repeatCount动画执行次数
repeatDur重复动画的总时间
fill动画间隙的填充方式。支持参数有:freezeremove. 其中remove是默认值,表示动画结束直接回到开始的地方。freeze“冻结”表示动画结束后像是被冻住了,元素保持了动画结束之后的状态
accumulate支持参数有:nonesum. 默认值是none。如果值是sum表示动画结束时候的位置作为下次动画的起始位置
additive动画是否附加。支持参数有:replacesum. 默认值是replace。如果值是sum表示动画的基础知识会附加到其他低优先级的动画上
restart动画是否可以重复执行。可设置的值为always/whenNotActive/never
min / max执行的最短和最长时间

SVG 提供一些js接口可以用于控制动画, 如 svg.pauseAnimations()(暂停),svg.unpauseAnimations()(重启)

示例如(以下代码可正常演示,本地 Markdown 环境好像不支持动画的演示):

<svg width="200" height="200">
  <rect x="0" y="0" width="100" height="100" fill="red">
    <set attributeName="x" attributeType="XML" to="10" begin="1s" />
    <set attributeName="x" attributeType="XML" to="20" begin="2s" />
  </rect>
</svg>
<svg width="200" height="200" viewBox="0 0 20 20">
  <circle cx="0" cy="0" r="3" fill="blue" stroke="black" stroke-width="0.1">
    <animate attributeName="cx" from="0" to="200" dur="5s" repeatCount="indefinite" />
    <animate attributeName="cy" from="0" to="200" dur="5s" repeatCount="indefinite" />
  </circle>
</svg>
<svg width="200" height="200" viewBox="0 0 200 200">
  <rect x="0" y="0" width="60" height="60" fill="red">
    <animateTransform attributeName="transform" begin="0s" dur="3s" type="scale" from="1" to="2" repeatCount="indefinite" />
  </rect>
</svg>

animateMotion: 按 path 轨迹运动的正方形

<svg width="200" height="200" viewBox="0 0 200 200">
  <rect x="0" y="0" width="10" height="10" fill="red">
    <animateMotion
      path="M 10 10 L 110 10 L 110 110 L 10 110 Z"
      dur="5s"
      rotate="auto"
      fill="freeze"
      repeatCount="indefinite"
    />
  </rect>
  <path id="motion-path" d="M 10 10 L 110 10 L 110 110 L 10 110 Z" fill="none" stroke="green" />
</svg>

点击变色或位移:

<svg viewBox="0 0 200 200" width="200" height="200">
    <g id="rect1">
      <rect x="0" y="0" rx="0" ry="0" width="100" height="100" fill="red">
        <animate
          attributeType="XML"
          attributeName="fill"
          from="red"
          to="green"
          begin="rect1.click"
          dur="2s"
          fill="freeze"
        />
      </rect>
    </g>
    <animateTransform
      attributeType="XML"
      attributeName="transform"
      type="translate"
      from="0, 0"
      to="50, 50"
      begin="rect1.click"
      dur="2s"
      fill="freeze"
    />
    <rect x="0" y="100" width="100" height="100" fill="blue">
      <animate
        attributeType="XML"
        attributeName="fill"
        from="blue"
        to="green"
        begin="rect1.click"
        dur="2s"
        fill="freeze"
      />
    </rect>
</svg>

注:IE 不支持 SMIL。


参考:preserveAspectRatio W3school natsu-cc 数据可视化课程