From 2701edf87abafe0d8ce6d68fa87eecbc77f6d2d5 Mon Sep 17 00:00:00 2001 From: syc0123 Date: Tue, 31 Mar 2026 15:18:36 +0900 Subject: [PATCH] =?UTF-8?q?[RAPID-fix]=20=EC=BA=A1=EC=B2=98=20=EC=86=8D?= =?UTF-8?q?=EB=8F=84/=ED=99=94=EC=A7=88=20=EA=B0=9C=EC=84=A0=20+=20?= =?UTF-8?q?=EB=93=9C=EB=9E=98=EA=B7=B8=20=EC=BB=A4=EC=84=9C=204=EB=B0=A9?= =?UTF-8?q?=ED=96=A5=20=ED=99=94=EC=82=B4=ED=91=9C=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 오버레이 마운트 시점에 미리 캡처 시작 → mouseup 즉시 크롭 - scale: max(dpr, 2)로 화질 2배 향상 - 캡처 준비 중 wait 커서 표시 - 메신저 헤더 드래그 커서 cursor-grab → cursor-move (4방향 화살표) --- .../components/messenger/MessengerModal.tsx | 2 +- .../components/messenger/ScreenCapture.tsx | 94 ++++++++++--------- 2 files changed, 52 insertions(+), 44 deletions(-) diff --git a/frontend/components/messenger/MessengerModal.tsx b/frontend/components/messenger/MessengerModal.tsx index 4e0ce1b8..b0bed09d 100644 --- a/frontend/components/messenger/MessengerModal.tsx +++ b/frontend/components/messenger/MessengerModal.tsx @@ -189,7 +189,7 @@ export function MessengerModal() { {/* Header */}

메신저

diff --git a/frontend/components/messenger/ScreenCapture.tsx b/frontend/components/messenger/ScreenCapture.tsx index 2b1901df..4d62518d 100644 --- a/frontend/components/messenger/ScreenCapture.tsx +++ b/frontend/components/messenger/ScreenCapture.tsx @@ -16,10 +16,37 @@ interface ScreenCaptureProps { export function ScreenCapture({ onCapture, onCancel }: ScreenCaptureProps) { const canvasRef = useRef(null); + const capturedImgRef = useRef(null); + const [ready, setReady] = useState(false); const [selecting, setSelecting] = useState(false); const startRef = useRef<{ x: number; y: number } | null>(null); const [rect, setRect] = useState(null); + // Pre-capture on mount so mouseup is instant + useEffect(() => { + let cancelled = false; + (async () => { + try { + const { domToPng } = await import("modern-screenshot"); + const scale = Math.max(window.devicePixelRatio || 1, 2); + const dataUrl = await domToPng(document.body, { + width: window.innerWidth, + height: window.innerHeight, + scale, + }); + if (cancelled) return; + const img = new Image(); + img.src = dataUrl; + await new Promise((res) => { img.onload = res; }); + capturedImgRef.current = img; + setReady(true); + } catch { + if (!cancelled) onCancel(); + } + })(); + return () => { cancelled = true; }; + }, [onCancel]); + // ESC to cancel useEffect(() => { const handler = (e: KeyboardEvent) => { @@ -47,70 +74,51 @@ export function ScreenCapture({ onCapture, onCancel }: ScreenCaptureProps) { setRect(getRect(startRef.current.x, startRef.current.y, e.clientX, e.clientY)); }; - const handleMouseUp = async (e: React.MouseEvent) => { + const handleMouseUp = (e: React.MouseEvent) => { if (!selecting || !startRef.current) return; setSelecting(false); const r = getRect(startRef.current.x, startRef.current.y, e.clientX, e.clientY); startRef.current = null; - if (r.w < 4 || r.h < 4) { - onCancel(); - return; - } + if (r.w < 4 || r.h < 4) { onCancel(); return; } - // Capture via modern-screenshot - try { - const { domToPng } = await import("modern-screenshot"); - const dataUrl = await domToPng(document.body, { - width: window.innerWidth, - height: window.innerHeight, - }); + const img = capturedImgRef.current; + if (!img) { onCancel(); return; } - // Crop the captured region — image is at CSS pixel scale - const img = new Image(); - img.src = dataUrl; - await new Promise((res) => { img.onload = res; }); + const scaleX = img.naturalWidth / window.innerWidth; + const scaleY = img.naturalHeight / window.innerHeight; - // Scale factor between actual image size and CSS pixels - const scaleX = img.naturalWidth / window.innerWidth; - const scaleY = img.naturalHeight / window.innerHeight; + const canvas = canvasRef.current!; + canvas.width = r.w * scaleX; + canvas.height = r.h * scaleY; + const ctx = canvas.getContext("2d")!; + ctx.drawImage( + img, + r.x * scaleX, r.y * scaleY, r.w * scaleX, r.h * scaleY, + 0, 0, r.w * scaleX, r.h * scaleY, + ); - const canvas = canvasRef.current!; - canvas.width = r.w * scaleX; - canvas.height = r.h * scaleY; - const ctx = canvas.getContext("2d")!; - ctx.drawImage( - img, - r.x * scaleX, r.y * scaleY, r.w * scaleX, r.h * scaleY, - 0, 0, r.w * scaleX, r.h * scaleY, - ); - - canvas.toBlob((blob) => { - if (!blob) { onCancel(); return; } - const file = new File([blob], `capture-${Date.now()}.png`, { type: "image/png" }); - onCapture(file); - }, "image/png"); - } catch { - onCancel(); - } + canvas.toBlob((blob) => { + if (!blob) { onCancel(); return; } + const file = new File([blob], `capture-${Date.now()}.png`, { type: "image/png" }); + onCapture(file); + }, "image/png"); }; return ( <>
- {/* instruction */}
- 드래그하여 캡처 영역을 선택하세요  ·  ESC로 취소 + {ready ? "드래그하여 캡처 영역을 선택하세요" : "캡처 준비 중..."} · ESC로 취소
- {/* selection rect */} {rect && rect.w > 0 && rect.h > 0 && (