Canvas在三星Mobile Chrome中无法绘制全部图片的问题排查
Canvas绘制异常问题(三星Mobile Chrome专属)
问题描述
仅在三星设备的Mobile Chrome浏览器中,Canvas无法绘制全部图片(仅部分图片能显示),其他设备无此问题。现象表现为部分图片可正常绘制,部分完全无法显示;偶尔还会出现已绘制的部分图片被替换的情况(因速度过快难以确认)。
相关代码
const drawSymbol = ( ctx: CanvasRenderingContext2D, symbol: CardSymbol, image: HTMLImageElement, ) => { window.requestAnimationFrame(() => { ctx.drawImage( image, symbol.x, symbol.y, symbol.width, symbol.height, ); }); }; useEffect(() => { const canvas = canvasRef.current; if (canvas) { const ctx = canvas.getContext('2d'); if (ctx) { ctx.clearRect(0, 0, canvas.width, canvas.height); for (const symbol of card) { const image = images[symbol.image.id]; if (image.loadedImg) { drawSymbol(ctx, symbol, image.loadedImg); } else { const img = new Image(); img.onload = () => { image.loadedImg = img; // Cache loaded image drawSymbol(ctx, symbol, img); }; img.src = image.src; } } } } }, [card]);
可能原因分析
- 分散的requestAnimationFrame回调冲突:当前每个图片绘制都单独触发
requestAnimationFrame,三星Mobile Chrome的渲染线程可能对高频异步回调存在调度限制,导致部分回调被丢弃或延迟,进而出现绘制不完整。 - Canvas上下文竞争:多个异步绘制回调共享同一个上下文,若
card更新触发新的useEffect,新的clearRect会覆盖还未执行的旧绘制操作,或者回调执行顺序混乱导致图片被替换。 - 三星Chrome的定制化渲染策略:三星定制的Chrome可能在图片加载优先级、Canvas资源分配上有特殊处理,分散的异步绘制请求无法被正确调度。
统一使用单个requestAnimationFrame的效果
使用单个requestAnimationFrame统一绘制所有图片大概率能解决问题,原因如下:
- 所有绘制操作集中在同一帧完成,避免了大量分散回调抢占渲染资源,保证绘制的原子性和顺序性。
- 确保
clearRect与所有绘制操作在同一帧执行,不会被后续useEffect的清理操作打断,消除上下文竞争问题。
优化代码示例
useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; // 收集所有绘制任务(已加载图片直接加入,未加载的转为Promise等待) const drawTasks: Promise<{symbol: CardSymbol, image: HTMLImageElement}>[] = []; for (const symbol of card) { const imageData = images[symbol.image.id]; if (imageData.loadedImg) { drawTasks.push(Promise.resolve({symbol, image: imageData.loadedImg})); } else { drawTasks.push(new Promise(resolve => { const img = new Image(); img.onload = () => { imageData.loadedImg = img; resolve({symbol, image: img}); }; img.src = imageData.src; })); } } // 等待所有图片就绪后,统一在一帧内完成绘制 Promise.all(drawTasks).then(tasks => { window.requestAnimationFrame(() => { ctx.clearRect(0, 0, canvas.width, canvas.height); tasks.forEach(({symbol, image}) => { ctx.drawImage(image, symbol.x, symbol.y, symbol.width, symbol.height); }); }); }); }, [card]);
内容的提问来源于stack exchange,提问作者TheE
相关产品推荐
相关产品推荐

