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 길이 구하기

Motion path 수학

이번에는 motion path입니다.

지금까지 모션을 “값이 시간에 따라 변한다”로 봤습니다. x를 보간하고, opacity를 보간하고, keyframe 사이를 샘플링했죠. motion path는 여기에 path geometry를 끼워 넣습니다.

time -> progress -> distance on path -> point on path -> render

즉 x, y를 직접 keyframe으로 찍는 대신, 오브젝트가 따라갈 경로를 먼저 정의하고 그 경로 위의 거리로 위치를 계산합니다.

1. path parameter와 거리 parameter는 다르다

cubic bezier를 배울 때 우리는 B(t)를 봤습니다.

B(t) = (1-t)^3 P0
     + 3(1-t)^2t P1
     + 3(1-t)t^2 P2
     + t^3 P3

여기서 t는 0..1입니다. 그래서 “그럼 t를 progress로 쓰면 되겠네?”라고 생각하기 쉽습니다.

point = B(progress)

하지만 이러면 속도가 일정하지 않을 수 있습니다. bezier의 parameter t는 곡선 위의 거리와 선형 대응하지 않습니다.

t가 0.1 증가해도 이동 거리는 구간마다 다를 수 있다.

그래서 motion path에서 진짜 필요한 값은 parameter가 아니라 arc length, 즉 경로를 따라간 거리입니다.

distance = progress * totalPathLength
point = pointAtLength(distance)

이렇게 하면 progress가 일정하게 증가할 때 path 위 거리도 일정하게 증가합니다. 드디어 “속도”가 우리가 기대하는 쪽으로 얌전해집니다. 물론 easing을 넣으면 다시 개성 있어집니다.

2. arc length는 작은 조각들의 합이다

직선의 길이는 쉽습니다.

length = distance(A, B)

곡선은 보통 닫힌 형태의 쉬운 공식으로 다루기 어렵습니다. 그래서 작은 선분으로 쪼개 근사합니다.

samples = [B(0), B(0.01), B(0.02), ... B(1)]
length ≈ sum(distance(samples[i], samples[i + 1]))

이걸 누적하면 lookup table을 만들 수 있습니다.

table = [
  { t: 0.00, length: 0 },
  { t: 0.01, length: 3.4 },
  { t: 0.02, length: 7.1 },
  ...
]

원하는 distance가 들어오면 table에서 가까운 구간을 찾고, 그 안에서 다시 보간합니다.

distance -> t -> B(t)

SVG에서는 이 작업을 브라우저가 도와줍니다.

const total = path.getTotalLength();
const point = path.getPointAtLength(distance);

MDN의 SVGGeometryElement 문서도 getTotalLength()와 getPointAtLength()를 제공합니다. 강의 데모도 이 API를 사용합니다. 수학 숙제를 브라우저에게 조금 맡기는 겁니다. 숙제는 나눠야 제맛입니다.

3. offset-distance는 path length의 비율이다

CSS motion path에서는 offset-path와 offset-distance를 씁니다.

.node {
  offset-path: path("M 40 180 C 160 20, 300 300, 470 120");
  offset-distance: 50%;
  offset-rotate: auto;
}

offset-distance는 path를 따라간 거리입니다.

distance = offsetDistancePercent * totalPathLength

0%면 시작점, 100%면 끝점입니다.

0%   -> distance = 0
50%  -> distance = totalLength * 0.5
100% -> distance = totalLength

CSS animation으로는 이렇게 갑니다.

@keyframes follow-path {
  from { offset-distance: 0%; }
  to { offset-distance: 100%; }
}

이 방식은 선언형이라 간단합니다. 버튼 hover, 장식적인 path motion, landing page section animation 같은 곳에서는 아주 좋습니다.

4. 방향은 tangent에서 나온다

path 위에서 오브젝트가 움직일 때 보통 방향도 path를 따라 돌립니다.

CSS에서는 offset-rotate: auto가 이 역할을 합니다. 수학적으로는 tangent angle입니다.

tangent = pointAtLength(s + ε) - pointAtLength(s - ε)
angle = atan2(tangent.y, tangent.x)

여기서 s는 현재 path distance입니다.

s = progress * totalLength

근처의 두 점을 찍어서 차이를 구하면 tangent 방향을 근사할 수 있습니다.

const p0 = path.getPointAtLength(s - 1);
const p1 = path.getPointAtLength(s + 1);
const angle = Math.atan2(p1.y - p0.y, p1.x - p0.x);

이 각도를 CSS transform에 넣으면 오브젝트가 path 방향을 바라봅니다.

transform: translate(x, y) rotate(angle);

offset-rotate: auto 90deg 같은 보정도 결국 tangent angle에 추가 각도를 더하는 것입니다.

finalAngle = tangentAngle + angleOffset

5. normal은 path 옆으로 밀 때 쓴다

tangent가 진행 방향이라면, normal은 옆 방향입니다.

tangent = normalize(dx, dy)
normal = (-tangent.y, tangent.x)

normal은 path 옆으로 offset을 줄 때 씁니다.

offsetPoint = point + normal * sideOffset

예를 들면 이런 기능입니다.

path를 따라 움직이되 12px 위로 띄우기
여러 오브젝트가 같은 path를 따라가되 서로 다른 lane에 배치하기
텍스트나 marker를 path 위가 아니라 path 옆에 붙이기

디자인 도구에서는 label, connector marker, motion trail, path editor overlay에서 자주 나옵니다.

6. keyframe timeline과 motion path를 합치기

motion path는 keyframe 엔진과도 잘 맞습니다. 직접 x, y를 keyframe으로 두는 대신 offsetDistance를 keyframe으로 둡니다.

node.tracks = {
  pathId: "curve-1",
  offsetDistance: [
    { time: 0, value: 0 },
    { time: 1200, value: 0.55 },
    { time: 2400, value: 1 }
  ],
  offsetRotate: "auto",
  sideOffset: [
    { time: 0, value: 0 },
    { time: 1200, value: 24 },
    { time: 2400, value: 0 }
  ]
};

샘플링은 이렇게 됩니다.

progress value -> distance = value * pathLength
point = pointAtLength(distance)
tangent = pointAtLength(distance + ε) - pointAtLength(distance - ε)
angle = atan2(tangent.y, tangent.x)
normal = perpendicular(tangent)
position = point + normal * sideOffset

이렇게 하면 path 자체를 편집해도 motion이 path를 따라 다시 계산됩니다. x, y keyframe을 전부 다시 찍지 않아도 됩니다. editor에서는 이게 큰 장점입니다.

7. CSS와 JS 중 무엇을 쓸까?

CSS motion path가 좋은 경우:

정적인 path를 따라가는 단순 animation
hover/focus 장식 motion
스크롤/섹션 진입 animation
path 편집이 필요 없는 product UI

JS motion path engine이 좋은 경우:

path를 사용자가 직접 편집한다.
timeline scrub이 필요하다.
path 위 collision, snapping, marker 배치가 필요하다.
normal offset이나 custom orientation이 필요하다.
Canvas/SVG editor model과 동기화해야 한다.

CSS는 간단하고 선언형입니다. JS는 귀찮지만 강합니다. 손이 많이 가는 대신, path 편집기나 motion design tool에는 필요한 제어권을 줍니다.

마무리

motion path 수학은 이렇게 정리됩니다.

1. path의 전체 길이를 구한다.
2. progress를 path 위 거리로 바꾼다.
3. 거리에서 point를 샘플링한다.
4. 근처 두 점으로 tangent와 angle을 구한다.
5. 필요하면 normal로 path 옆 offset을 만든다.
6. position과 rotation을 렌더링한다.

여기까지 오면 공간 좌표, 시간 좌표, path 좌표가 한 번에 만납니다. 그래픽 툴 강의의 마지막 보스답습니다. 다행히 보스의 약점은 명확합니다. 결국 또 point, vector, distance, angle입니다.

Edit this page
최근 수정: 26. 5. 14. PM 5:45
Contributors: easylogic
Prev
Keyframe timeline 엔진
Next
Bezier curve 길이 구하기