feat: 강의실 화이트보드 — 그리기/도형/텍스트/선택·편집/레이어#71
Conversation
- 객체(retained) 기반 캔버스로 전환: 선택/이동/크기변경/삭제/수정 - 도구: 선택·펜·형광펜·직선·사각형·원·텍스트·지우개(부분)·전체지우기 - 선굵기/투명도 슬라이더(임의값), 임의 색 선택(컬러피커), 텍스트 글꼴/크기/굵기 - 지우개 원형 미리보기(굵기 연동) - ClassroomPage: mountedRef를 마운트 시 true로 리셋(StrictMode에서 입장 멈춤 회귀 수정)
- 회전 핸들 + 각도 말풍선(실시간 표시·직접 입력) - 커서: 이동(move)/크기조절(회전각 반영 nwse·nesw·ns·ew)/회전(원형 화살표) - Shift: 회전 45° 스냅, 크기조절 비율 유지, 이동 수직/수평 고정 - Ctrl+이동: 복사본 드래그(원본 유지)
- 다중선택(마퀴 드래그/Shift), 다중 이동·삭제·속성적용 - 곡선 도구(클릭 점 추가→더블클릭 완료) - 삼각형·다각형 추가, 다각형 각수 ↑↓ 실시간(그리는 중 draft 포함)·말풍선 - 레이어 패널: 도형별 모양 아이콘, 헤더 드래그 이동(보드 내 제한), 고정높이+스크롤, 표시/숨김·정렬·수정·삭제 - 좌측 툴바 그룹화(펜/형광펜, 직선/곡선, 도형) 길게눌러 플라이아웃
- whiteboard/constants.js: 상수·id 생성기 - whiteboard/geometry.js: 기하·히트테스트 순수함수(bbox/center/hitTest/handleAt/mapShape 등) - whiteboard/painting.js: 캔버스 도형 렌더링(paintShape/paintPath) - whiteboard/OptionsBar.jsx: 상단 옵션바 UI - whiteboard/LayersPanel.jsx: 레이어 패널 UI - Whiteboard.jsx: 상태+포인터 핸들러 오케스트레이터(548→약 300줄) 동작 동일, 빌드 통과
- 텍스트 editor: textarea(멀티라인, Enter완료/Shift+Enter줄바꿈), key remount+autoFocus - 멀티라인 렌더/측정(painting/geometry) - 좌측 도구 그룹 선택을 onPointerUp→onClick 기반으로(탭 안정성) - handleDown에 진단 console.log + tool prop 폴백 (임시) - [임시] 보드 좌하단 디버그 배지
- 사진(이미지) 불러오기: 여러 장, 좌측 사이드바 버튼(forwardRef로 호출), 이동/크기/회전/Ctrl/Shift/레이어 기존 도형과 동일 - 옵션 표시 정리: 지우개=투명도 숨김(굵기는 '지우개 크기'로 표시), 텍스트/사진=굵기 숨김 - 텍스트 크기 드롭다운→직접 입력(최대 100px 클램프) - 전체 지우기 아이콘을 지우개 모양 SVG로 교체 - 레이어 패널에 사진 아이콘/라벨 추가
leejy1019
left a comment
There was a problem hiding this comment.
FE PR #71 코드 리뷰 — 강의실 화이트보드 (그리기/도형/텍스트/선택·편집/레이어)
잘 된 부분
- 관심사 분리가 모범적입니다.
Whiteboard.jsx(상태·포인터 오케스트레이터) /constants.js/geometry.js(순수 기하·히트테스트) /painting.js(렌더) /OptionsBar·LayersPanel(UI)로 깔끔하게 쪼갰습니다. 838줄 규모인데도 각 파일 책임이 명확해 읽기 쉽습니다 - retained-mode 설계가 정석적입니다. 회전 반영
toLocal/screenAABB히트테스트, 모서리 anchor 기반 리사이즈, 마퀴 교차 판정, DPR 보정 캔버스(setTransform(dpr,...))까지 — 캔버스 에디터에서 흔히 빠뜨리는 디테일을 잘 챙김 - 한글 IME 조합 처리 (
composingRef+e.nativeEvent.isComposing)를 정확히 구현 — 조합 중 Enter/Escape 오작동을 막는, 자주 놓치는 부분입니다 - StrictMode 재마운트 회귀 수정 (
mountedRef를 마운트 effect에서true로 복구) — #56에서 지적됐던 "입장 중…" 멈춤을 정확히 해결 - 곡선 미드포인트 2차 베지어 스무딩, 회전/리사이즈 커서 각도 반영, Shift/Ctrl 모디파이어, 포인터 캡처 +
touchAction:none등 인터랙션 완성도가 높음
🟠 전역 keydown 핸들러가 다른 입력창(채팅 등) 입력을 가로챔
Whiteboard.jsx — onKey (window keydown)
const onKey = (e) => {
if (editing) return // ← 화이트보드 자체 텍스트 편집만 체크
if (toolRef.current === 'polygon' && (ArrowUp/Down)) { e.preventDefault(); ... }
if ((Delete || Backspace) && selRef.current.length) { /* 선택 도형 삭제 */ }
}
window.addEventListener('keydown', onKey)editing은 화이트보드 내부 텍스트 편집 상태만 가리킵니다. 같은 강의실 화면의 채팅 입력창에 포커스가 있어도 이 핸들러는 동작합니다. 그래서:
- 채팅 입력 중 Backspace → 도형이 선택돼 있으면 그 도형이 삭제됨 (글자 지우려다 도형이 날아감)
- polygon 도구가 켜진 채 채팅에서 ↑/↓ →
preventDefault로 커서 이동이 막힘
e.target이 폼 요소면 무시하도록 가드를 추가하면 해결됩니다.
const el = e.target
if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.isContentEditable) return강의실은 채팅과 화이트보드가 한 화면에 공존하므로 실제로 부딪칠 수 있는 시나리오입니다.
🟡 이미지 Object URL이 해제되지 않음 — 메모리 누수
Whiteboard.jsx — addImages
const url = URL.createObjectURL(file)
const img = new Image()
img.onload = () => { ... setShapes(prev => [...prev, { ..., src: url, _img: img }]) }
img.onerror = () => URL.revokeObjectURL(url) // 실패 시에만 revoke성공 경로에서 URL.revokeObjectURL이 호출되지 않습니다. 캔버스는 디코드된 _img로 그리므로 load 직후 revoke해도 안전합니다. 또 도형 삭제·"전체 지우기" 시에도 해당 이미지의 Object URL이 남습니다. 큰 사진을 여러 장 넣다 보면 누적됩니다. load 직후 revoke하거나, 삭제/clear 시 해당 shape의 url을 정리해 주세요.
🟡 성능 — 포인터 이동마다 전체 캔버스 재그리기
펜으로 그리는 동안 매 pointermove가 setDraft → 리렌더 → redraw()(전체 도형 순회)를 유발합니다. 도형이 수백 개 쌓이면 드로잉이 무거워질 수 있습니다. MVP·로컬 사용엔 충분하지만, 장기적으로 requestAnimationFrame으로 redraw를 코얼레싱하거나 "확정된 도형 레이어 + 드로잉 중 도형만 다시 그리는" 더블 레이어 구조를 고려할 만합니다. (낮은 우선순위)
🟡 실시간/영속화 단계에서 직렬화 불가 — 후속 작업용 메모
PR 설명대로 실시간 공유가 후속이라 지금은 무관하지만, 미리 인지하면 좋습니다:
image도형의_img(Image 객체)·src(blob URL)는 다른 클라이언트로 전송 불가 — 서버 업로드 URL로 바꿔야 함nextId가 모듈 전역 카운터(s1,s2…)라 여러 클라이언트에서 ID 충돌 — UUID나clientId:seq형태 필요
🟡 기존 텍스트 편집 시 도형의 원래 글꼴이 아닌 현재 툴바 값으로 표시
텍스트를 더블클릭해 편집하면 textarea가 현재 툴바의 fontFamily/fontSize/bold로 렌더되고, commitText도 text만 갱신합니다. serif로 만든 텍스트를 sans-serif 상태에서 편집하면 편집 중 글꼴이 어긋나 보입니다. 편집 진입 시 해당 도형의 글꼴 값을 툴바/에디터에 로드하면 일관됩니다. (낮은 우선순위)
참고 — 툴바 키보드 접근성
좌측 도구·색상이 <div onClick>이라 키보드 포커스/Enter로 조작이 안 됩니다(기존 패턴 유지). 캔버스 도구 특성상 우선순위는 낮지만, 추후 <button> + aria-pressed로 바꾸면 접근성이 좋아집니다.
총평: 이 규모의 캔버스 에디터를 회전 히트테스트·IME·DPR 보정까지 갖춰 깔끔한 모듈 구조로 구현한, 완성도 높은 PR입니다. 머지 전 🟠 전역 keydown 가드(채팅과 충돌)만 꼭 처리해 주세요 — 같은 화면에 채팅이 있어 실사용에서 부딪칩니다. 🟡 이미지 URL 누수 정도는 가볍게 같이 고치면 좋고, 나머지는 실시간 연동 후속 때 챙기면 됩니다.
- 각 페이지가 자신의 shapes(도형=레이어) 보관, 전환 시 해당 페이지 내용·레이어 표시 - 하단 페이지 바: 현재/총 페이지 표시(예 5 / 12) + ◀ ▶ 이동 + '+ new page' 추가 - 페이지 전환 시 선택/그리기 중 상태 초기화(페이지별 독립)
요약
강의실 화이트보드(로컬 필기) 프론트엔드 구현. 객체(retained) 기반
<canvas>로 그리기·선택·편집·레이어 관리를 지원합니다. (실시간 공유는 후속 작업)도구
선택·편집
레이어 패널
색상·속성
구조
src/pages/classroom/Whiteboard.jsx— 상태·포인터 핸들러 오케스트레이터whiteboard/constants.js·geometry.js(기하·히트테스트) ·painting.js(렌더) ·OptionsBar.jsx·LayersPanel.jsx로 관심사 분리참고
mountedRef) 수정 포함vite build통과. 로컬 전용이며 다른 참가자와의 실시간 동기화는 후속.