【问题标题】:How do I draw images in layers on canvas?如何在画布上的图层中绘制图像?
【发布时间】:2020-12-22 16:40:48
【问题描述】:

我有一个画布,我使用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 html5-canvas


    【解决方案1】:

    您需要确保第一个图像已加载,然后再启动下一个图像的加载。所以做一个异步循环:

    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
    &lt;canvas id="myCanvas" width="280" height="157.5"&lt;/canvas&gt;

    【讨论】:

    • 糟糕,我看到您再次触发了函数onload。是的,这行得通。我其实很喜欢这种设计。
    【解决方案2】:

    这是你真正想做的事情:

    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
    &lt;canvas id='myCanvas' width='280' height='157.5'&gt;&lt;/canvas&gt;

    【讨论】:

      【解决方案3】:

      问题是您无法真正控制浏览器下载每张图片需要多长时间。因此,触发 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>

      【讨论】:

        【解决方案4】:

        现有答案解决了问题,但它们是序列化的,不会同时触发请求。如果您想优化以在屏幕上显示某些内容并且不在乎到达完整图像需要多长时间,这很好,但如果您的目标是尽快绘制整个图像和/或不显示一个部分完成的图像,一个接一个的网络请求是次优的。

        而不是这个:

        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 图像的成本。

        使用 Promise 是一个很好的方法。您可以将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>

        如果您的目标是尽快在屏幕上显示某些内容,您可以将这两种方法结合起来,对后台执行一个快速的串行请求,然后并行执行其余的请求,甚至分批执行。但这对于这种情况来说感觉有点过头了。我提到了完整性的技术。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-12-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-10-28
          • 2012-10-02
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多