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

cursor anchored zoom

이번에는 zoom의 사용감을 결정하는 공식입니다.

그냥 zoom 값만 바꾸면 화면이 중심 기준으로 확대되면서 포인터 아래의 대상이 휙 밀려납니다. 사용자는 분명 특정 도형 위에 마우스를 올려놓고 확대했는데, 확대 후 그 도형이 다른 곳으로 도망간 것처럼 보입니다.

좋은 에디터는 커서 아래 world point를 유지합니다. 이걸 cursor anchored zoom이라고 부릅니다.

줌 전후 같은 점을 보게 하자

먼저 zoom 전 커서 아래 world point를 구합니다.

worldUnderCursor = screen / oldZoom + oldCamera

이제 zoom을 바꾼 뒤에도 같은 screen 좌표가 같은 world point를 가리키게 camera를 다시 계산합니다.

newCamera = worldUnderCursor - screen / newZoom

이 한 줄이 핵심입니다. zoom만 바꾸는 게 아니라 camera도 같이 보정해야 합니다. 사용자는 이 차이를 “부드럽다” 또는 “뭔가 이상하다”로 바로 느낍니다.

wheel delta는 정규화가 필요하다

wheel 입력은 장치와 브라우저에 따라 값이 제법 다릅니다. 마우스 휠과 트랙패드는 감각도 다르고 delta도 다릅니다.

보통은 지수 함수를 써서 zoom factor를 만듭니다.

newZoom = clamp(oldZoom * exp(-wheelDelta * k), min, max)

clamp로 최소/최대 zoom을 제한해야 합니다. 0에 가까운 zoom은 역행렬과 hit testing을 불안정하게 만들고, 너무 큰 zoom은 화면이 실용성을 잃습니다.

구현 순서

wheel event의 clientX/clientY를 viewport local 좌표로 바꿉니다. 그 다음 viewport 중심을 빼서 camera 공식이 쓰는 screen 좌표로 만들고, old zoom 기준 world point를 구하고, new zoom을 계산하고, 마지막으로 new camera를 계산합니다.

1. local = event.client - viewportRect.left/top
2. screen = local - viewportCenter
3. worldUnderCursor 구하기
4. newZoom 구하기
5. newCamera 구하기
6. content transform 갱신하기

grid와 ruler도 같은 frame에서 새 camera/zoom을 반영해야 흔들림이 없습니다.

데모에서 볼 것

데모에서는 마우스를 캔버스 위로 움직인 다음 wheel로 확대/축소해보세요. 기본 wheel은 cursor anchored zoom입니다. cursor world 값이 zoom 순간에 튀지 않고 같은 지점을 붙잡는지 readout으로 확인할 수 있습니다.

트랙패드에서 화면을 움직이고 싶을 때는 Space를 누른 상태로 wheel/trackpad scroll을 해보세요. 이때는 zoom을 건드리지 않고 camera만 이동합니다. 즉, 기본 모드는 zoom, Space 모드는 pan입니다. 입력 모드를 분리해두면 트랙패드에서 “움직였는데 확대도 같이 되는” 애매한 느낌을 줄일 수 있습니다.

오른쪽 아래 DOM camera stack도 같이 보세요. wheel을 굴릴 때 child shape의 left/top은 그대로인데, .world-layer의 transform matrix와 camera 값만 바뀝니다. 이게 진짜 중요한 장면입니다. “확대했으니 도형 좌표도 바꿔야 하나?” 싶은 유혹이 오는데, 그러면 문서 모델이 금방 어질어질해집니다. 카메라만 바꿉시다. 도형은 자기 자리에 앉혀두고요.

오늘의 핵심은 zoom이 단순한 scale 변경이 아니라 camera 보정까지 포함한 UX 공식이라는 점입니다. 커서 아래 점을 붙잡아두면 에디터가 훨씬 똑똑해 보입니다.

Edit this page
최근 수정: 26. 5. 14. PM 5:45
Contributors: easylogic
Prev
viewport와 camera 모델
Next
ruler와 tick 계산