SVG 的使用

20180729

SVG 图标是矢量图,可以很方便的转换颜色,修改文字,画质无损进行缩放,而且文件非常小,相比同样的 png 图片,大小仅为一成
SVG 通过可配置颜色和配置文字的方式,有效减少了图标文件的个数。从而使设计师和前端工程师工作量都减少,还能提升了加载速度

介绍

说到性能优化,常见的一种方式就是压缩图片,通常图片最多压缩到原文件的 50%。而且如果一个图标有 7 种颜色呢?那么设计就得给出 7 种颜色的图片,前端代码也得引用不同的 7 个路径。。。想想都觉得复杂。

SVG 图完美地解决了这一痛点,大小仅为原文件 10% 左右。还可以通过改写代码来任意改变颜色,甚至支持在代码中动态地传入颜色。动态地传入颜色时,即便有 1万种颜色,也仅仅一个文件。

而生成 SVG 文件也非常简单,设计师可以使用 矢量图 绘图软件直接导出 svg 格式的文件,如 Adobe Illustrator(简称 AI)、sketch

当然对于 Adobe Photoshop 中绘制的图片,手工转换矢量图比较困难耗时,在线工具解决了这一难题

其中目前免费在线转换工具里,这款是最好的 https://www.vectorizer.io/

转换后,可能会有一些冗余的代码,可以使用命令行工具 svgo 进行批量压缩。当然如果文件数量不多,直接使用在线工具 https://jakearchibald.github.io/svgomg/ 即可。

这些个 SVG 压缩工具只是静态工具,不会被上传到网络上去,不需要担心被盗图,有兴趣的同学可以研究下源码,以及那个web 压缩工具的源码

SVG 图标示例

首先 svg 在浏览器里和手机上,与图片一样的,可以正常显示出来
下面这个就是一个完整的 SVG 图,包括文字

20180729-buttonFollow

下面将它改写为为 400px 宽度的 SVG 图,仍然可以看到很清晰

20180729-buttonFollow-big

接下来,使用微信屏幕截图,看看同样 400px 宽度的 png 图,并且使用智图 压缩图片

20180729-png

接下来看看这三个文件的大小

20180729-filesize

可以看出 SVG 图可以任意改变尺寸,不损失清晰度
相比 png 图体积小很多的情况下,仍然比 png 图更清晰

接下来使用代码编辑器打开 SVG 文件,可以看到如下代码

也许看到这一堆代码要头晕了。别担心,实际应用时,并不需要自己手写 SVG 代码,只是改改就足够了
仔细看很类似前端常用的 html 标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<svg width="60px" height="24px" viewBox="0 0 60 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 51 (57462) - http://www.bohemiancoding.com/sketch -->
<title>按钮/关注-红色底</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="按钮/关注-红色底" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Rectangle-3" fill="#FF4C6A" x="0" y="0" width="60" height="24" rx="12"></rect>
<g id="icon/关注加号" transform="translate(3.000000, 0.000000)">
<g id="Group" stroke-width="1" transform="translate(8.000000, 8.000000)" fill="#FFFFFF">
<path d="M3,3 L3,1 C3,0.44771525 3.44771525,1.01453063e-16 4,0 C4.55228475,-1.01453063e-16 5,0.44771525 5,1 L5,3 L7,3 C7.55228475,3 8,3.44771525 8,4 C8,4.55228475 7.55228475,5 7,5 L5,5 L5,7 C5,7.55228475 4.55228475,8 4,8 C3.44771525,8 3,7.55228475 3,7 L3,5 L1,5 C0.44771525,5 6.76353751e-17,4.55228475 0,4 C-6.76353751e-17,3.44771525 0.44771525,3 1,3 L3,3 Z" id="Combined-Shape"></path>
</g>
<rect id="Rectangle-10" fill="#D8D8D8" opacity="0" x="0" y="0" width="24" height="24"></rect>
</g>
<text id="哈哈" font-family="PingFangSC-Regular, PingFang SC" font-size="12" font-weight="normal" line-spacing="18" fill="#FFFFFF">
<tspan x="24" y="16">哈哈</tspan>
</text>
</g>
</svg>

SVG 代码说明

1
<svg width="60px" height="24px" viewBox="0 0 60 24" ... > ... </svg>

svg 标签来控制宽高,width,height 是实际显示的宽高,可以修改为你想要显示的大小
而 viewBox 里的大小,则是原始大小,可以理解为画纸的大小位置,其中左上角坐标为 0 0,右下角坐标为 60 24(方向分别为 x, y)

1
<g id="Group" stroke-width="1" transform="translate(8.000000, 8.000000)" fill="#FFFFFF"> ... </g>

g 标签表示分组,也就是绘图软件中的图层。类似代码中的继承,它的属性,如果子标签里没有规定,就会使用它的属性设置
例如下面这两段代码是同样的效果

1
2
3
<g fill="#FF4C6A">
<rect x="0" y="0" width="60" height="24" rx="12"></rect>
</g>
1
2
3
<g >
<rect fill="#FF4C6A" x="0" y="0" width="60" height="24" rx="12"></rect>
</g>

path 表示线条, reat 表示方形,text 表示文字
代码 d="M3,3 L3,1 ...' 是绘制线条的代码,也称作路径(path)

fill 表示填充色,类似于 css 里的背景色(background-color)

stroke 表示描边,类似于 css 的边框颜色(border-color)

所以修改色值时,只需要修改这两个颜色即可
色值与 css 相同,可以使用透明色 transparent,以及 rgba(0,0,0,0.5)
也可以添加属性 opacity='0.5' 来控制透明度,值为 0 ~ 1

至于修改文字,找到对应的文字,直接替换即可

动态渲染 SVG

由于最近正在做 react-native, SVG 的配置难度较大,就用它来示例一下

这里使用 react-native-svg 库来渲染 SVG 图片

注意设计师导出 SVG 图标前,请清除掉蒙层(mask)、颜色叠加和滤镜(filter)、阴影(shadow),目前 react-native 是不支持的

首先使用 msvgc 库来一键把 SVG 文件转换为 React 组件

nodejs 环境里安装 msvgc,源文件放置到 App/Svg/目录下,配置脚本运行即可
导出的组件位于 ./App/Components/Svg/svg 目录

1
2
3
"scripts": {
"svg": "msvgc --react-native -f ./App/Svg/ -o ./App/Components/Svg && standard --fix './App/Components/Svg/svg/*.js'"
}

再把修正属性的语法,全部改为驼峰写法,例如 fill-rule 改为 fillRule
修正后将文件移动到 App/Components/Svg(其他目录也昆虫,因为每次新转换时,会覆盖App/Components/Svg/svg/ 目录)

接下来,进一步修改代码,就可以通过组件的 props 值里动态传参了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import React from 'react'
import Svg, { G, Rect, Path, Text } from 'react-native-svg'
import { path } from 'ramda' // 根据键名取值,取不到或错误时,返回 undefined
// path([键名],被取值的对象)

const ButtonFollow = props => {
const Color = {
red: '#ff4c6a',
grey: '#6c6c6c'
}
const fillColor = path([props.color], Color) || props.color || Color.red
return (
<Svg
width={props.width || 60}
height={props.height || 24}
viewBox='0 0 60 24'>
<G stroke='none' strokeWidth='1' fill='none' fillRule='evenodd'>
<Rect fill={fillColor} x='0' y='0' width='60' height='24' rx='12' />
<G strokeWidth='1' transform='translate(8, 8)' fill='#FFFFFF'>
<Path d='M3,3 L3,1 C3,0.44771525 3.44771525,1.01453063e-16 4,0 C4.55228475,-1.01453063e-16 5,0.44771525 5,1 L5,3 L7,3 C7.55228475,3 8,3.44771525 8,4 C8,4.55228475 7.55228475,5 7,5 L5,5 L5,7 C5,7.55228475 4.55228475,8 4,8 C3.44771525,8 3,7.55228475 3,7 L3,5 L1,5 C0.44771525,5 6.76353751e-17,4.55228475 0,4 C-6.76353751e-17,3.44771525 0.44771525,3 1,3 L3,3 Z' />
</G>
<Text fontSize='12' lineSpacing='18' fill='#FFFFFF' x='24' y='16'>
{props.text || '关注'}
</Text>
</G>
</Svg>
)
}

export default ButtonFollow

调用

1
2
3
<ButtonFollow width={300} height={120} color='red' />
<ButtonFollow /> 取默认值 64,24,默认色 red
<ButtonFollow width={300} height={120} color='#ccc' /> 任意颜色

react-art

(基于 react-naive 0.55.4 介绍)

facebook 自家也出品了一个库 react-art,并且有 react-native-art,二者大体上使用的同一套 API
它支持的元素标签交少,例如没有方形。但更简洁,不需要引入额外库
(react-native-svg 压缩后大约 200KB)

相关语法可以看 react-native-art-绘图入门 了解一下
源码位于 node_modules/react-native/Libraries/ART/ReactNativeART.js
缺点是还没有找到现成的转换插件,需要手工转换

首先来封装一个方形组件,width、height 控制宽高,r 控制圆角半径(border-radius)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React from 'react'
import { ART } from 'react-native'

const { Shape } = ART

function extractNumber (value, defaultValue) {
if (value == null) {
return defaultValue
}
return +value
}

const RectART = props => {
let w = extractNumber(props.width, 0)
let h = extractNumber(props.height, 0)
let r = extractNumber(props.r, 0)

if (w === 0 || h === 0) return null

if (r > 0) {
h -= r * 2
w -= r * 2
return <Shape {...props}
d={`M${r},0 h${w} a${r},${r} 0 0,1 ${r},${r} v${h} a${r},${r} 0 0,1 ${-r},${r} h${-w} a${r},${r} 0 0,1 ${-r},${-r} v${-h} a${r},${r} 0 0,1 ${r},${-r} z`}
/>
} else {
return <Shape {...props} d={`h${w} v${h} h${-w} z`} />
}
}

export default RectART

接下来转换按钮

  1. 将 svg 换为 Surface,宽高为显示端宽高,不指定画布大小
  2. Surface 内层写上一层 Group ,添加属性 scale 用于整体缩放,缩放倍数 X 基于画布原始大小来计算
  3. g 转换为 Group, Group 里的属性只支持 fill 和 transform,其他的都写到子标签里去
  4. path 转换为 Shape,d 仍然是路径,复制过来即可(react-art 内置了一套 svg 转换器,源码位于路径 node_modules/art/core/path.js
  5. transform 转换为这种形式,注意 scale 要放在左边,与 css3 相同还支持 rotate

    1
    transform = new Transform().scale(2).translate(2, 3)
  6. text 转换为 Text,直接包裹文字即可,

其中 font 指定字体格式(注意必须指定字体)
(react-native 里指定多个字体只有第一个字体生效)

1
font='normal 12 PingFangSC-Regular'

也可以写为(注意必须指定字体)

1
font={fontFamily: 'PingFangSC-Regular',fontSize: '12'}

还有两个属性是 fontWeight、fontStyle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import React from 'react'
import { path } from 'ramda' // 根据键名取值,取不到或错误时,返回 undefined
// path([键名],被取值的对象)
import { ART, View } from 'react-native'
import Rect from './RectART.js'

const { Group, Shape, Surface, Transform, Text } = ART

const ButtonFollow = props => {
const Color = {
red: '#ff4c6a',
grey: '#6c6c6c'
}

const fillColor = path([props.color], Color) || props.color || Color.red
const X = (Math.min(props.width / 60, props.height / 24)) || 1

return (
<View style={props.style}>
<Surface width={props.width || 60} height={props.height || 24} >
<Group scale={X} >
<Rect fill={fillColor} r='12' width='60' height='24' />
<Shape fill='#FFFFFF' transform={new Transform().translate(8, 8)} d='M3,3 L3,1 C3,0.44771525 3.44771525,1.01453063e-16 4,0 C4.55228475,-1.01453063e-16 5,0.44771525 5,1 L5,3 L7,3 C7.55228475,3 8,3.44771525 8,4 C8,4.55228475 7.55228475,5 7,5 L5,5 L5,7 C5,7.55228475 4.55228475,8 4,8 C3.44771525,8 3,7.55228475 3,7 L3,5 L1,5 C0.44771525,5 6.76353751e-17,4.55228475 0,4 C-6.76353751e-17,3.44771525 0.44771525,3 1,3 L3,3 Z' />
<Text font={'normal 12 PingFangSC-Regular'} fill='#FFF' transform={new Transform().translate(24, 4)}>
{props.text || '关注'}
</Text>
</Group>
</Surface>
</View>
)
}

export default ButtonFollow
1
<ButtonFollow width={300} height={120} />

react-native-art API

(基于 react-naive 0.55.4 介绍)
下面是我看了源码之后罗列的 API,供参考使用

相关语法可以看 react-native-art-绘图入门 了解一下
源码位于 node_modules/react-native/Libraries/ART/ReactNativeART.js

Surface

Property Type Description
width string
heigh string -

Group

Property Type Description
opacity number
scale number
scaleX number
scaleY number
transform transform
visible boolean false equals opacity 0

Shape

Property Type Description
d path
fill string Color
opacity number
scale number
scaleX number
scaleY number
stroke string Color
strokeCap string butt, square, round(default)
strokeDash
strokeJoin string miter, bevel, round
strokeWidth number 1(default)
transform transform
visible boolean false equals opacity 0

Text

Property Type Description
alignment string right, center, left(default)
fill string Color
font string normal 10 PingFangSC-Regular
font object {fontFamily,fontSize,fontWeight,fontStyle}
opacity number
path path
scale number
scale number
scaleX number
scaleX number
scaleY number
scaleY number
strokeCap string butt, square, round(default)
strokeDash
strokeJoin string miter, bevel, round
strokeWidth number 1(default)
transform transform -

Transform

Property params Description
transform xx, yx, xy, yy, x, y
translate x, y
move x, y
moveTo x, y
scale x, y
scaleTo x, y
rotate deg, x, y
rotateTo deg, x, y
resizeTo width, height this.scaleTo(width / w, height / h)
point x, y
inversePoint x, y -

上面表格含 To 的都是绝对坐标,例如 move 是相对坐标,moveTo 是绝对坐标

LinearGradient
| params | type | Description |
| —— | —— | ————— |
| stop | object | |
| x0 | number | 非必填,默认270 |
| y0 | number | 非必填 |
| x1 | number | 非必填 |
| y1 | number | 非必填 |

例如:

1
2
3
4
5
6
7
var linearGradient = new LinearGradient({
'.1': 'blue', // blue in 1% position
'1': 'rgba(255, 255, 255, 0)' // opacity white in 100% position
},
"0", "0", "0", "400"
)
<Shape fill={linearGradient}>

其他存在但不常用的 API
Path
ClippingRectangle
LinearGradient
Pattern
RadialGradient

SVG path 说明

具体可以看 w3c 官方文档 https://www.w3.org/TR/SVG2/paths.html
大写字母都代表绝对坐标
小写字母都代表相对坐标

下面这几个写法效果相同
M 10 20 M 15 25 v 5
M 10,20 M 15,25 v 5
M10,20 M15,25 v5
M10,20 15,25 v5 (与前一个字母相同时,字母可以省略)

字母主要有以下几种,可以对应到上面的表格

Command Name Parameters Description
M moveto x, y 移动
L lineto x, y 画线
H horizontal lineto x 画水平线
V vertical lineto y 画垂直线
A elliptical arc 椭圆
Z closepath 连接到起始点,只能在最后使用

椭圆 A 有6个参数 rx ry x-axis-rotation large-arc-flag sweep-flag x y
含义分别为

Parameters Description
rx x 轴半径
ry y 轴半径
x-axis-rotation 相对于 x 轴的旋转角度,例如 30 表示 30 度
large-arc-flag 1 绘制大圆,0 绘制小圆
sweep-flag 旋转方式: 1 顺时针,0 逆时针
x 结束点的 x 坐标
y 结束点的 x 坐标

封面图片来源:https://www.pixiv.net/member_illust.php?mode=medium&illust_id=38190692

夏味 wechat
欢迎添加我个人微信,互相学习交流