From 23c87c43e12e01d52b9f694ff40569a5d9a3e214 Mon Sep 17 00:00:00 2001 From: shawko Date: Tue, 12 May 2026 11:24:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=9B=91=E6=8E=A7=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E5=9B=BE=E7=89=87=E5=8E=8B=E7=BC=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useClassMonitor.ts | 83 ++++++++++++++++++++++++++++++++---- 1 file changed, 75 insertions(+), 8 deletions(-) diff --git a/src/hooks/useClassMonitor.ts b/src/hooks/useClassMonitor.ts index 6ac737d..d9deca0 100644 --- a/src/hooks/useClassMonitor.ts +++ b/src/hooks/useClassMonitor.ts @@ -15,6 +15,9 @@ const MONITOR_SNAPSHOT_INTERVAL = 10000 const MONITOR_MSG_TYPE = 'monitorStart' const MONITOR_STOP_MSG_TYPE = 'monitorStop' const MONITOR_LOG_PREFIX = '[class-monitor]' +const MONITOR_SNAPSHOT_TARGET_SIZE = 100 * 1024 +const MONITOR_SNAPSHOT_MIN_WIDTH = 480 +const MONITOR_SNAPSHOT_MIN_HEIGHT = 270 const monitorLog = (...args: unknown[]) => console.log(MONITOR_LOG_PREFIX, ...args) const monitorWarn = (...args: unknown[]) => console.warn(MONITOR_LOG_PREFIX, ...args) @@ -119,20 +122,84 @@ const prepareMonitorDomForCapture = async () => { }) } -const canvasToPngFile = (canvas: HTMLCanvasElement) => - new Promise(resolve => { - canvas.toBlob(blob => { - if (!blob) return resolve(null) - resolve(new File([blob], `monitor-${Date.now()}.png`, { type: 'image/png' })) - }, 'image/png') +const canvasToBlob = (canvas: HTMLCanvasElement, type: string, quality?: number) => + new Promise(resolve => { + canvas.toBlob( + blob => { + resolve(blob) + }, + type, + quality, + ) }) +const resizeCanvas = (source: HTMLCanvasElement, ratio: number) => { + const canvas = document.createElement('canvas') + canvas.width = Math.max(1, Math.round(source.width * ratio)) + canvas.height = Math.max(1, Math.round(source.height * ratio)) + const ctx = canvas.getContext('2d') + if (!ctx) return source + ctx.drawImage(source, 0, 0, canvas.width, canvas.height) + return canvas +} + +const canvasToMonitorFile = async (canvas: HTMLCanvasElement) => { + const qualities = [0.72, 0.6, 0.5, 0.42, 0.35, 0.3, 0.25] + let current = canvas + let bestBlob: Blob | null = null + let bestInfo = { + width: canvas.width, + height: canvas.height, + quality: qualities[0], + } + + for (let resizeCount = 0; resizeCount < 8; resizeCount++) { + for (const quality of qualities) { + const blob = await canvasToBlob(current, 'image/jpeg', quality) + if (!blob) return null + if (!bestBlob || blob.size < bestBlob.size) { + bestBlob = blob + bestInfo = { + width: current.width, + height: current.height, + quality, + } + } + if (blob.size <= MONITOR_SNAPSHOT_TARGET_SIZE) { + monitorLog('web screenshot compressed', { + width: current.width, + height: current.height, + quality, + size: blob.size, + }) + return new File([blob], `monitor-${Date.now()}.jpg`, { type: 'image/jpeg' }) + } + } + + if ( + current.width <= MONITOR_SNAPSHOT_MIN_WIDTH || + current.height <= MONITOR_SNAPSHOT_MIN_HEIGHT + ) { + break + } + current = resizeCanvas(current, 0.85) + } + + if (!bestBlob) return null + monitorWarn('web screenshot compressed but still over target', { + ...bestInfo, + size: bestBlob.size, + target: MONITOR_SNAPSHOT_TARGET_SIZE, + }) + return new File([bestBlob], `monitor-${Date.now()}.jpg`, { type: 'image/jpeg' }) +} + const createWebScreenshotFile = async (): Promise => { await prepareMonitorDomForCapture() const vw = window.innerWidth const vh = window.innerHeight - const scale = Math.min(window.devicePixelRatio || 1, 2) + const scale = Math.min(window.devicePixelRatio || 1, 1) monitorLog('web screenshot start', { href: window.location.href, viewport: `${vw}x${vh}`, @@ -200,7 +267,7 @@ const createWebScreenshotFile = async (): Promise => { monitorWarn('web screenshot canvas empty', index + 1) continue } - const file = await canvasToPngFile(canvas) + const file = await canvasToMonitorFile(canvas) if (file) { monitorLog('web screenshot file ready', { attempt: index + 1,