Files
vexplor_dev/frontend/components/messenger/ScreenCapture.tsx
syc0123 eaa47ee5df [RAPID-fix] 메신저 3가지 수정
- 최신 메시지 버튼: 스크롤 컨테이너 밖으로 이동, 입력창 위 중앙 고정
- 스크롤 하단: rAF + 600ms 지연 2회 (이미지 비동기 로드 대응)
- 캡처: 버튼 클릭 즉시 오버레이 + domToPng 병렬 실행, mouseup에서 await (font:false 최적화)
2026-03-31 15:49:03 +09:00

99 lines
3.1 KiB
TypeScript

"use client";
import { useEffect, useRef, useState } from "react";
interface Rect {
x: number;
y: number;
w: number;
h: number;
}
interface ScreenCaptureProps {
capturePromise: Promise<HTMLImageElement>;
onCapture: (file: File) => void;
onCancel: () => void;
}
export function ScreenCapture({ capturePromise, onCapture, onCancel }: ScreenCaptureProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const [selecting, setSelecting] = useState(false);
const startRef = useRef<{ x: number; y: number } | null>(null);
const [rect, setRect] = useState<Rect | null>(null);
useEffect(() => {
const handler = (e: KeyboardEvent) => { if (e.key === "Escape") onCancel(); };
window.addEventListener("keydown", handler);
return () => window.removeEventListener("keydown", handler);
}, [onCancel]);
const getRect = (ax: number, ay: number, bx: number, by: number): Rect => ({
x: Math.min(ax, bx),
y: Math.min(ay, by),
w: Math.abs(bx - ax),
h: Math.abs(by - ay),
});
const handleMouseDown = (e: React.MouseEvent) => {
startRef.current = { x: e.clientX, y: e.clientY };
setSelecting(true);
setRect(null);
};
const handleMouseMove = (e: React.MouseEvent) => {
if (!selecting || !startRef.current) return;
setRect(getRect(startRef.current.x, startRef.current.y, e.clientX, e.clientY));
};
const handleMouseUp = async (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; }
try {
const img = await capturePromise;
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);
canvas.toBlob((blob) => {
if (!blob) { onCancel(); return; }
onCapture(new File([blob], `capture-${Date.now()}.png`, { type: "image/png" }));
}, "image/png");
} catch {
onCancel();
}
};
return (
<>
<canvas ref={canvasRef} className="hidden" />
<div
className="fixed inset-0 bg-black/40 select-none cursor-crosshair"
style={{ zIndex: 99999 }}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
>
<div className="absolute top-4 left-1/2 -translate-x-1/2 bg-black/70 text-white text-sm px-4 py-2 rounded-full pointer-events-none">
&nbsp;·&nbsp;ESC로
</div>
{rect && rect.w > 0 && rect.h > 0 && (
<div
className="absolute border-2 border-blue-400 bg-blue-400/10 pointer-events-none"
style={{ left: rect.x, top: rect.y, width: rect.w, height: rect.h }}
/>
)}
</div>
</>
);
}