CSS Graphics Geometry
Guide
Reference
GitHub
Guide
Reference
GitHub
  • Part 0. Math foundations

    • 좌표계와 CSS pixel
    • 점, 벡터, 거리와 기본 용어
    • 각도, 라디안, atan2
  • Part 1. Transform linear algebra

    • translate, scale, rotate
    • CSS matrix(a,b,c,d,e,f)
    • 변환 순서와 transform-origin
    • local, world, screen 좌표계
    • inverse matrix와 포인터 입력
    • 좌표와 transform 디버깅
  • Part 2. Infinite canvas math and architecture

    • viewport와 camera 모델
    • cursor anchored zoom
    • ruler와 tick 계산
    • 주기 함수와 grid step
    • content, overlay, controls layer
    • scene graph와 nested transform
  • Part 3. Editor tool math

    • Pointer Events와 드래그
    • DOMRect와 getBoundingClientRect
    • hit testing과 bounding box
    • selection rectangle과 marquee
    • selection bounds와 handles
    • resize와 rotation handle
    • snapping과 smart guides
    • group, frame, clipping
  • Part 4. CSS graphics properties as math

    • CSS box로 도형 만들기
    • clip-path, radius, shadow
    • 선형보간과 linear-gradient
    • 거리 함수와 radial/conic-gradient
    • stacking context와 합성
    • layout, paint, composite
  • Part 5. Editor state and persistence

    • 레이어 모델과 z-index
    • 이동, 복제, 삭제, 잠금
    • undo/redo command model
    • JSON export/import
  • Part 6. SVG overlay and vector editing

    • SVG viewBox와 좌표계
    • getScreenCTM과 inverse
    • SVG pointer-events와 stroke hit testing
    • cubic bezier path editing
    • mask, clipPath, marker
  • Part 7. Figma to CSS translation

    • Figma node와 CSS DOM 모델
    • Frame constraints와 Auto Layout
    • Fills, strokes, effects를 CSS로
    • Vector와 text 변환
    • gradient 밖의 CSS 수학
    • Figma gradient를 CSS gradient로 변환하기
    • 수학이 필요한 Figma to CSS 속성들
    • Figma VectorNetwork 정리
  • Appendix A. Canvas renderer transition

    • Canvas로 넘어가는 기준
  • Appendix B. Motion and timing

    • 모션 수학과 timing 함수
    • Keyframe timeline 엔진
    • Motion path 수학
    • Bezier curve 길이 구하기

수학이 필요한 Figma to CSS 속성들

gradient 하나만 봐도 이미 머리가 살짝 뜨끈해졌습니다. 그런데 Figma to CSS에서 수학이 필요한 속성은 gradient만 있는 게 아닙니다.

Figma는 기본적으로 scene graph + affine transform + vector paint model 위에 올라가 있습니다. CSS는 DOM tree + box model + layout engine + paint/composite pipeline 위에 올라가 있고요. 둘 다 화면에 사각형과 텍스트를 그리지만, 값을 저장하는 방식이 꽤 다릅니다.

그래서 변환기는 단순히 이름만 바꾸는 번역기가 아닙니다.

Figma property
-> 중간 수학 모델
-> CSS로 정확히 표현 가능한가?
-> 안 되면 SVG, asset, runtime fallback을 쓸 것인가?

이 판단을 해야 합니다. 안 그러면 export 결과가 처음에는 그럴듯한데, 프레임 크기를 바꾸거나 회전된 그룹 안에 넣는 순간 슬쩍 다른 얼굴이 됩니다. 자, 범인을 잡으러 갑시다.

먼저 전체 지도부터 보기

Figma 속성을 CSS로 옮길 때 수학이 필요한 대표 항목은 이 정도입니다.

Figma 쪽 값CSS 쪽 목표필요한 수학주의할 점
relativeTransform, absoluteTransformtransform: matrix(...), transform-origin: 0 0affine matrix, parent-child composition, inverse, pivot correctionFigma rotation은 top-left 기준, CSS 기본 origin은 center
constraintsleft, right, %, calc()parent resize equationFigma constraint와 CSS layout은 1:1 대응이 아님
Auto Layoutdisplay:flex, gap, padding, flex-growaccumulated size, hug/fill/fixed sizingFigma의 hug contents는 CSS intrinsic sizing과 비슷하지만 완전히 같지 않음
cornerRadius, rectangleCornerRadii, cornerSmoothingborder-radius, SVG pathradius normalization, superellipse approximationcorner smoothing은 CSS 기본 radius로 정확히 표현 불가
strokeWeight, strokeAlign, dashPatternborder, outline, box-shadow, SVG strokevisual bounds expansion, path strokingCSS border는 주로 box 안쪽 레이아웃에 영향을 줌
effectsbox-shadow, filter, mix-blend-modeblur spread, alpha compositing, bounds expansionshadow/blur는 geometry 밖으로 퍼짐
text metricsfont-*, line-height, letter-spacingbaseline, line box, glyph advance폰트 렌더링 엔진 차이로 픽셀 완전 일치가 어려움
image fillobject-fit, background-size, background-positioncover/contain scale, UV transformimageTransform이 있으면 배경 위치 계산이 필요함
vectorNetwork, vector, boolean, maskSVG, clip-path, assetgraph traversal, path transform, fill rule, boolean operation복잡한 vector는 CSS box로 억지 변환하지 않는 편이 좋음

이 표가 오늘의 지도입니다. “CSS로 갈 수 있나?”만 보면 안 되고, “어떤 좌표계의 값을 어떤 좌표계로 보내는가?”를 같이 봐야 합니다.

1. Transform: 행렬은 거의 항상 출발점입니다

Figma의 transform은 2x3 affine transform입니다.

T = [
  [a, c, e],
  [b, d, f]
]

점 하나를 변환하면 이렇게 됩니다.

x' = a * x + c * y + e
y' = b * x + d * y + f

CSS의 matrix(a, b, c, d, e, f)도 같은 식으로 읽을 수 있습니다.

transform: matrix(a, b, c, d, e, f);

Figma 값을 CSS로 옮길 때는 거의 항상 origin을 같이 써야 합니다.

.figma-node {
  position: absolute;
  width: 120px;
  height: 80px;
  transform-origin: 0 0;
  transform: matrix(a, b, c, d, e, f);
}

이유는 간단합니다.

Figma rotation 기준점 = local top-left
CSS 기본 transform-origin = center

Figma plugin API의 rotation은 top-left 기준입니다. CSS는 기본값이 50% 50%라서, 같은 matrix라도 origin 보정이 끼면 화면 위치가 달라질 수 있습니다. 그러니 Figma의 relativeTransform 또는 absoluteTransform을 CSS matrix로 직렬화할 때는 transform-origin: 0 0을 명시하는 편이 안전합니다.

단, 여기서 한 번 조심해야 합니다. Figma의 node는 parent frame 안에서 relativeTransform을 가지고, 화면 전체 기준으로는 absoluteTransform을 가집니다.

childWorld = parentWorld * childLocal
childLocal = inverse(parentWorld) * childWorld

이 공식은 editor 전체에서 계속 나옵니다. group 안의 child를 밖으로 빼거나, frame에서 group으로 옮기거나, 회전된 부모 안의 child를 드래그할 때 모두 같은 이야기입니다.

DOM으로 옮길 때도 마찬가지입니다.

Figma scene graph:
Frame
  Group
    Rect

CSS DOM:
div.frame
  div.group
    div.rect

이렇게 계층을 유지하면 local transform을 비교적 자연스럽게 쓸 수 있습니다. 반대로 모든 요소를 하나의 절대 좌표 평면에 펼치면 absoluteTransform을 CSS transform으로 만들어야 합니다. 둘 중 뭐가 더 좋냐고요? 상황마다 다릅니다. 변환기 입장에서는 둘 다 준비해야 마음이 덜 시끄럽습니다.

center 기준 회전은 translation 보정이다

그럼 Figma에서 center 기준으로 돌리고 싶으면 어떻게 할까요? CSS처럼 transform-origin: center 하나로 끝내는 게 아니라, pivot이 움직이지 않도록 matrix의 translation을 다시 계산합니다.

pivotLocal = (width / 2, height / 2)
pivotParent = oldMatrix * pivotLocal
newTranslation = pivotParent - newLinearPart * pivotLocal

좀 더 풀면 이렇습니다.

newE = pivotParent.x - (a * pivotLocal.x + c * pivotLocal.y)
newF = pivotParent.y - (b * pivotLocal.x + d * pivotLocal.y)

여기서 newLinearPart는 새 회전, 새 skew, 새 scale에 해당하는 matrix의 왼쪽 2x2 부분입니다.

newLinearPart = [
  [a, c],
  [b, d]
]

이 식의 의미는 하나입니다.

새 matrix를 적용해도 pivotLocal이 같은 parent 위치에 남아야 한다.

rotation handle, group resize, child reparenting이 모두 이 식의 친척입니다. 회전 중심, resize 반대편 corner, reparenting 전 world position은 이름만 다르고 전부 “고정해야 하는 점”입니다. 수학이 같은 집안입니다. 명절에 만나면 서로 알아봅니다.

2. Constraints: 반응형은 이름이 아니라 식입니다

Figma constraint는 부모 크기가 바뀔 때 child의 위치와 크기를 어떻게 다시 계산할지 말합니다.

왼쪽 고정이면 단순합니다.

left' = left
width' = width

오른쪽도 같이 고정이면 width가 바뀝니다.

right = parentWidth - left - width
left' = left
width' = parentWidth' - left - right

가운데 고정이면 중심점 비율이나 offset을 잡습니다.

centerOffset = childCenter - parentWidth / 2
childCenter' = parentWidth' / 2 + centerOffset
left' = childCenter' - width / 2

scale이면 비율입니다.

leftRatio = left / parentWidth
widthRatio = width / parentWidth

left' = parentWidth' * leftRatio
width' = parentWidth' * widthRatio

CSS로는 left, right, width, %, calc()를 섞어서 표현합니다.

.child {
  position: absolute;
  left: 24px;
  right: 32px;
}

여기서 중요한 건 “Figma의 constraint 이름을 CSS 속성 하나로 바꾼다”가 아닙니다. 부모가 바뀔 때의 재계산 식을 CSS가 대신 풀 수 있게 만들어야 합니다.

3. Auto Layout: flex로 가지만, 완전히 flex는 아닙니다

Figma Auto Layout은 CSS flex와 많이 닮았습니다.

layoutMode: HORIZONTAL
paddingLeft / paddingRight / paddingTop / paddingBottom
itemSpacing
primaryAxisSizingMode
counterAxisSizingMode

가로 Auto Layout의 child 위치는 대략 이렇게 누적됩니다.

childX(i) = paddingLeft
          + sum(width(0..i-1))
          + itemSpacing * i

CSS로는 이렇게 갑니다.

.frame {
  display: flex;
  flex-direction: row;
  gap: 12px;
  padding: 16px 20px;
}

하지만 sizing에서 슬쩍 어려워집니다.

Figma FIXED -> width: Npx
Figma HUG   -> width: fit-content 또는 auto
Figma FILL  -> flex: 1 1 0

Figma의 HUG는 “내용에 맞춘다”이고, CSS의 intrinsic sizing도 비슷합니다. 하지만 text wrapping, min size, flex shrink가 끼면 결과가 달라질 수 있습니다. 이럴 때는 중간 모델에서 fixed, hug, fill을 유지한 뒤, CSS export와 runtime preview를 따로 검증하는 편이 좋습니다.

4. Radius와 smoothing: 둥근 모서리는 생각보다 성격이 있습니다

기본 cornerRadius는 CSS border-radius로 잘 갑니다.

border-radius: 12px;

네 모서리가 다르면 이렇게 됩니다.

border-radius: 8px 16px 24px 4px;

그런데 radius는 box보다 커질 수 있습니다. 브라우저는 인접한 radius 합이 edge 길이를 넘으면 자동으로 줄입니다.

scale = min(
  width  / (topLeftX + topRightX),
  width  / (bottomLeftX + bottomRightX),
  height / (topLeftY + bottomLeftY),
  height / (topRightY + bottomRightY),
  1
)

normalizedRadius = radius * scale

Figma의 cornerSmoothing은 더 어렵습니다. CSS의 border-radius는 원호 기반이고, Figma smoothing은 squircle 또는 superellipse에 가까운 곡선으로 보입니다. 정확히 맞추려면 SVG path fallback이 필요합니다.

CSS 가능:
border-radius

정확도 필요:
SVG path로 rounded rectangle 생성

여기서 억지로 CSS 하나로 다 해결하려고 하면, 디자인 QA에서 “뭔가 모서리가 다른데요?”라는 아주 무서운 문장이 등장합니다.

5. Stroke: border와 stroke는 같은 척하지만 다릅니다

Figma stroke에는 strokeAlign이 있습니다.

INSIDE
CENTER
OUTSIDE

사각형의 원래 geometry가 w x h이고 stroke 두께가 s라면 visual bounds는 이렇게 달라집니다.

INSIDE:
visualWidth = w
visualHeight = h

CENTER:
visualWidth = w + s
visualHeight = h + s

OUTSIDE:
visualWidth = w + 2s
visualHeight = h + 2s

CSS border는 box model에서 기본적으로 요소 크기 계산에 영향을 줍니다. box-sizing: border-box를 쓰면 inside stroke처럼 다루기 좋습니다.

.rect {
  box-sizing: border-box;
  border: 4px solid #111;
}

하지만 center/outside stroke, dash, cap, join까지 정확히 가려면 SVG가 더 안전합니다.

<rect stroke="black" stroke-width="4" stroke-dasharray="8 4" />

즉 Figma stroke는 “border로 갈 수 있나?”보다 “이 stroke가 box stroke인가, path stroke인가?”를 먼저 판단해야 합니다.

6. Effects: shadow와 blur는 bounds를 키웁니다

Drop shadow는 CSS box-shadow로 꽤 잘 갑니다.

box-shadow: 0 12px 32px rgb(0 0 0 / 0.18);

하지만 bounds 계산은 별도입니다.

shadowLeft   = x + offsetX - blur - spread
shadowTop    = y + offsetY - blur - spread
shadowRight  = x + width  + offsetX + blur + spread
shadowBottom = y + height + offsetY + blur + spread

selection bounds를 geometry만 보고 잡으면 shadow가 잘립니다. export 이미지도 마찬가지입니다. blur나 shadow가 있으면 “보이는 영역”은 원래 도형보다 커집니다.

opacity와 blend mode는 색 합성입니다.

out = src * alpha + dst * (1 - alpha)

CSS로는 대체로 이렇게 갑니다.

opacity: 0.72;
mix-blend-mode: multiply;
filter: blur(8px);

단, Figma와 브라우저의 blending pipeline이 완전히 같지 않을 수 있습니다. 특히 isolation, stacking context, backdrop이 섞이면 변환기가 조용히 땀을 흘립니다.

7. Text: 폰트는 숫자보다 렌더러가 더 셉니다

텍스트는 CSS로 바로 옮길 수 있어 보입니다.

font-family: Inter, sans-serif;
font-size: 16px;
font-weight: 600;
line-height: 24px;
letter-spacing: -0.2px;

하지만 정확히 맞추려면 text metrics를 봐야 합니다.

lineBoxHeight = lineHeight
lineY(i) = top + i * lineHeight
glyphAdvance' = glyphAdvance + letterSpacing

Figma의 text auto resize도 중요합니다.

AUTO_WIDTH  -> width: max-content 계열
AUTO_HEIGHT -> width fixed, height auto
FIXED_SIZE  -> width/height fixed + overflow 처리

텍스트는 특히 운영체제, 브라우저, 폰트 파일, font feature에 따라 픽셀이 달라질 수 있습니다. 그래서 “완전한 픽셀 매칭”을 목표로 하면 CSS만으로는 피곤해질 수 있습니다. 웹으로 다시 편집할 텍스트는 CSS text로, 로고처럼 고정된 그래픽 텍스트는 SVG path나 asset fallback도 고려합니다.

8. Image fill: cover와 crop은 좌표 변환입니다

Figma image fill의 scaleMode는 CSS와 연결됩니다.

FILL -> object-fit: cover
FIT  -> object-fit: contain
CROP -> background-size/position 또는 imageTransform
TILE -> background-repeat

cover와 contain은 scale 계산입니다.

coverScale = max(boxWidth / imageWidth, boxHeight / imageHeight)
containScale = min(boxWidth / imageWidth, boxHeight / imageHeight)

renderWidth = imageWidth * scale
renderHeight = imageHeight * scale

center crop이면 offset도 계산합니다.

offsetX = (boxWidth - renderWidth) / 2
offsetY = (boxHeight - renderHeight) / 2

Figma의 imageTransform이 들어오면 gradient와 비슷하게 normalized image 좌표계를 box 좌표계로 옮기는 문제가 됩니다. 단순히 background-size: cover로 끝나지 않습니다. 이때는 UV transform을 계산해서 background-position, background-size, 또는 wrapper transform으로 나눠야 합니다.

9. Vector, mask, boolean: CSS box로 억지 번역하지 않기

Figma vector는 path입니다. CSS box가 아닙니다.

path point -> transform -> fill rule -> stroke -> mask/clip

단순한 사각형, 원, rounded rectangle은 CSS box나 SVG primitive로 바꿀 수 있습니다.

.circle {
  border-radius: 999px;
}

하지만 boolean operation, complex path, mask, winding rule이 들어오면 SVG가 더 자연스럽습니다.

<path d="..." fill-rule="evenodd" />
<clipPath id="clip">...</clipPath>
<mask id="mask">...</mask>

이건 포기라기보다 올바른 매체 선택입니다. CSS는 layout과 box paint에 강하고, SVG는 vector geometry에 강합니다. 친구끼리 역할을 나누면 됩니다. 싸움 붙이지 맙시다.

변환기의 세 가지 출력 전략

Figma to CSS 변환기는 모든 것을 CSS 한 줄로 만들려고 하면 금방 무리합니다. 대신 속성마다 전략을 나누는 게 좋습니다.

1. Exact CSS
   CSS 속성으로 의미와 픽셀이 충분히 맞는 경우

2. CSS Approximation
   거의 맞지만 일부 렌더링 차이가 허용되는 경우

3. SVG / Asset / Runtime fallback
   vector, mask, smoothing, complex effect처럼 CSS box 모델로 무리인 경우

예를 들면 이렇습니다.

simple rectangle       -> CSS div
rounded rectangle      -> CSS border-radius
smoothed rounded rect  -> SVG path fallback
linear gradient        -> CSS linear-gradient with transform conversion
complex vector mask    -> SVG
photo crop             -> CSS background or img wrapper transform

좋은 변환기는 “CSS를 많이 쓰는 변환기”가 아닙니다. 어떤 값이 CSS의 수학 모델과 맞는지 알고, 안 맞는 값은 더 정확한 표현으로 보내는 변환기입니다.

다음에 깊게 파면 좋은 강의들

이 레슨은 지도입니다. 여기서 더 깊게 들어가면 다음 주제들이 따로 한 강의씩 필요합니다.

1. Figma transform tree -> CSS DOM transform tree
2. Constraints -> responsive absolute layout
3. Auto Layout -> flex/grid sizing resolver
4. Radius smoothing -> SVG path generation
5. Stroke align -> CSS/SVG stroke strategy
6. Text metrics -> browser text layout difference
7. Image fill and imageTransform -> CSS background math
8. Mask/vector/boolean -> SVG export pipeline

오늘 핵심은 하나입니다.

Figma to CSS는 속성 이름 바꾸기가 아니라, Figma의 수학 모델을 CSS/SVG/DOM의 수학 모델로 옮기는 일입니다. 이걸 인정하면 구현이 훨씬 차분해집니다. 물론 차분한 척하는 거죠. 내부에서는 여전히 행렬이 춤을 춥니다.

Edit this page
최근 수정: 26. 5. 14. PM 5:45
Contributors: easylogic
Prev
Figma gradient를 CSS gradient로 변환하기
Next
Figma VectorNetwork 정리