如何在单个canvas上绘制多张图片,解决后图覆盖前图的问题
问题原因
你遇到的是异步回调变量作用域污染问题:
- 循环中用
var声明的tx/ty/tw/th属于函数级作用域变量,每次循环迭代都会覆盖旧值 img.onload是异步触发的回调,等图片加载完成执行绘制逻辑时,整个for循环早已执行完毕,此时所有回调读取到的坐标/尺寸都是循环最后一次迭代的值,所有图片都被绘制到同一个位置,就出现了后绘制的图覆盖之前的图的现象。
解决方案
方案1:使用块级作用域声明变量(最简单)
将循环内的变量声明从var改为let,let具备块级作用域,每次循环迭代都会生成独立的变量副本,回调会捕获当前迭代的变量值,不会被后续迭代覆盖:
var canvas = document.getElementById("paint"); var ctx = canvas.getContext("2d"); function loadImages(data, targetX, targetY, targetWidth, targetHeight) { data = data.replace(/'/g, '"'); targetX = targetX.replace(/'/g, '"'); targetY = targetY.replace(/'/g, '"'); targetWidth = targetWidth.replace(/'/g, '"'); targetHeight = targetHeight.replace(/'/g, '"'); data = JSON.parse(data); targetX = JSON.parse(targetX); targetY = JSON.parse(targetY); targetWidth = JSON.parse(targetWidth); targetHeight = JSON.parse(targetHeight); for(var i = 0; i < data.length; i++) { // 用let声明块级作用域变量 let img = new Image(); let tx = parseInt(targetX[i]); let ty = parseInt(targetY[i]); let tw = parseInt(targetWidth[i]); let th = parseInt(targetHeight[i]); img.src = data[i]; img.onload = function() { ctx.drawImage(this, tx, ty, tw, th); }; } }
方案2:保证绘制顺序与数组顺序一致
如果对图片绘制顺序有严格要求(比如底层图必须先画,上层图后画),因为不同图片加载速度不同,可能出现后定义的图片先加载完成先绘制的问题,可以先预加载所有图片,再按顺序统一绘制:
var canvas = document.getElementById("paint"); var ctx = canvas.getContext("2d"); async function loadImages(data, targetX, targetY, targetWidth, targetHeight) { data = data.replace(/'/g, '"'); targetX = targetX.replace(/'/g, '"'); targetY = targetY.replace(/'/g, '"'); targetWidth = targetWidth.replace(/'/g, '"'); targetHeight = targetHeight.replace(/'/g, '"'); data = JSON.parse(data); targetX = JSON.parse(targetX); targetY = JSON.parse(targetY); targetWidth = JSON.parse(targetWidth); targetHeight = JSON.parse(targetHeight); // 预加载所有图片 const loadTasks = data.map((src, index) => { return new Promise((resolve) => { const img = new Image(); img.onload = () => resolve({ img, tx: parseInt(targetX[index]), ty: parseInt(targetY[index]), tw: parseInt(targetWidth[index]), th: parseInt(targetHeight[index]) }); img.src = src; }) }) // 等待所有图片加载完成后按数组顺序绘制 const imageList = await Promise.all(loadTasks); imageList.forEach(item => { ctx.drawImage(item.img, item.tx, item.ty, item.tw, item.th); }) }
内容的提问来源于stack exchange,提问作者Ravish Jha
相关产品推荐
相关产品推荐

