У меня есть холст, на котором я использую 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>


![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Проблема в том, что вы не можете контролировать, сколько времени потребуется браузеру для загрузки каждого изображения. Таким образом, первое изображение, запускающее событие onload, может не быть первым изображением в массиве — аналогично, второе изображение может быть 10-м в массиве и так далее.
В качестве обходного пути я бы рекомендовал просмотреть массив изображений один за другим и начать загрузку нового изображения, как только завершится загрузка последнего изображения.
Вот пример:
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>Вам нужно будет убедиться, что первое изображение загружено, прежде чем запускать загрузку следующего. Итак, сделайте асинхронный цикл:
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 изображений по одному.
Хороший способ сделать это — промисы. Вы можете обещать onload и onerror обратные вызовы для resolve и reject соответственно, затем использовать 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. Да, это работает. Мне действительно нравится этот дизайн.