diff --git a/frontend/src/lib/components/HandwriteCanvas.svelte b/frontend/src/lib/components/HandwriteCanvas.svelte index 0a64008..658ba16 100644 --- a/frontend/src/lib/components/HandwriteCanvas.svelte +++ b/frontend/src/lib/components/HandwriteCanvas.svelte @@ -373,11 +373,19 @@ // pointerup → 정상 finalize. pointercancel → inflight 폐기 (사용자 의도가 아닌 // OS 강제 취소 — multi-touch / 시스템 gesture 인식 시 발생. cancel 된 stroke 가 // strokes 에 들어가면 의도치 않은 짧은 노이즈 stroke 누적, 사용자 글자 망가짐). - // pointerleave 는 핸들러 미바인딩 — setPointerCapture 가 잡히면 leave 자체가 안 - // 오고, 캡처 실패 케이스는 OS 가 pointercancel 로 흘려보냄. + // pointerleave 는 안전망 — capture 가 정상 잡혀 있으면 leave 자체가 사양상 안 + // 오므로 무해. 만약 leave 가 도착했다면 iOS Safari 가 capture 를 silently 풀어 + // pointerup 이 캔버스에 routing 안 된 케이스 → 이 분기에서 강제 finalize 해야 + // isDrawing 락이 풀려서 다음 stroke 가 막히지 않는다 (ㄱ → ㅏ hang 회귀 방어). function endStroke(e: PointerEvent) { if (e.type === 'pointerup') dbg = { ...dbg, up: dbg.up + 1 }; else if (e.type === 'pointercancel') dbg = { ...dbg, cancel: dbg.cancel + 1 }; + else if (e.type === 'pointerleave') { + // capture 가 활성이면 leave 는 정상 흐름이 아님 — ignore (정상적으로 pointerup + // 이 곧 도착할 것). capture 가 풀렸을 때만 안전망으로 finalize 진행. + if (canvas?.hasPointerCapture?.(e.pointerId)) return; + dbg = { ...dbg, leave: dbg.leave + 1 }; + } if (!isDrawing) return; if (e.pointerId !== activePointerId) { dbg = { ...dbg, rejectedByPointerId: dbg.rejectedByPointerId + 1 }; @@ -620,6 +628,7 @@ onpointermove={onPointerMove} onpointerup={endStroke} onpointercancel={endStroke} + onpointerleave={endStroke} oncontextmenu={(e) => e.preventDefault()} onselectstart={(e) => e.preventDefault()} class="block" diff --git a/frontend/src/routes/study/write/[id]/+page.svelte b/frontend/src/routes/study/write/[id]/+page.svelte index 90206b7..9159b34 100644 --- a/frontend/src/routes/study/write/[id]/+page.svelte +++ b/frontend/src/routes/study/write/[id]/+page.svelte @@ -152,9 +152,12 @@ - - + +