如何在画布上分层绘制图像



我有一个画布,在那里我使用drawImage在画布上绘制一堆图像。

我希望结果是:

我希望我画的第一张图像在第1层,下一张图像在2层,以此类推

实际情况:

图像被放置在随机层上。

const images = [
'https://attefallsverket.picarioxpo.com/1_series_base.jpg?1=1&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_housebase.png?1=1&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_facade_roof_panels.pfs?1=1&p.c=&p.tn=wooden_summer_green.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_windows.pfs?1=1&p.c=71343a&p.tn=&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_door_01.pfs?1=1&p.c=&p.tn=rainsystem_grey.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_facade_01.pfs?1=1&p.c=&p.tn=wooden_summer_green.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_facade_panels.pfs?1=1&p.c=&p.tn=wooden_summer_green.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_facade_corners.pfs?1=1&p.c=&p.tn=wooden_summer_green.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_tin_windows.pfs?1=1&p.c=&p.tn=rainsystem_white.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_tin_roof.pfs?1=1&p.c=&p.tn=rainsystem_white.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_roof_metal_orange.png?1=1&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_rain_system.pfs?1=1&p.c=&p.tn=rainsystem_white.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1_series_terrace.png?1=1&width=2000',
];
let c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
for(let i=0; i<images.length; i++) {
let img = new Image();
img.crossOrigin = '';
img.src = images[i]
img.onload = () => {
ctx.drawImage(img, 0, 0, c.width, c.height);
}
}
<canvas id="myCanvas" width="280" height="157.5" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>

在启动下一个映像的加载之前,您需要确保已加载第一个映像。所以做一个异步循环:

const images = [
'https://attefallsverket.picarioxpo.com/1_series_base.jpg?1=1&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_housebase.png?1=1&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_facade_roof_panels.pfs?1=1&p.c=&p.tn=wooden_summer_green.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_windows.pfs?1=1&p.c=71343a&p.tn=&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_door_01.pfs?1=1&p.c=&p.tn=rainsystem_grey.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_facade_01.pfs?1=1&p.c=&p.tn=wooden_summer_green.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_facade_panels.pfs?1=1&p.c=&p.tn=wooden_summer_green.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_facade_corners.pfs?1=1&p.c=&p.tn=wooden_summer_green.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_tin_windows.pfs?1=1&p.c=&p.tn=rainsystem_white.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_tin_roof.pfs?1=1&p.c=&p.tn=rainsystem_white.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_roof_metal_orange.png?1=1&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_rain_system.pfs?1=1&p.c=&p.tn=rainsystem_white.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1_series_terrace.png?1=1&width=2000',
];
let c = document.getElementById("myCanvas");
let ctx = c.getContext("2d");
(function loop(i) {
if (i >= images.length) return; // all done
let img = new Image();
img.crossOrigin = '';
img.onload = () => {
ctx.drawImage(img, 0, 0, c.width, c.height);
loop(i+1); // continue with next...
}
img.src = images[i];
})(0); // start loop with first image
<canvas id="myCanvas" width="280" height="157.5"</canvas>

现有的答案可以解决问题,但它们是序列化的,不会同时发出请求。如果你想优化屏幕上的内容,而不在乎需要多长时间才能达到完整的图像,这是可以的,但如果你的目标是尽快绘制整个图像和/或不显示部分完成的图像,那么逐个网络请求是次优的。

取而代之的是:

request image 0
wait for request 0 over the wire or file IO
draw image 0
request image 1
wait for request 1 over the wire or file IO
draw image 1
...
request image n
wait for request n over the wire or file IO
draw image n

这可能有意义:

request/load all images at once
wait for all images to be received
draw all images in order

其思想是利用并行性,只等待一个映像(最慢的(到达,在最慢的加载时间内与所有其他请求重叠,而不是一次加载一个所有n映像。

实现这一点的一个好方法是承诺。您可以将onloadonerror分别回调到resolvereject,然后使用Promise.all等待所有图像到达,此时您可以应用传统的同步循环按顺序绘制图层。

const images = ['https://attefallsverket.picarioxpo.com/1_series_base.jpg?1=1&width=2000','https://attefallsverket.picarioxpo.com/1kp_housebase.png?1=1&width=2000','https://attefallsverket.picarioxpo.com/1kp_facade_roof_panels.pfs?1=1&p.c=&p.tn=wooden_summer_green.jpg&width=2000','https://attefallsverket.picarioxpo.com/1kp_windows.pfs?1=1&p.c=71343a&p.tn=&width=2000','https://attefallsverket.picarioxpo.com/1kp_door_01.pfs?1=1&p.c=&p.tn=rainsystem_grey.jpg&width=2000','https://attefallsverket.picarioxpo.com/1kp_facade_01.pfs?1=1&p.c=&p.tn=wooden_summer_green.jpg&width=2000','https://attefallsverket.picarioxpo.com/1kp_facade_panels.pfs?1=1&p.c=&p.tn=wooden_summer_green.jpg&width=2000','https://attefallsverket.picarioxpo.com/1kp_facade_corners.pfs?1=1&p.c=&p.tn=wooden_summer_green.jpg&width=2000','https://attefallsverket.picarioxpo.com/1kp_tin_windows.pfs?1=1&p.c=&p.tn=rainsystem_white.jpg&width=2000','https://attefallsverket.picarioxpo.com/1kp_tin_roof.pfs?1=1&p.c=&p.tn=rainsystem_white.jpg&width=2000','https://attefallsverket.picarioxpo.com/1kp_roof_metal_orange.png?1=1&width=2000','https://attefallsverket.picarioxpo.com/1kp_rain_system.pfs?1=1&p.c=&p.tn=rainsystem_white.jpg&width=2000','https://attefallsverket.picarioxpo.com/1_series_terrace.png?1=1&width=2000',];
const c = document.getElementById("myCanvas");
const ctx = c.getContext("2d");
Promise.all(images.map(url =>
new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = "";
img.onerror = e => reject(`${url} failed to load`);
img.onload = function () { 
resolve(this);
};
img.src = url;
})))
.then(images =>
images.forEach(e =>
ctx.drawImage(e, 0, 0, c.width, c.height)
)
)
.catch(err => console.error(err))
;
<canvas id="myCanvas" width="280" height="157.5" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>

如果你的目标是尽快在屏幕上看到一些东西,你可以将这两种方法结合起来,对后台进行一个快速的串行请求,然后并行甚至分批进行其余的请求。但对于这起案件来说,这感觉有些过头了;我提到了完整性的技巧。

以下是您真正想要做的事情:

addEventListener('load', ()=>{ // page and script load
const images = [
'https://attefallsverket.picarioxpo.com/1_series_base.jpg?1=1&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_housebase.png?1=1&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_facade_roof_panels.pfs?1=1&p.c=&p.tn=wooden_summer_green.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_windows.pfs?1=1&p.c=71343a&p.tn=&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_door_01.pfs?1=1&p.c=&p.tn=rainsystem_grey.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_facade_01.pfs?1=1&p.c=&p.tn=wooden_summer_green.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_facade_panels.pfs?1=1&p.c=&p.tn=wooden_summer_green.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_facade_corners.pfs?1=1&p.c=&p.tn=wooden_summer_green.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_tin_windows.pfs?1=1&p.c=&p.tn=rainsystem_white.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_tin_roof.pfs?1=1&p.c=&p.tn=rainsystem_white.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_roof_metal_orange.png?1=1&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_rain_system.pfs?1=1&p.c=&p.tn=rainsystem_white.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1_series_terrace.png?1=1&width=2000'
];
const canvas = document.getElementById('myCanvas'), ctx = canvas.getContext('2d'), promises = [];
let w = canvas.width, h = canvas.height, p;
for(let m of images){
p = new Promise(r=>{
const im = new Image;
im.onload = ()=>{
r(im);
}
im.src = m;
});
promises.push(p);
}
Promise.all(promises).then(imgs=>{
for(let im of imgs){
ctx.drawImage(im, 0, 0, w, h);
}
});
}); // end page load
<canvas id='myCanvas' width='280' height='157.5'></canvas>

问题是您无法真正控制浏览器下载每个图像所需的时间。因此,引发onload事件的第一张图像可能不是阵列中的第一张,同样,第二张图像可能是阵列中的第十张,依此类推

为了解决此问题,我建议逐一查看您的图像阵列,并在最后一个图像完成加载后立即开始加载新图像。

这里有一个例子:

const images = [
'https://attefallsverket.picarioxpo.com/1_series_base.jpg?1=1&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_housebase.png?1=1&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_facade_roof_panels.pfs?1=1&p.c=&p.tn=wooden_summer_green.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_windows.pfs?1=1&p.c=71343a&p.tn=&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_door_01.pfs?1=1&p.c=&p.tn=rainsystem_grey.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_facade_01.pfs?1=1&p.c=&p.tn=wooden_summer_green.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_facade_panels.pfs?1=1&p.c=&p.tn=wooden_summer_green.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_facade_corners.pfs?1=1&p.c=&p.tn=wooden_summer_green.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_tin_windows.pfs?1=1&p.c=&p.tn=rainsystem_white.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_tin_roof.pfs?1=1&p.c=&p.tn=rainsystem_white.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_roof_metal_orange.png?1=1&width=2000',
'https://attefallsverket.picarioxpo.com/1kp_rain_system.pfs?1=1&p.c=&p.tn=rainsystem_white.jpg&width=2000',
'https://attefallsverket.picarioxpo.com/1_series_terrace.png?1=1&width=2000',
];
let imagesLoaded = 0;
let c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");

function loadImage() {
let img = new Image();
img.crossOrigin = '';
img.onload = () => {
ctx.drawImage(img, 0, 0, c.width, c.height);
if (imagesLoaded + 1 < images.length) {
imagesLoaded++;
loadImage(imagesLoaded);
}
}
img.src = images[imagesLoaded];
}
loadImage(0)
<canvas id="myCanvas" width="280" height="157.5" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>