前言
在《Figma Vector Networks: 重新定义矢量图形编辑》一文中,我们分析了Vector Networks (矢量网格)与SVG Path的根本差异。Vector Networks之所以被视为"更高维度"的表述,在于其独特的拓扑结构,这为矢量图形的编辑带来了前所未有的灵活性与效率。
本文将举例说明用Vector Networks表示下面图形的形状、填充及描边。

以上图形分为两类:左边是由直线组成的,右边是由曲线组成的,当然无论矢量图形有多复杂,都可以用Vector Networks表示。
概述
文章以具体图形为例,详细解析了 Vector Networks 的组成结构------包括节点(vertices) 、线条(segments) 和区域(regions) ,并展示了如何通过 JSON 结构描述直线、矩形、三角形、五角星、曲线和椭圆等常见图形。
同时,文章还区分了底层矢量引擎与上层工具开发者的职责,强调了 Vector Networks 在简化矢量图形编辑复杂度方面的重要作用。
化繁为简
Vector Networks主要由点、线、和区域组成。
对底层矢量引擎开发者来说,需要将Vector Networks这种描述协议转换成GPU可识别的渲染命令,而解析的核心功能是根据节点和线条自动识别区域。
对上层矢量工具开发者来说,不需要关心如何解析Vector Networks,只要关注矢量图形的编辑交互功能。
这样划分的好处是:底层矢量引擎将任意矢量图形抽象成Vector Networks,并自动识别出了矢量网格中的区域,暴露出矢量网格的点线编辑,大大简化了矢量图形编辑的复杂度。
公共部分
- type 是图形类型
- strokeWeight 描边线宽
- strokeAlign 描边对齐方式,分为:Center、Inside、Outside
- strokePaints 主要是描边颜色数组
- fillPaints 主要是填充颜色数组
- vectorNetwork存储的是矢量网格数据,包含节点数组、线条数组、区域数组
-
- vertices是节点数组,每个节点有自己的坐标和样式索引styleID (默认对应到strokePaints数组下标)
- segments是线条数组,每个线条有起始节点的索引vertex,和对应的控制点dx/dy(曲线会用到),结束点的索引vertex,和对应的控制点dx/dy,以及样式索引styleID
- regions是区域数组,包含多个区域。
-
-
- 每个区域的styleID (填充样式索引) 定义了当前区域的填充,默认对应到fillPaints数组下标,也可以对应到styleOverrideTable样式覆盖表里
- 每个区域的loops (区域) 定义了当前区域的拓扑结构,由多个segments (环) 组成,如果只有一个segments则代表当前区域由一个环围城,多个segments一般代表由内环和外环共同围城的区域
-
直线
javascript
const line = {
"type": "VECTOR", // 图形类型是矢量网格
"strokeWeight": 1, // 描边宽度为1个单位
"strokeAlign": "CENTER", // 描边对齐方式为居中
"strokeJoin": "MITER", // 描边连接方式为尖角连接
"strokePaints": [
{
"type": "SOLID",
"color": { // 描边颜色
"r": 0,
"g": 0,
"b": 0,
"a": 1
},
"opacity": 1,
"visible": true,
"blendMode": "NORMAL"
}
],
"vectorNetwork": { // 矢量网络数据
"vertices": [ // 顶点数组
{
"styleID": 0, // 顶点样式ID,对应描边样式数组的第一个样式
"x": 0,
"y": 1
},
{
"styleID": 0,
"x": 100,
"y": 0
}
],
"segments": [ // 线条数组
{
"styleID": 0, // 线条样式ID,对应描边样式数组的第一个样式
"start": {
"vertex": 0,
"dx": 0,
"dy": 0
},
"end": {
"vertex": 1,
"dx": 0,
"dy": 0
}
}
],
"regions": [] // 区域数组为空,表示没有可填充区域
}
};
export default line;
矩形
javascript
const rectangle = {
"type": "ROUNDED_RECTANGLE",
"strokeWeight": 1,
"strokeAlign": "INSIDE",
"strokeJoin": "MITER",
"fillPaints": [
{
"type": "SOLID",
"color": { // 填充颜色
"r": 0.8509804010391235,
"g": 0.8509804010391235,
"b": 0.8509804010391235,
"a": 1
},
"opacity": 1,
"visible": true,
"blendMode": "NORMAL"
}
],
"strokePaints": [
{
"type": "SOLID",
"color": { // 描边颜色
"r": 0,
"g": 0,
"b": 0,
"a": 1
},
"opacity": 1,
"visible": true,
"blendMode": "NORMAL"
}
],
"vectorNetwork": {
"vertices": [
{
"styleID": 0,
"x": 0,
"y": 0
},
{
"styleID": 0,
"x": 100,
"y": 0
},
{
"styleID": 0,
"x": 100,
"y": 50
},
{
"styleID": 0,
"x": 0,
"y": 50
}
],
"segments": [
{
"styleID": 0,
"start": {
"vertex": 0,
"dx": 0,
"dy": 0
},
"end": {
"vertex": 1,
"dx": 0,
"dy": 0
}
},
{
"styleID": 0,
"start": {
"vertex": 1,
"dx": 0,
"dy": 0
},
"end": {
"vertex": 2,
"dx": 0,
"dy": 0
}
},
{
"styleID": 0,
"start": {
"vertex": 2,
"dx": 0,
"dy": 0
},
"end": {
"vertex": 3,
"dx": 0,
"dy": 0
}
},
{
"styleID": 0,
"start": {
"vertex": 3,
"dx": 0,
"dy": 0
},
"end": {
"vertex": 0,
"dx": 0,
"dy": 0
}
}
],
"regions": [ // 区域数组里有一个区域
{
"styleID": 0,
"windingRule": "NONZERO",
"loops": [ // 当前区域由一个环围城
{
"segments": [ // 当前环由边 0/1/2/3 四条边围城
0,
1,
2,
3
]
}
]
}
]
}
};
export default rectangle;
三角形
javascript
const polygon = {
"type": "REGULAR_POLYGON",
"strokeWeight": 1,
"strokeAlign": "INSIDE",
"strokeJoin": "MITER",
"fillPaints": [
{
"type": "SOLID",
"color": {
"r": 0.8509804010391235,
"g": 0.8509804010391235,
"b": 0.8509804010391235,
"a": 1
},
"opacity": 1,
"visible": true,
"blendMode": "NORMAL"
}
],
"strokePaints": [
{
"type": "SOLID",
"color": {
"r": 0,
"g": 0,
"b": 0,
"a": 1
},
"opacity": 1,
"visible": true,
"blendMode": "NORMAL"
}
],
"vectorNetwork": {
"vertices": [
{
"styleID": 0,
"x": 50,
"y": 0
},
{
"styleID": 0,
"x": 93.30126953125,
"y": 75
},
{
"styleID": 0,
"x": 6.69873046875,
"y": 75
}
],
"segments": [
{
"styleID": 0,
"start": {
"vertex": 0,
"dx": 0,
"dy": 0
},
"end": {
"vertex": 1,
"dx": 0,
"dy": 0
}
},
{
"styleID": 0,
"start": {
"vertex": 1,
"dx": 0,
"dy": 0
},
"end": {
"vertex": 2,
"dx": 0,
"dy": 0
}
},
{
"styleID": 0,
"start": {
"vertex": 2,
"dx": 0,
"dy": 0
},
"end": {
"vertex": 0,
"dx": 0,
"dy": 0
}
}
],
"regions": [
{
"styleID": 0,
"windingRule": "NONZERO",
"loops": [
{
"segments": [ // 当前环由三个边围成
0,
1,
2
]
}
]
}
]
}
};
export default polygon;
五角星
javascript
const star = {
"type": "STAR",
"strokeWeight": 1,
"strokeAlign": "INSIDE",
"strokeJoin": "MITER",
"fillPaints": [
{
"type": "SOLID",
"color": {
"r": 0.8509804010391235,
"g": 0.8509804010391235,
"b": 0.8509804010391235,
"a": 1
},
"opacity": 1,
"visible": true,
"blendMode": "NORMAL"
}
],
"strokePaints": [
{
"type": "SOLID",
"color": {
"r": 0,
"g": 0,
"b": 0,
"a": 1
},
"opacity": 1,
"visible": true,
"blendMode": "NORMAL"
}
],
"vectorNetwork": {
"vertices": [
{
"styleID": 0,
"x": 50,
"y": 0
},
{
"styleID": 0,
"x": 61.22570037841797,
"y": 34.54914855957031
},
{
"styleID": 0,
"x": 97.55282592773438,
"y": 34.54914855957031
},
{
"styleID": 0,
"x": 68.16356658935547,
"y": 55.90169906616211
},
{
"styleID": 0,
"x": 79.38926696777344,
"y": 90.45085144042969
},
{
"styleID": 0,
"x": 50,
"y": 69.09829711914062
},
{
"styleID": 0,
"x": 20.610736846923828,
"y": 90.45085144042969
},
{
"styleID": 0,
"x": 31.836435317993164,
"y": 55.90169906616211
},
{
"styleID": 0,
"x": 2.447174072265625,
"y": 34.54914855957031
},
{
"styleID": 0,
"x": 38.77429962158203,
"y": 34.54914855957031
}
],
"segments": [
{
"styleID": 0,
"start": {
"vertex": 0,
"dx": 0,
"dy": 0
},
"end": {
"vertex": 1,
"dx": 0,
"dy": 0
}
},
{
"styleID": 0,
"start": {
"vertex": 1,
"dx": 0,
"dy": 0
},
"end": {
"vertex": 2,
"dx": 0,
"dy": 0
}
},
{
"styleID": 0,
"start": {
"vertex": 2,
"dx": 0,
"dy": 0
},
"end": {
"vertex": 3,
"dx": 0,
"dy": 0
}
},
{
"styleID": 0,
"start": {
"vertex": 3,
"dx": 0,
"dy": 0
},
"end": {
"vertex": 4,
"dx": 0,
"dy": 0
}
},
{
"styleID": 0,
"start": {
"vertex": 4,
"dx": 0,
"dy": 0
},
"end": {
"vertex": 5,
"dx": 0,
"dy": 0
}
},
{
"styleID": 0,
"start": {
"vertex": 5,
"dx": 0,
"dy": 0
},
"end": {
"vertex": 6,
"dx": 0,
"dy": 0
}
},
{
"styleID": 0,
"start": {
"vertex": 6,
"dx": 0,
"dy": 0
},
"end": {
"vertex": 7,
"dx": 0,
"dy": 0
}
},
{
"styleID": 0,
"start": {
"vertex": 7,
"dx": 0,
"dy": 0
},
"end": {
"vertex": 8,
"dx": 0,
"dy": 0
}
},
{
"styleID": 0,
"start": {
"vertex": 8,
"dx": 0,
"dy": 0
},
"end": {
"vertex": 9,
"dx": 0,
"dy": 0
}
},
{
"styleID": 0,
"start": {
"vertex": 9,
"dx": 0,
"dy": 0
},
"end": {
"vertex": 0,
"dx": 0,
"dy": 0
}
}
],
"regions": [
{
"styleID": 0,
"windingRule": "NONZERO",
"loops": [
{
"segments": [ 当前环由十条边围城
0,
1,
2,
3,
4,
5,
6,
7,
8,
9
]
}
]
}
]
}
};
export default star;
曲线
javascript
const line2 = {
"type": "VECTOR",
"strokeWeight": 1,
"strokeAlign": "CENTER",
"strokeJoin": "MITER",
"strokePaints": [
{
"type": "SOLID",
"color": {
"r": 0,
"g": 0,
"b": 0,
"a": 1
},
"opacity": 1,
"visible": true,
"blendMode": "NORMAL"
}
],
"vectorNetwork": {
"vertices": [
{
"styleID": 0,
"x": 0,
"y": 23.836986541748047
},
{
"styleID": 0,
"x": 100,
"y": 22.8122615814209
},
{
"styleID": 1,
"x": 50,
"y": 23.324623107910156
}
],
"segments": [
{
"styleID": 0,
"start": {
"vertex": 0,
"dx": 19.28675651550293, // 起始点的控制点x坐标(相对于当前点偏移量)
"dy": -29.784029006958008 // 起始点的控制点y坐标(相对于当前点偏移量)
},
"end": {
"vertex": 2,
"dx": -22.682817459106445, // 结束点的控制点x坐标(相对于当前点偏移量)
"dy": -33.02580642700195 // 结束点的控制点y坐标(相对于当前点偏移量)
}
},
{
"styleID": 0,
"start": {
"vertex": 2,
"dx": 22.682817459106445,
"dy": 33.02580642700195
},
"end": {
"vertex": 1,
"dx": -14.171252250671387,
"dy": 28.461177825927734
}
}
],
"regions": []
}
};
export default line2;
椭圆
javascript
const ellipse = {
"type": "ELLIPSE",
"strokeWeight": 1,
"strokeAlign": "INSIDE",
"strokeJoin": "MITER",
"fillPaints": [
{
"type": "SOLID",
"color": {
"r": 0.8509804010391235,
"g": 0.8509804010391235,
"b": 0.8509804010391235,
"a": 1
},
"opacity": 1,
"visible": true,
"blendMode": "NORMAL"
}
],
"strokePaints": [
{
"type": "SOLID",
"color": {
"r": 0,
"g": 0,
"b": 0,
"a": 1
},
"opacity": 1,
"visible": true,
"blendMode": "NORMAL"
}
],
"vectorNetwork": {
"vertices": [
{
"styleID": 0,
"x": 100,
"y": 25
},
{
"styleID": 0,
"x": 50,
"y": 50
},
{
"styleID": 0,
"x": 0,
"y": 25
},
{
"styleID": 0,
"x": 50,
"y": 0
}
],
"segments": [
{
"styleID": 0,
"start": {
"vertex": 0,
"dx": 0,
"dy": 13.80711841583252
},
"end": {
"vertex": 1,
"dx": 27.61423683166504,
"dy": 0
}
},
{
"styleID": 0,
"start": {
"vertex": 1,
"dx": -27.61423683166504,
"dy": 0
},
"end": {
"vertex": 2,
"dx": 0,
"dy": 13.80711841583252
}
},
{
"styleID": 0,
"start": {
"vertex": 2,
"dx": 0,
"dy": -13.80711841583252
},
"end": {
"vertex": 3,
"dx": -27.61423683166504,
"dy": 0
}
},
{
"styleID": 0,
"start": {
"vertex": 3,
"dx": 27.61423683166504,
"dy": 0
},
"end": {
"vertex": 0,
"dx": 0,
"dy": -13.80711841583252
}
}
],
"regions": [
{
"styleID": 0,
"windingRule": "NONZERO",
"loops": [
{
"segments": [ // 当前环由四条曲线组成
0,
1,
2,
3
]
}
]
}
]
}
};
export default ellipse;
总结
Figma Vector Networks 提供了一种更高维度的矢量图形描述方式 ,它不仅突破了传统 SVG Path 的编辑限制,还通过其独特的拓扑结构 实现了更灵活、高效的图形构建与渲染。本文通过多个实际图形示例,系统演示了如何使用 Vector Networks 的结构化数据(如 vertices、segments、regions)来定义图形的几何形状、描边样式与填充区域。无论是直线、多边形还是曲线图形,Vector Networks 都能以统一而强大的方式予以支持,极大地降低了矢量图形编辑的复杂度,为图形工具的开发与使用提供了新的可能性。
更多精彩内容可关注 风起的博客 ,微信公众号:听风说图