Как рисовать изображения слоями на холсте?

У меня есть холст, на котором я использую 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>
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
4
0
202
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Проблема в том, что вы не можете контролировать, сколько времени потребуется браузеру для загрузки каждого изображения. Таким образом, первое изображение, запускающее событие 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>

К сожалению, я вижу, что вы снова запускаете функцию onload. Да, это работает. Мне действительно нравится этот дизайн.

StackSlave 22.12.2020 11:11

Существующие ответы решают проблему, но они сериализованы и не запускают запросы одновременно. Если вы хотите оптимизировать вывод чего-либо на экран и не заботитесь о том, сколько времени потребуется для достижения полного изображения, это нормально, но если ваша цель состоит в том, чтобы как можно быстрее нарисовать все изображение и/или не показывать частично завершенный образ, одиночные сетевые запросы неоптимальны.

Вместо этого:

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>

Другие вопросы по теме