From 5ce99d870867ad99406d046b87d43f55e9dd5bc0 Mon Sep 17 00:00:00 2001 From: wdvipa Date: Sun, 15 Feb 2026 18:41:47 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20Web=E7=AB=AF=E6=8A=95=E5=B1=8F=E7=94=BB?= =?UTF-8?q?=E9=9D=A2=E7=BC=A9=E5=B0=8F=E9=97=AA=E7=83=81=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - canvas尺寸锁定策略:首次有效帧锁定canvas尺寸,后续帧统一drawImage缩放绘制 - 消除不同采集模式(MediaProjection/无障碍截图)帧尺寸不一致导致的canvas反复resize - 设备切换或断开时重置锁定尺寸,下次连接重新锁定 - 移除renderLatestFrame对screenDisplay.fitMode的无效依赖 --- src/components/Device/DeviceScreen.tsx | 72 ++++++++++---------------- 1 file changed, 27 insertions(+), 45 deletions(-) diff --git a/src/components/Device/DeviceScreen.tsx b/src/components/Device/DeviceScreen.tsx index 5b89219..1ea81f8 100644 --- a/src/components/Device/DeviceScreen.tsx +++ b/src/components/Device/DeviceScreen.tsx @@ -38,10 +38,9 @@ const DeviceScreen: React.FC = ({ deviceId, onScreenSizeChang const isRenderingRef = useRef(false) const imageSizeRef = useRef<{ width: number, height: number } | null>(null) - // ✅ 尺寸稳定性:防止偶发异常帧导致canvas闪烁 - const pendingSizeRef = useRef<{ width: number, height: number } | null>(null) - const pendingSizeCountRef = useRef(0) - const SIZE_STABLE_THRESHOLD = 3 // 连续3帧相同尺寸才更新 + // canvas锁定尺寸:首次有效帧确定canvas尺寸后不再变化, + // 避免不同采集模式(MediaProjection/无障碍截图)帧尺寸不一致导致闪烁 + const lockedCanvasSizeRef = useRef<{ width: number, height: number } | null>(null) // ✅ 添加控制权状态跟踪,避免重复申请 const [isControlRequested, setIsControlRequested] = useState(false) @@ -172,6 +171,8 @@ const DeviceScreen: React.FC = ({ deviceId, onScreenSizeChang rafIdRef.current = 0 } isRenderingRef.current = false + // 设备切换或断开时重置锁定尺寸,下次连接重新锁定 + lockedCanvasSizeRef.current = null } }, [webSocket, deviceId]) @@ -303,52 +304,33 @@ const DeviceScreen: React.FC = ({ deviceId, onScreenSizeChang return } - // canvas尺寸匹配bitmap,只在尺寸真正变化时才设置 - // 注意:设置canvas.width/height会清空画布内容,所以必须紧接着绘制新帧 - const needsResize = canvas.width !== bitmap.width || canvas.height !== bitmap.height - if (needsResize) { + // canvas尺寸锁定策略: + // 首次有效帧确定canvas尺寸后锁定,后续帧直接绘制到固定尺寸canvas上 + // 避免不同采集模式帧尺寸不一致导致canvas反复resize闪烁 + const locked = lockedCanvasSizeRef.current + if (!locked) { + // 首次帧:锁定canvas尺寸 + lockedCanvasSizeRef.current = { width: bitmap.width, height: bitmap.height } canvas.width = bitmap.width canvas.height = bitmap.height + } else if (canvas.width !== locked.width || canvas.height !== locked.height) { + // canvas被外部重置了,恢复锁定尺寸 + canvas.width = locked.width + canvas.height = locked.height } - switch (screenDisplay.fitMode) { - case 'fit': - drawFitMode(ctx, bitmap, canvas) - break - case 'fill': - drawFillMode(ctx, bitmap, canvas) - break - case 'stretch': - drawStretchMode(ctx, bitmap, canvas) - break - case 'original': - drawOriginalMode(ctx, bitmap, canvas) - break - } + // 始终将bitmap绘制到整个canvas区域,浏览器自动缩放 + // 这样不同尺寸的帧都能正确显示,不会闪烁 + ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height) - const bw = bitmap.width - const bh = bitmap.height - const prevSize = imageSizeRef.current - if (!prevSize || prevSize.width !== bw || prevSize.height !== bh) { - // 尺寸稳定性检查:只有连续多帧相同尺寸才更新,防止偶发异常帧闪烁 - const pending = pendingSizeRef.current - if (pending && pending.width === bw && pending.height === bh) { - pendingSizeCountRef.current++ - } else { - pendingSizeRef.current = { width: bw, height: bh } - pendingSizeCountRef.current = 1 + // 使用锁定的canvas尺寸上报,保持稳定 + const reportSize = lockedCanvasSizeRef.current + if (reportSize) { + const prevSize = imageSizeRef.current + if (!prevSize || prevSize.width !== reportSize.width || prevSize.height !== reportSize.height) { + imageSizeRef.current = { width: reportSize.width, height: reportSize.height } + setImageSize({ width: reportSize.width, height: reportSize.height }) } - - if (pendingSizeCountRef.current >= SIZE_STABLE_THRESHOLD || !prevSize) { - imageSizeRef.current = { width: bw, height: bh } - setImageSize({ width: bw, height: bh }) - pendingSizeRef.current = null - pendingSizeCountRef.current = 0 - } - } else { - // 尺寸未变,重置pending - pendingSizeRef.current = null - pendingSizeCountRef.current = 0 } const now = Date.now() @@ -373,7 +355,7 @@ const DeviceScreen: React.FC = ({ deviceId, onScreenSizeChang } doRender() - }, [screenDisplay.fitMode]) + }, []) const drawFitMode = (ctx: CanvasRenderingContext2D, img: HTMLImageElement | ImageBitmap, canvas: HTMLCanvasElement) => { const scale = Math.min(canvas.width / img.width, canvas.height / img.height)