Figma VectorNetwork 정리
마지막으로 Figma의 VectorNetwork를 정리해봅시다.
여기까지 우리는 좌표계, 행렬, viewport, hit testing, selection, gradient, Figma to CSS 변환까지 왔습니다. 이제 남은 큰 산은 vector입니다. 왜냐하면 vector는 “div로 바꾸면 되죠?”가 거의 통하지 않는 영역이기 때문입니다.
Figma의 vector는 단순한 SVG path 문자열 하나가 아닙니다. 공식 문서 기준으로 VectorNetwork는 더 고급 geometry 표현입니다. SVG path가 보통 “한 줄로 이어지는 path command”라면, Figma vector network는 점과 선분으로 이루어진 그래프에 가깝습니다.
VectorNetwork =
vertices + segments + regions
자, 이름이 갑자기 진지해졌죠. 하지만 겁먹을 필요는 없습니다. 결국 점, 선, 면입니다. 미술 시간으로 돌아왔습니다. 단지 선생님이 행렬을 들고 들어오셨을 뿐입니다.
데모에서는 흰색 vertex를 직접 드래그할 수 있습니다. 곡선 segment가 있는 예제에서는 청록색 handle도 움직일 수 있습니다. 검은 segment를 드래그하면 양 끝 vertex가 같이 움직이고, 노란 region을 드래그하면 그 region에 포함된 vertex들이 함께 움직입니다. vertex를 움직이면 연결된 segment와 region fill이 같이 따라오고, handle을 움직이면 tangentStart 또는 tangentEnd가 바뀝니다. 그러니까 이 데모는 단순 그림이 아니라 작은 vector network 편집기입니다. 아주 작지만 의젓합니다.
VectorPath와 VectorNetwork의 차이
Figma에는 vector를 다루는 API가 크게 두 계층으로 보입니다.
VectorPath
- SVG path에 가까운 단순 표현
- data: "M ... L ... C ... Z"
- windingRule
VectorNetwork
- vertex / segment / region 그래프 표현
- branch, shared vertex, 여러 loop를 더 자연스럽게 표현
- 더 강력하지만 더 틀리기 쉬움
Figma 공식 문서도 일반적인 geometry 변경은 VectorPath가 더 간단하고 권장되는 방식이라고 말합니다. VectorNetwork는 더 강력하지만 복잡합니다. 이 말은 꽤 중요합니다.
export만 한다 -> vectorPaths 또는 SVG path로 충분한 경우가 많다
편집기를 만든다 -> VectorNetwork 같은 graph 모델을 이해해야 한다
우리는 CSS 기반 Figma 같은 툴을 만들고 싶으니까, export뿐 아니라 편집 모델도 봐야 합니다.
1. vertices: 점 목록
vertices는 그래프의 점 목록입니다.
vertices: [
{ x: 0, y: 100 },
{ x: 100, y: 100 },
{ x: 50, y: 0 }
]
각 vertex의 x, y는 node 내부 좌표입니다. 즉 local coordinate입니다.
vertex local point = (x, y)
world point = nodeWorldMatrix * vertexLocalPoint
screen point = viewportMatrix * worldPoint
우리가 앞에서 지겹게 봤던 local/world/screen 변환이 여기서 다시 나옵니다. 벡터 점도 결국 점입니다. 점은 어디 안 갑니다. 계속 우리 옆에 앉아 있습니다.
vertex에는 단순 위치 말고도 point별 stroke cap, stroke join, corner radius, handle mirroring 같은 정보가 붙을 수 있습니다.
VectorVertex
x, y
strokeCap?
strokeJoin?
cornerRadius?
handleMirroring?
이건 Figma vector editor가 point 단위로 cap, join, corner를 다룰 수 있다는 뜻입니다. CSS box 하나로 옮기기 어렵겠죠. 이쯤 되면 SVG 쪽으로 손이 갑니다.
2. segments: vertex index를 잇는 edge
segments는 두 vertex를 연결합니다.
segments: [
{ start: 0, end: 1 },
{ start: 1, end: 2 },
{ start: 2, end: 0 }
]
여기서 start, end는 좌표가 아니라 vertices 배열의 index입니다.
segment.start = vertices[startIndex]
segment.end = vertices[endIndex]
그래서 segment를 화면에 그리려면 먼저 index를 실제 점으로 풀어야 합니다.
const a = vertices[segment.start];
const b = vertices[segment.end];
line segment는 SVG로 이렇게 됩니다.
M a.x a.y L b.x b.y
하지만 segment에는 tangent가 있을 수 있습니다.
{
start: 0,
end: 1,
tangentStart: { x: 60, y: -90 },
tangentEnd: { x: -70, y: 80 }
}
이 경우 segment는 cubic bezier입니다.
P0 = vertices[start]
P3 = vertices[end]
P1 = P0 + tangentStart
P2 = P3 + tangentEnd
SVG:
M P0.x P0.y C P1.x P1.y, P2.x P2.y, P3.x P3.y
즉 Figma의 tangent는 bezier control point를 직접 저장한다기보다, vertex에서 뻗어 나가는 handle vector로 읽으면 편합니다.
3. path와 network의 결정적 차이: branch
SVG path는 기본적으로 command가 순서대로 이어지는 chain입니다.
M A L B L C L D
반면 VectorNetwork는 그래프입니다.
C
|
A ---- B ---- D
|
E
한 vertex에 segment가 셋 이상 붙을 수 있습니다. 이게 Figma vector network의 핵심입니다.
degree(vertex) = connected segment count
path chain:
degree <= 2
network branch:
degree can be 3, 4, ...
이 구조는 편집기에서 자연스럽습니다. 사용자는 선 하나를 그리고, 중간 점에서 다른 선을 뻗고, 또 연결하고 싶어 합니다. 전통적인 path 모델은 이런 branch를 여러 path로 쪼개야 하지만, network는 하나의 그래프로 들고 있을 수 있습니다.
그래서 CSS 변환 전략은 이렇게 나뉩니다.
closed simple loop -> SVG path
open stroke chain -> SVG path with fill="none"
branching network -> 여러 SVG path로 분해하거나 SVG group으로 export
editable network -> 자체 graph model 보존
4. regions: 어떤 loop가 fill인가
regions는 채워질 면을 정의합니다.
regions: [
{
windingRule: "NONZERO",
loops: [[0, 1, 2]]
}
]
여기서 loops 안의 숫자는 segment index입니다. vertex index가 아닙니다.
loop = [segmentIndex0, segmentIndex1, segmentIndex2]
Figma 문서 기준으로 region loop는 연결된 연속 chain이어야 합니다. gap이나 fork가 있으면 안 됩니다. 즉 fill 영역은 “펜을 떼지 않고 따라갈 수 있는 폐곡선”이어야 합니다.
구멍이 있는 도형은 loop가 두 개일 수 있습니다.
region loops:
outer loop
inner loop
SVG에서는 보통 하나의 path 안에 subpath를 여러 개 넣고 fill-rule을 지정합니다.
<path
d="M outer... Z M inner... Z"
fill-rule="evenodd"
/>
Figma의 windingRule은 SVG의 fill-rule과 연결됩니다.
Figma NONZERO -> SVG fill-rule="nonzero"
Figma EVENODD -> SVG fill-rule="evenodd"
NONE -> fill 없음, open path처럼 처리
여기서 winding rule은 point-in-path 판정에도 영향을 줍니다. hit testing에서 “이 점이 도형 안인가?”를 계산할 때도 같은 개념을 씁니다.
5. VectorNetwork를 SVG path로 내보내는 과정
단순한 closed region이라면 변환은 이런 흐름입니다.
1. region.loop에서 segment index 목록을 읽는다.
2. 각 segment의 start/end vertex를 찾는다.
3. loop 순서대로 연결 방향을 맞춘다.
4. 첫 점은 M으로 쓴다.
5. line segment는 L로 쓴다.
6. tangent가 있으면 C로 쓴다.
7. loop가 닫히면 Z를 붙인다.
8. windingRule을 fill-rule로 옮긴다.
주의할 점이 있습니다. VectorNetwork의 segment는 그래프 edge라서, loop를 따라갈 때 segment의 start -> end 방향이 현재 진행 방향과 다를 수 있습니다.
현재 점 == segment.start -> start to end
현재 점 == segment.end -> end to start
뒤집어서 따라가야 할 때 cubic bezier control point도 같이 뒤집어야 합니다.
original:
P0 = start
P1 = start + tangentStart
P2 = end + tangentEnd
P3 = end
reversed:
P0' = end
P1' = end + tangentEnd
P2' = start + tangentStart
P3' = start
이 부분을 대충 하면 곡선이 “어? 나 이런 애 아닌데요?” 하고 다른 모양이 됩니다.
6. CSS로 직접 바꿀 수 있나?
대부분의 VectorNetwork는 CSS box로 직접 바꾸지 않는 편이 좋습니다.
simple rectangle -> CSS div 가능
circle/ellipse -> CSS border-radius 가능
polygon -> clip-path polygon 가능
bezier path -> SVG path 추천
branching network -> SVG group 또는 자체 vector model
boolean/vector mask -> SVG 또는 asset fallback
CSS clip-path: path(...) 같은 선택지도 있지만, 브라우저 지원과 편집 가능성까지 생각하면 강의의 기본 전략은 이렇게 잡는 게 좋습니다.
Figma VectorNetwork
-> internal graph model
-> preview/edit: SVG overlay or canvas renderer
-> export: SVG path/group or raster asset
CSS는 layout과 box paint에 강합니다. VectorNetwork는 geometry graph입니다. 전공이 다릅니다. 둘을 억지로 결혼시키면 식장에서부터 삐걱댑니다.
7. 편집기를 만든다면 어떤 모델을 저장할까?
CSS 기반 그래픽 툴을 만든다고 해도, vector 자체는 DOM element의 style만으로 저장하면 안 됩니다.
좋은 중간 모델은 이런 식입니다.
type EditorVectorNode = {
id: string;
transform: Matrix2D;
vertices: Vertex[];
segments: Segment[];
regions: Region[];
fills: Paint[];
strokes: Paint[];
};
렌더링은 그때그때 선택합니다.
editing overlay -> SVG circles/paths/handles
preview -> SVG path or canvas path
CSS export -> 가능한 경우만 CSS primitive
SVG export -> 대부분의 vector에 대한 기본 fallback
hit testing도 비슷합니다.
stroke hit:
distance(point, segment) <= strokeWidth / 2
fill hit:
point-in-region with windingRule
handle hit:
distance(point, vertex or control handle) <= threshold
도구 동작도 결국 같은 모델 위에 올라갑니다.
drag vertex:
vertex.x/y를 직접 수정
drag handle:
tangentStart 또는 tangentEnd를 수정
drag segment:
segment.start와 segment.end vertex를 같은 delta만큼 이동
drag region:
region.loops에 들어 있는 segment들의 vertex를 모아서 같은 delta만큼 이동
실제 Figma 수준으로 가면 segment를 구부리기, edge 위에 새 vertex 삽입하기, delete and heal, corner radius 보존, branch 유지 같은 처리가 더 들어갑니다. 하지만 기본 구조는 여기서 시작합니다. “어떤 UI를 잡았는가?”를 “어떤 vertex/tangent/region 데이터를 바꿀 것인가?”로 바꾸는 일입니다.
결국 우리가 앞에서 배운 hit testing, inverse matrix, bezier tangent, fill rule이 한꺼번에 돌아옵니다. 마지막 강의답게 복습 시험을 들고 왔네요. 교수님도 채점하기 싫지만 해야 합니다.
마무리
Figma의 VectorNetwork는 “SVG path보다 어려운 데이터”가 아니라, Figma가 vector를 편집하기 좋게 저장하는 그래프 기반 geometry 모델입니다.
오늘의 핵심은 이겁니다.
vertices = 점
segments = 점을 잇는 edge, line 또는 cubic bezier
regions = 어떤 segment loop를 fill할지 정하는 면
windingRule = 안/밖 판정 규칙
Figma to CSS 변환의 마지막 결론도 여기서 깔끔해집니다.
CSS로 가능한 건 CSS로 간다.
SVG가 자연스러운 건 SVG로 간다.
편집 가능한 vector는 graph model을 보존한다.
이렇게 하면 CSS 기반 에디터를 만들면서도 Figma의 vector 모델을 억지로 납작하게 만들지 않을 수 있습니다. 여기까지 오면 이제 꽤 그럴듯한 그래픽 툴의 뼈대가 생겼습니다. 칠판 지워도 됩니다. 다만 행렬은 마음속에 남겨둡시다.