Angular Canvas移动端截图优化:Android设备4张PNG截图耗时过长求助
我在Angular项目中使用Canvas捕获4张PNG图片,通过npm run build:widgets命令将项目打包为包含HTML、CSS、JS文件的Widget,部署至S3后通过Android或iPhone的浏览器访问。在iPhone上能按预期以50毫秒的间隔完成4张图片的捕获,但Android设备捕获全部4张图片耗时超过5秒。请问如何针对Android移动端进行优化,或是我应该采用其他方案?
相关代码
HTML
<canvas class="d-none"></canvas> <button type="button" (click)="captureImage()"> CaptureImage </button>
TypeScript
canvas: any = document.querySelector('canvas'); /** * Function to capture image */ async captureImage() { this.loader.emit(true); // 5 milliseconds timeout added here to resolve the video loading issue in android phones setTimeout(()=>{ this.isCapturing = true; this.getAttributes(); this.canvas.width = (this.video.videoWidth); this.canvas.height = (this.video.videoHeight); this.imagesArray = []; this.phoneAnglesArray = []; // minimum resolution 1920 * 1440 /* if (this.video.videoWidth < 1440 || this.video.videoHeight < 1920) { this.resolutionError.show(); } */ if (this.video.videoWidth < 640 || this.video.videoHeight < 480) { this.errorMsg = ["The camera does not support the resolution required for calculation"]; this.showError.show(); // alert("resolutionError"); } else { try { let { height, width, benchmark } = canvasSize.maxArea({usePromise: true }).catch(err => { console.log(err)}) let canvasHeight = this.canvas.height; let canvasWidth = this.canvas.width; if(height) { canvasHeight = this.canvas.height > height ? height: this.canvas.height } if(width) { canvasWidth = this.canvas.width > width ? width: this.canvas.width } this.canvas.getContext('2d').drawImage(this.video, 0, 0, this.video.videoWidth, this.video.videoHeight, 0, 0, canvasWidth, canvasHeight); this.phoneAnglesArray.push({ x : JSON.parse(JSON.stringify(Math.floor(this.phoneAngle.z))), y : JSON.parse(JSON.stringify(Math.floor(this.phoneAngle.y))) }); console.log(this.canvas.toDataURL('image/png')); if (this.canvas.toDataURL('image/png')) { this.imagesArray.push(this.canvas.toDataURL('image/png')); } else { this.imagesArray.push(null); } this.captureImageLoop(); } catch (err) { console.log("Error drawing image") } } }, 5); }; /** * Function to capture images in every 50 milliseconds interval for 3 times */ captureImageLoop() { let i = 0; const captureInterval = setInterval(() => { i++ let { height, width, benchmark } = canvasSize.maxArea({usePromise: true }).catch(err => { console.log(err)}); let canvasHeight = this.canvas.height; let canvasWidth = this.canvas.width; if(height) { canvasHeight = this.canvas.height > height ? height: this.canvas.height } if(width) { canvasWidth = this.canvas.width > width ? width: this.canvas.width } this.phoneAnglesArray.push({ x : JSON.parse(JSON.stringify(Math.floor(this.phoneAngle.z))), y : JSON.parse(JSON.stringify(Math.floor(this.phoneAngle.y))) }); this.canvas.getContext('2d').drawImage(this.video, 0, 0, this.video.videoWidth, this.video.videoHeight, 0, 0, canvasWidth, canvasHeight); console.log(this.canvas.toDataURL('image/png')); if (this.canvas.toDataURL('image/png')) { this.imagesArray.push(this.canvas.toDataURL('image/png')); } else { this.imagesArray.push(null); } if (i == this.captureCount) { i=0; clearInterval(captureInterval); console.log('this.imagesArray', this.imagesArray.length, this.phoneAnglesArray.length, this.phoneAnglesArray); this.getMeasurements(this.imagesArray, this.phoneAnglesArray); this.isCapturing = false; } }, 50); }
优化方案
1. 缓存Canvas上下文,避免重复获取
每次截图调用getContext('2d')会产生额外性能开销,建议初始化时获取一次并缓存:
ctx: CanvasRenderingContext2D | null = null; ngAfterViewInit() { this.canvas = document.querySelector('canvas'); this.ctx = this.canvas?.getContext('2d'); }
后续截图直接使用this.ctx调用drawImage方法。
2. 预计算Canvas最大尺寸,避免重复调用
当前每次截图都异步调用canvasSize.maxArea却用同步方式取值,既无法获取正确结果,又重复计算浪费资源。建议初始化时预计算一次并复用:
maxCanvasWidth = 0; maxCanvasHeight = 0; async ngAfterViewInit() { try { const { height, width } = await canvasSize.maxArea({usePromise: true}); this.maxCanvasWidth = width; this.maxCanvasHeight = height; } catch(err) { console.log(err); } }
截图时直接使用缓存的maxCanvasWidth和maxCanvasHeight。
3. 减少toDataURL重复调用
toDataURL('image/png')是Android端性能瓶颈之一,当前每次截图调用两次,改为只调用一次并缓存结果:
const imageDataUrl = this.canvas.toDataURL('image/png'); console.log(imageDataUrl); this.imagesArray.push(imageDataUrl || null);
4. 用requestAnimationFrame替代setInterval
setInterval在主线程繁忙时会延迟执行,requestAnimationFrame与浏览器渲染帧同步,能保证截图间隔更稳定:
captureImageLoop() { let i = 0; const captureFrame = () => { // 复用预计算的尺寸 let canvasHeight = this.canvas.height; let canvasWidth = this.canvas.width; if(this.maxCanvasHeight) { canvasHeight = this.canvas.height > this.maxCanvasHeight ? this.maxCanvasHeight : this.canvas.height } if(this.maxCanvasWidth) { canvasWidth = this.canvas.width > this.maxCanvasWidth ? this.maxCanvasWidth : this.canvas.width } this.phoneAnglesArray.push({ x : Math.floor(this.phoneAngle.z), y : Math.floor(this.phoneAngle.y) }); this.ctx?.drawImage(this.video, 0, 0, this.video.videoWidth, this.video.videoHeight, 0, 0, canvasWidth, canvasHeight); const imageDataUrl = this.canvas.toDataURL('image/png'); console.log(imageDataUrl); this.imagesArray.push(imageDataUrl || null); i++; if (i < this.captureCount) { setTimeout(() => requestAnimationFrame(captureFrame), 50); } else { console.log('this.imagesArray', this.imagesArray.length, this.phoneAnglesArray.length, this.phoneAnglesArray); this.getMeasurements(this.imagesArray, this.phoneAnglesArray); this.isCapturing = false; } }; requestAnimationFrame(captureFrame); }
5. 动态降低Canvas分辨率(业务允许的情况下)
高分辨率Canvas的绘制和转码会大幅增加Android设备的负载,可根据设备性能动态缩小尺寸:
const scale = 0.5; // 可根据设备调整 this.canvas.width = Math.floor(this.video.videoWidth * scale); this.canvas.height = Math.floor(this.video.videoHeight * scale); this.ctx?.drawImage(this.video, 0, 0, this.video.videoWidth, this.video.videoHeight, 0, 0, this.canvas.width, this.canvas.height);
6. 替代方案:使用WebCodecs API
如果目标Android浏览器支持WebCodecs API(Chrome 94+),可以直接从视频流中提取帧,跳过Canvas的中间步骤,性能会大幅提升。WebCodecs专门用于高效处理音视频帧,适合连续截图场景。
内容的提问来源于stack exchange,提问作者Rajagopal Ganesan

