Skip to content

♻️ Refactor: 보드 스티커 렌더링 Konva -> DOM 전환 및 인터랙션 스펙 구현 - #59

Open
euoonw wants to merge 8 commits into
feat/sticker-drag-resizefrom
refactor/board-dom-interaction
Open

♻️ Refactor: 보드 스티커 렌더링 Konva -> DOM 전환 및 인터랙션 스펙 구현#59
euoonw wants to merge 8 commits into
feat/sticker-drag-resizefrom
refactor/board-dom-interaction

Conversation

@euoonw

@euoonw euoonw commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

🔍 PR 요약

보드 스티커 렌더링을 Konva 캔버스에서 순수 DOM으로 전환하고,
디자이너가 확정한 인터랙션 스펙(2손가락 회전+확대, 제스처 라우팅, 90° 스냅, 뱃지 z-순서, 선택 표시 등)을 새로 구현

인터렉션 스펙 참고
https://claude.ai/code/artifact/8c982d99-e3a1-4bca-b9db-fb930257f90a

이번 pr은 바로 develop에 머지하는게 아니라 이전 작업인 feat/sticker-drag-resize 브랜치에 우선 머지합니다!

🧾 관련 이슈

🧠 의도 및 배경

기존엔 스티커는 konva로 그리고, 제목 뱃지만 Html(react-konva-utils)로 캔버스 위에 얹는 구조였습니다. 이 구조에서 뱃지의 z-index가 konva랑 맞물리지 않는 문제가 있어서 뱃지를 konva로 전환할 예정이었습니다. 그런데 이렇게 계속 konva로 진행하다보니 복잡도가 너무 높아져서 스티커를 전면적으로 dom 리액트 컴포넌트로 전환하는 방향을 고민하게 됐습니다,,

캔버스기능 구현한다고 당연히 캔버스 라이브러리부터 고르다 보니 사전에 깊은 고민을 하지 못했던 부분인데
지금 저희 서비스 보드 기획에 복잡한 기능은 없고 이정도 규모면 앞으로 계속해서 konva 복잡도를 감수하는게 맞나 싶어져서, 검증 삼아 브랜치를 새로 파서 dom으로 전환한 뒤에 전달받은 스펙 기반 인터렉션을 새로 구현했습니다.
Konva가 주는 대량 렌더링 성능 같은 장점도 있긴한데, 보드당 스티커 수십개 수준에서는 의미가 크게 없다고 판단했고, 기능이 더 쌓이기 전에 지금 DOM으로 옮기는 게 낫다고 우선 저는 생각합니다!
이번 PR의 2손가락 회전+확대 제스처 같은 것도 Konva 좌표계 위에서 구현했다면 훨씬 복잡했을 작업인데 꽤 간단히 해결했습니다
확인해보시고 그래도 konva 유지가 낫다 싶으면 말씀해주셔도 됩니다!

참고로 나중에 붙일 드로잉 기능은 이 결정이랑 상관없이 스티커를 konva로 유지하든 말든 어차피 별도의 캔버스 레이어로 가야 해서 문제 없고, 텍스트 삽입도 오히려 더 간단해집니다.

디자이너님이 전달해주신 인터렉션 스펙에서 도트배경 애니메이션은 이번 범위에서는 제외했습니다.

변경사항이 많아 주요 변경사항을 자세히 작성해두었습니다!

🛠️ 주요 변경 사항

변경된 파일이 많아서 지금 작업중/리뷰중이신 것중에 충돌되는것들 있을텐데, 머지 충돌은 다른분들 작업 머지되면 제가 정리한 뒤에 머지하겠습니다!

렌더링/제스처 핵심

  • BoardCanvas.tsx: Konva 이벤트 대신 Pointer Events를 직접 구독해 제스처 상태 머신을 구현했습니다.

    • Gesture 유니온
      • pan(배경 1손가락) → pinch(배경 2손가락, 보드 줌)
      • move(스티커 1손가락) → stickerPinch(스티커 2손가락, 회전+확대)
      • 손가락 수가 바뀔 때마다 기준값을 다시 잡고(rebase), 2→1로 줄어들 때는 커밋하지 않고 남은 손가락 기준으로 이어가다가 마지막 손가락이 뗄 때만 저장합니다.
    • 스펙의 핵심 규칙(스티커에서 시작한 제스처는 두 번째 손가락이 스티커 밖에 닿아도 같은 제스처로 합류)을 구현하기 위해, pointerdown은 첫 손가락일 때만 히트테스트하고 이후 손가락은 무조건 기존 제스처에 합류시킵니다.
    • 네이티브 이벤트 리스너 안에서 stale closure 문제를 피하려고 camera/stickers/selectedId 등을 ref로 미러링해서 씁니다(매 렌더 후 useEffect로 동기화).
    • 드래그 없이 탭만 한 경우(값이 실제로 안 바뀐 경우) 저장 요청을 스킵하는 unchanged 가드가 있습니다.
  • Sticker.tsx: Konva Image → DOM 로 교체

    • stickerZIndex/badgeZIndex 헬퍼를 여기서 export합니다.
    • 스펙은 "뱃지 z-index = 스티커 z+1"이지만, zIndex가 연속 정수(2, 3 등)일 때 인접 스티커와 충돌하는 문제를 실기기 테스트 중 발견해서 스티커 z2 / 뱃지 z2+1로 2배 간격을 뒀습니다. 상대적 순서는 스펙과 동일합니다.
  • StickerBadgeMark.tsx(신규): 뱃지를 별도 컴포넌트로 분리하고, 스티커의 자식이 아니라 같은 보드 레이어의 형제 노드로 렌더링합니다. CSS transform이 자식에 새 stacking context를 만들어서, 자식으로 두면 z-index가 전역으로 비교되지 않는 문제가 있었습니다.

    • 스펙(뱃지는 위치만 따라가고 회전·크기 고정)대로 translate(-50%,-50%)만 적용하고 회전은 안 합니다.
    • 보드 전체 줌은 부모(content)의 공통 transform에 포함되어 자동으로 같이 커집니다.
    • 선택된 스티커는 배열에서 필터링해서 아예 렌더링하지 않는 방식으로 "선택 시 뱃지 숨김"을 구현했습니다.
  • SelectBox.tsx: 기존엔 코너 핸들을 드래그해서 리사이즈하는 인터랙티브 컴포넌트였는데, 리사이즈가 2손가락 핀치로 옮겨가면서 순수 시각(비인터랙티브, pointerEvents: none) 컴포넌트로 다시 작성했습니다.

순수 계산 함수 (테스트 포함)

  • board-camera.ts

    • computeBoardPinchZoom 추가 — 기존엔 핀치 중 pointermove 이벤트 발생 빈도에 비례해서 줌이 튀는 버그가 있었는데, 시작 거리 대비 현재 거리의 비율로 계산하도록 바꿔서 손가락 움직임에만 비례하게 고쳤습니다.
    • zoomCameraTo 추가 — 더블탭으로 줌 1.0x 복귀 시 탭 지점을 화면상 같은 위치에 고정한 채 점프하도록. 죽은 코드였던 pinchToZoomParams는 삭제.
  • board-transform.ts

    • computeStickerPinchTransform 추가 — 회전+확대+이동을 두 손가락의 중점 기준으로 한 번에 계산(스펙: 회전 중심은 스티커 중심이 아니라 손가락 중점).
    • applySnap 추가 — 90°/±5° 스냅. 코너 드래그 리사이즈가 없어지면서 computeResizeScale은 삭제.
  • geometry.ts: angleBetween, rotatePoint, clamp 추가.

부수 변경

  • save-sticker-image.ts: Konva Stage가 없어지면서 캡처 방식을 html-to-image의 toCanvas로 교체했습니다.
  • apps/web/app/layout.tsx, apps/mobile/.../AppWebView.tsx: 인터랙션 스펙의 "뷰포트 확대 방지" 항목 — 웹은 viewport meta에 userScalable: false, 안드로이드 웹뷰는 scalesPageToFit={false}. (iOS는 gesturestart 계열 preventDefault로 이미 처리되어 있던 부분에 안드로이드 쪽 빠진 걸 채운 것.)
  • board-api.ts: boardApi.list()도 USE_MOCK사용할 수 있게 변경

🚨 트러블슈팅 (선택)

  • 보드 핀치줌 증폭 버그: 위 computeBoardPinchZoom 참고
  • 더블탭 줌 리셋 시 엉뚱한 곳으로 이동: 위 zoomCameraTo 참고

남은 항목 (이번 PR 범위 밖)

  • 90° 스냅 햅틱 피드백: 스펙에서 필수로 명시하는데 아직 미구현입니다. 안드로이드는 navigator.vibrate()로 웹에서 바로 가능하지만, iOS는 네이티브 브릿지가 필요합니다. 이번 pr이 너무 커져서 별도 이슈로 진행하겠습니다
  • 배경 도트가 보드 팬/줌과 함께 움직이는 것: PR #57이 develop에 merge된 뒤 별도로 진행하겠습니다

📸 스크린샷

아이폰 화면녹화에 터치 보이게 하는법이 없길래 우선 이렇게만...

ScreenRecording_08-10-2026.12-41-42_1.mov

@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ppotto-client-web Ready Ready Preview Aug 10, 2026 3:52am

@kimyounghee425 kimyounghee425 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다!
시뮬레이터에서 스티커 드래그가 되지 않습니다! 이 버그만 수정해주세요!
추후 konva 가 쓰일 일이 없을 것 같으면 pacakges.json 도 정리 부탁드립니다.

하고 지금 BoardCanvas.tsx 에 모든 상태랑 전이가 몰려있습니다.
다음 PR에서 전이만 한곳에서 reducer로 관리하고 상태별 동작은 분리하면 좋을 것 같아요. 그래야 디버깅이 좀 수월할 듯 싶어요. 전이 테스트도 추가해주시면 좋을 것 같습니다!
그리고 요건 앞으로 상태가 좀 많아질 것 같아 xstate 미리 고려해봐도 좋아보여요

@euoonw

euoonw commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator Author

고생하셨습니다! 시뮬레이터에서 스티커 드래그가 되지 않습니다! 이 버그만 수정해주세요! 추후 konva 가 쓰일 일이 없을 것 같으면 pacakges.json 도 정리 부탁드립니다.

하고 지금 BoardCanvas.tsx 에 모든 상태랑 전이가 몰려있습니다. 다음 PR에서 전이만 한곳에서 reducer로 관리하고 상태별 동작은 분리하면 좋을 것 같아요. 그래야 디버깅이 좀 수월할 듯 싶어요. 전이 테스트도 추가해주시면 좋을 것 같습니다! 그리고 요건 앞으로 상태가 좀 많아질 것 같아 xstate 미리 고려해봐도 좋아보여요

혹시 시뮬레이터에서 어떤 드래그가 안되나요? 선택박스(하늘색박스)는 잘 뜨고 드래그로 이동만 안되나요? 리사이즈는 되고 드래그만 안되는건가요?!?
제 시뮬레이터에서는 잘 동작해서 어느 부분이 문제인지 추측이 잘 안되네요 ㅠㅠ 이런 😭 pnpm install + 캐시 클리어 후에도 똑같은지 한번 더 확인 부탁드려도 될까요?

보드캔버스 부분은 말씀하신대로 reducer로 분리하면 좋을 것 같아요! 다음 pr에서 작업해볼게요! xstate도 고려해보겠습니다!

@kimyounghee425

Copy link
Copy Markdown
Collaborator

고생하셨습니다! 시뮬레이터에서 스티커 드래그가 되지 않습니다! 이 버그만 수정해주세요! 추후 konva 가 쓰일 일이 없을 것 같으면 pacakges.json 도 정리 부탁드립니다.
하고 지금 BoardCanvas.tsx 에 모든 상태랑 전이가 몰려있습니다. 다음 PR에서 전이만 한곳에서 reducer로 관리하고 상태별 동작은 분리하면 좋을 것 같아요. 그래야 디버깅이 좀 수월할 듯 싶어요. 전이 테스트도 추가해주시면 좋을 것 같습니다! 그리고 요건 앞으로 상태가 좀 많아질 것 같아 xstate 미리 고려해봐도 좋아보여요

혹시 시뮬레이터에서 어떤 드래그가 안되나요? 선택박스(하늘색박스)는 잘 뜨고 드래그로 이동만 안되나요? 리사이즈는 되고 드래그만 안되는건가요?!? 제 시뮬레이터에서는 잘 동작해서 어느 부분이 문제인지 추측이 잘 안되네요 ㅠㅠ 이런 😭 pnpm install + 캐시 클리어 후에도 똑같은지 한번 더 확인 부탁드려도 될까요?

보드캔버스 부분은 말씀하신대로 reducer로 분리하면 좋을 것 같아요! 다음 pr에서 작업해볼게요! xstate도 고려해보겠습니다!

2026-08-11.1.34.39.mov

이렇게 돼요 왠진 모르겠지만
회전해도 -> 돌아옴
끌어서 이동이 안됨

@kimyounghee425

kimyounghee425 commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

고생하셨습니다! 시뮬레이터에서 스티커 드래그가 되지 않습니다! 이 버그만 수정해주세요! 추후 konva 가 쓰일 일이 없을 것 같으면 pacakges.json 도 정리 부탁드립니다.
하고 지금 BoardCanvas.tsx 에 모든 상태랑 전이가 몰려있습니다. 다음 PR에서 전이만 한곳에서 reducer로 관리하고 상태별 동작은 분리하면 좋을 것 같아요. 그래야 디버깅이 좀 수월할 듯 싶어요. 전이 테스트도 추가해주시면 좋을 것 같습니다! 그리고 요건 앞으로 상태가 좀 많아질 것 같아 xstate 미리 고려해봐도 좋아보여요

혹시 시뮬레이터에서 어떤 드래그가 안되나요? 선택박스(하늘색박스)는 잘 뜨고 드래그로 이동만 안되나요? 리사이즈는 되고 드래그만 안되는건가요?!? 제 시뮬레이터에서는 잘 동작해서 어느 부분이 문제인지 추측이 잘 안되네요 ㅠㅠ 이런 😭 pnpm install + 캐시 클리어 후에도 똑같은지 한번 더 확인 부탁드려도 될까요?
보드캔버스 부분은 말씀하신대로 reducer로 분리하면 좋을 것 같아요! 다음 pr에서 작업해볼게요! xstate도 고려해보겠습니다!

2026-08-11.1.34.39.mov
이렇게 돼요 왠진 모르겠지만 회전해도 -> 돌아옴 끌어서 이동이 안됨

아 보드 저장이 400떠서 그런거였네요...
목데이터로 정상동작 확인했습니다. 바로 머지하면 될 것 같아요~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants