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

selection rectangle과 marquee

이번에는 드래그 박스로 여러 오브젝트를 선택하는 marquee selection입니다.

사용자는 빈 공간에서 드래그해서 사각형을 만들고, 그 안에 들어온 오브젝트를 선택합니다. 겉으로는 익숙한 기능이지만, 안쪽에는 좌표계와 UX 정책이 들어 있습니다.

선택 박스는 화면에 그려지고, 오브젝트는 world에 있습니다. 또 좌표 변환이 등장합니다. 아주 성실하게 계속 나오죠.

마키 박스 만들기

selectionRect = rectFromPoints(startScreen, currentScreen)
marquee selection screen rectangle diagramscreen space querycompare(selectionRect, objectScreenBounds)
marquee rectangle은 screen 좌표의 질의 영역입니다. 오브젝트 bounds도 같은 screen 좌표로 투영한 뒤 비교해야 합니다.

드래그 시작점과 현재점을 이용해 screen rectangle을 만듭니다. 사용자가 왼쪽 위에서 오른쪽 아래로 드래그하든, 오른쪽 아래에서 왼쪽 위로 드래그하든 같은 rect가 나와야 합니다.

구현은 보통 이런 상태를 둡니다.

dragStart: pointerdown screen point
dragCurrent: pointermove screen point
selectionRect: rectFromPoints(dragStart, dragCurrent)
selectedIds: Set<objectId>

pointerdown에서 시작점을 저장하고, pointermove마다 현재점을 갱신합니다. pointerup이 오면 마지막 rectangle로 선택 결과를 확정합니다. 데모에서는 선택 박스를 일부러 화면에 남겨둡니다. 실제 제품에서는 pointerup 이후 박스를 숨기고 selection outline만 남기는 경우가 많습니다.

pointerdown -> dragStart = screenPoint
pointermove -> dragCurrent = screenPoint, update selectedIds
pointerup   -> commit selectedIds

교차냐 포함이냐

오브젝트를 조금만 걸쳐도 선택할지, 완전히 포함되어야 선택할지는 제품 정책입니다.

intersects = !(a.right < b.left || a.left > b.right || a.bottom < b.top || a.top > b.bottom)
contains = a fully includes b

교차 선택은 빠르고 관대합니다. 완전 포함 선택은 더 엄격합니다. 어떤 툴은 modifier key로 둘을 바꾸기도 합니다.

여기서 주의할 점이 하나 있습니다. contains는 “오브젝트의 어떤 bounds를 완전히 포함하느냐”를 먼저 정해야 합니다. 회전된 사각형의 screen AABB를 기준으로 하면, 실제 도형보다 더 큰 상자를 완전히 감싸야 합니다. 더 정교하게 하려면 오브젝트의 transformed corner 4개가 selection rect 안에 들어왔는지 보거나, 실제 shape geometry와 rectangle의 교차를 계산해야 합니다.

초기 에디터에서는 보통 screen AABB 기준으로 시작해도 충분합니다. 중요한 건 이 정책을 코드에서 분명하게 이름 붙이는 겁니다. 나중에 “왜 이건 선택되고 저건 안 돼요?”라는 질문이 오면, 정책 이름이 디버깅을 구해줍니다.

screen에서 비교할지 world에서 비교할지

마키 박스는 screen 좌표로 그립니다. 그래서 오브젝트 bounds도 screen으로 투영해서 비교하면 사용자가 보는 것과 판단이 잘 맞습니다.

objectScreenBounds = project(objectWorldBounds, viewport)
compare(selectionRect, objectScreenBounds)

DOM/CSS 기반 데모에서는 각 오브젝트의 local bounds를 world/screen matrix로 변환해 screen AABB를 만듭니다.

objectScreenBounds = transformBounds(localBounds, worldMatrix)

그 다음 selectionRect와 objectScreenBounds를 같은 screen 좌표계에서 비교합니다. 이게 포인트입니다. 한쪽은 screen, 한쪽은 world인 상태로 비교하면 선택 박스가 엉뚱하게 동작합니다. 좌표계가 섞이면 선택 도구는 아주 창의적으로 틀립니다. 별로 보고 싶지 않은 창의성입니다.

데모에는 rect만 두지 않고 rounded rect, circle, ellipse도 같이 둡니다. 이때 selection은 “보이는 곡선의 면적”이 아니라 각 오브젝트의 transformed screen bounds를 기준으로 합니다.

circleObject -> local bounds는 원을 감싸는 정사각형
ellipseObject -> local bounds는 타원을 감싸는 사각형
roundedRectObject -> local bounds는 radius 적용 전의 사각형
marquee selection -> 이 bounds와 selectionRect를 비교

이건 실제 에디터에서도 흔한 정책입니다. 마키 선택은 보통 “오브젝트가 차지하는 박스”를 고르는 도구에 가깝기 때문입니다. 만약 원의 실제 곡선 면적이나 border-radius로 잘린 corner까지 정확히 따지고 싶다면, hit testing 레슨에서 봤던 shape-specific 판정을 marquee intersection으로 확장해야 합니다. 다만 그건 계산이 훨씬 무거워지고, 사용자가 기대하는 선택 감각과 다를 수도 있습니다.

Shift로 선택 추가, Alt로 제외 같은 modifier 정책도 여기서 같이 처리합니다. 선택 도구는 수학도 중요하지만, 작은 UX 규칙들이 사용감을 많이 좌우합니다.

데모에서 볼 것

데모에서는 canvas 위에서 직접 드래그해 marquee rectangle을 그립니다. 드래그 중에도 선택 결과가 실시간으로 바뀌고, pointerup 이후에도 마지막 selection rectangle이 남아서 어떤 bounds로 선택됐는지 확인할 수 있습니다.

Policy 버튼으로 intersects와 contains를 바꿔보세요. intersects는 selection rectangle이 오브젝트 screen bounds와 조금만 겹쳐도 선택합니다. contains는 오브젝트 screen bounds가 selection rectangle 안에 완전히 들어와야 선택합니다.

rect, rounded rect, circle, ellipse가 모두 같은 방식으로 선택되는지도 보세요. 모양은 달라도 selection query는 같은 screen bounds 비교를 통과합니다. 이게 편집기에서 “visual geometry”와 “selection geometry”를 분리하는 첫걸음입니다.

오늘의 핵심은 selection rectangle이 단순한 div 하나가 아니라 “화면 좌표에서 만들어진 질의 영역”이라는 점입니다. 그 질의를 어떤 bounds에 적용할지 명확히 해야 합니다.

Edit this page
최근 수정: 26. 5. 14. PM 5:45
Contributors: easylogic
Prev
hit testing과 bounding box
Next
selection bounds와 handles