[RAPID-fix] 스크롤/캡처 3가지 수정
- 스크롤 하단 이동: useLayoutEffect → useEffect+rAF (이미지 레이아웃 완료 후 스크롤) - 스크롤 버튼: 리스너 deps를 room.id로 변경 (room 없을 때 미연결 문제 해결) - 캡처 속도: domToPng(느림) → getDisplayMedia 네이티브 API(즉시 캡처)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { MessageSquare, Pencil, Check, X, ChevronsDown } from "lucide-react";
|
||||
import { useMessages, useMarkAsRead, useUpdateRoom } from "@/hooks/useMessenger";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
@@ -38,11 +38,16 @@ export function ChatPanel({ room, messageInputRef, onCaptureClick }: ChatPanelPr
|
||||
|
||||
const lastMessageId = messages?.[messages.length - 1]?.id;
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const el = scrollRef.current;
|
||||
if (el) el.scrollTop = el.scrollHeight;
|
||||
// Scroll to bottom when messages load or room changes
|
||||
// useEffect + rAF to let images/content finish laying out first
|
||||
useEffect(() => {
|
||||
requestAnimationFrame(() => {
|
||||
const el = scrollRef.current;
|
||||
if (el) el.scrollTop = el.scrollHeight;
|
||||
});
|
||||
}, [lastMessageId, selectedRoomId]);
|
||||
|
||||
// Re-attach scroll listener whenever room changes (scrollRef mounts after room is set)
|
||||
useEffect(() => {
|
||||
const el = scrollRef.current;
|
||||
if (!el) return;
|
||||
@@ -51,7 +56,7 @@ export function ChatPanel({ room, messageInputRef, onCaptureClick }: ChatPanelPr
|
||||
};
|
||||
el.addEventListener("scroll", onScroll);
|
||||
return () => el.removeEventListener("scroll", onScroll);
|
||||
}, []);
|
||||
}, [room?.id]);
|
||||
|
||||
if (!room) {
|
||||
return (
|
||||
|
||||
@@ -41,20 +41,29 @@ export function MessengerModal() {
|
||||
|
||||
const handleCaptureClick = useCallback(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,
|
||||
const stream = await (navigator.mediaDevices as any).getDisplayMedia({
|
||||
video: true,
|
||||
preferCurrentTab: true,
|
||||
});
|
||||
const video = document.createElement("video");
|
||||
video.srcObject = stream;
|
||||
await new Promise<void>((res) => { video.onloadedmetadata = () => res(); });
|
||||
await video.play();
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = video.videoWidth;
|
||||
canvas.height = video.videoHeight;
|
||||
canvas.getContext("2d")!.drawImage(video, 0, 0);
|
||||
stream.getTracks().forEach((t: MediaStreamTrack) => t.stop());
|
||||
|
||||
const img = new Image();
|
||||
img.src = dataUrl;
|
||||
await new Promise((res) => { img.onload = res; });
|
||||
img.src = canvas.toDataURL("image/png");
|
||||
await new Promise<void>((res) => { img.onload = () => res(); });
|
||||
|
||||
capturedImgRef.current = img;
|
||||
setCapturing(true);
|
||||
} catch {
|
||||
// fail silently
|
||||
// user cancelled
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user