0

There is an array which contains some image urls.

const imgs = [{
    src:'./canvas-img1.jpg',
    pos: 20
}, {
    src: './canvas-img2.jpeg',
    pos: 25
}]

I am trying to laod these images once in my application and then store them as cache for future use so that i don't have to load them again and again. I am using these images to draw them on canvas. I am doing the following steps.

  1. I initiate a Promise to load all the images. Promise loads the images and stores them into an array.
  2. Once the promise is resolved, I draw them on canvas

Here is my code

drawCanvas(){
    let canvas = document.getElementById("timeline-canvas")
    let canvas_container = document.getElementById("canvas-container")
    let ctx = canvas.getContext("2d")


    this.loadImage().then((can_imgs)=>{
        console.log(can_imgs.length) // this is coming as array of length 0
        this.state.can_imgs.map((item, i)=>{
            let x = (item.pos * window.innerWidth)/100
            ctx.drawImage(item.img, x,0, 100, 100);
        })
    })
    ctx.save()
}

//load images only once and cache them
loadImage = () => {
    return new Promise(resolve => {
        imgs.map((item, i)=>{
            let img1 = new Image()
            img1.src = item.src
            let x = (item.pos * window.innerWidth)/100
            img1.onload = function (e)
            {
                can_imgs.push({img: img1, pos: item.pos})
            }
        })
        this.setState({can_imgs: can_imgs}, ()=>{
             resolve(can_imgs)
        })

    })

}

But when I console "can_imgs.length" in "then", it consoles zero. It should be array of length 2. What am I doing wrong?

EdG
  • 2,243
  • 6
  • 48
  • 103

2 Answers2

1

Your then is called before the images are actually loaded - so before img.onload is triggered.

To solve your problem, wrap each individual image in a promise and await all of them before finally resolving the loadImage promise:


loadImage = () => {
    return new Promise(resolve => {

        let images = imgs.map((item, i)=> new Promise((resolve) => {
            let img1 = new Image()
            img1.src = item.src
            let x = (item.pos * window.innerWidth)/100
            img1.onload = function (e)
            {
                can_imgs.push({img: img1, pos: item.pos})
                resolve()
            }
        }))

        // wait for all images to resolve
        Promise.all(images).then(() => {
            this.setState({can_imgs: can_imgs}, ()=>{
                resolve(can_imgs)
            })
        })



    })

}
Fitzi
  • 1,641
  • 11
  • 17
0

The problem is you are expecting the onload to call synchronously but it happens asynchronously. Thus can_imgs.push() most probably is happening after you pass can_imgs to resolve().

I would try the following:

let started = 0;
let loaded = 0;

imgs.map((item, i)=>{
   let img1 = new Image()
   img1.src = item.src
   let x = (item.pos * window.innerWidth) / 100

   started++

   img1.onload = (e) => {
      can_imgs.push({img: img1, pos: item.pos})

      loaded++

      if (started === loaded) {
         this.setState({can_imgs: can_imgs}, () => {
            resolve(can_imgs)
         })
      }
   }
})

With started === loaded you are checking if all the images are loaded so then the can call resolve() - as it was in your original code.

I hope this helps!

norbitrial
  • 14,716
  • 7
  • 32
  • 59