0

I'm trying to load an image in JavaScript:

loadImage(src, img) {
  return new Promise((resolve, reject) => {
    img.onload = resolve;
    img.onerror = reject;
    img.src = src;
  });
}

The img has been created before, but doesn't exist in the DOM yet. It should be appended once it has loaded.

My problem: As far as I understand, it's not actually going to load the image, because the element is not part of the DOM yet.


I tried to use new Image(), because this will load the image source before being inserted into the DOM:

loadImage(src, img) {
  return new Promise((resolve, reject) => {
    var tmp = new Image();

    tmp.onload = resolve;
    tmp.onerror = reject;
    tmp.src = src;
    img.src = src;
  });
}

However, this did the loading twice and felt buggy.

I need to keep (and use) the img element, as other event handlers and attributes are attached to it.

What can I do to load the image and resolve the promise once it's done?

Andreas Remdt
  • 603
  • 1
  • 8
  • 15
  • So why are you not setting the image directly? – epascarello May 10 '19 at 13:08
  • 1
    `tmp.src = src; img.src = src;` that is not going to preload....the setting of img.src would have to be done in the onload call. – epascarello May 10 '19 at 13:09
  • @epascarello I thought the same, but in my DevTools I can see the source being fetched twice. Don't know why... Besides, I can't set the image directly, since it's part of a larger structure which depends on the image being loaded. – Andreas Remdt May 10 '19 at 13:12

3 Answers3

2

Not sure what is wrong with your second approach and why you think it is buggy. I can imagine it loading twice if image is not in cache yet: if you trigger loading two images at the same time, the browser can't know the URL's caching parameters, and should load it for both elements separately. But if you load them in sequence, the second one should pull from cache, and not make a separate request, except if your server is being weird and serving headers that prohibit caching.

function loadImage(src, img) {
  return new Promise((resolve, reject) => {
    var tmp = new Image();

    tmp.onload = () => {
      img.src = src;
      resolve();
    };
    tmp.onerror = reject;
    tmp.src = src;
  });
}

loadImage("https://via.placeholder.com/350x150", document.getElementById('foo'));
<image id="foo">
Amadan
  • 191,408
  • 23
  • 240
  • 301
  • Interesting! I have disabled my cache in DevTools since I wanted to test slow connections through throttling. Even using your method, the images are fetched twice in the network. Could the disabled cache be the issue then? – Andreas Remdt May 10 '19 at 13:21
  • If you disabled the cache, then obviously the second image will not be able to be found in it :) The only way to do it with cache disabled would be to AJAX/fetch the image source and turn it into data URL like [this](https://stackoverflow.com/a/45247049/240443), then use the data URL for `img.src`; this way you control the traffic explicitly; though then you are subject to XSS restrictions, and feels icky. Do yourself a favour and test with proper server config to make sure - image caching exists for a reason. :D – Amadan May 10 '19 at 13:29
  • Well, today I learned something new. I'll be using `deelay.me` to simulate network latency and leave the cache on :D Thanks a lot! – Andreas Remdt May 10 '19 at 13:30
0

If I understand you correctly, you want to render the image once it's done loading? Maybe just create the image in the "loadImage" function and then in the "onload" do the appropriate appending. Here's an example what I was thinking:

const loadImage = (src, parentEl) => {
const img = document.createElement('img')
img.src = src
img.onload = () => {
  parentEl.appendChild(img)
 }
}

const parentEl = document.getElementById('parent')
loadImage('https://fakeimg.pl/640x360', parentEl)

https://jsfiddle.net/2Lp8hyxf/

kimito
  • 1
  • Well, unfortunately, it's not that easy. The image is only part of an HTML structure, a lightbox. This structure is created through JavaScript. I only want to append the structure into the DOM once the image has loaded. I can't use your method since it creates a new image element. I need to use the element that's already there. Otherwise, it would be easy :D – Andreas Remdt May 10 '19 at 13:10
  • @AndreasRemdt I'm confused - you don't want to add the image to the DOM until it's loaded but you already have an image in the DOM that you have to re-use? Unless I'm mistaken, you could just *replace* the previous image using this method but you remove the old image and append the new within the onload event. – VLAZ May 10 '19 at 13:13
  • @VLAZ Imagine this: `var template = '
    ';`. The image is not part of the DOM yet, it's part of my JavaScript. It should be in the DOM once it has been loaded.
    – Andreas Remdt May 10 '19 at 13:15
0

I think your assumption is wrong. The image gets loaded without it needed to be attached to the DOM.

I modified your function slightly so it resolves with the image tag instead of the load event and it seems to be working fine:

const loadImage = (src, img) => {
    return new Promise((resolve, reject)=> {
    img.src = src
    img.onload = () => {
        resolve(img);
    }
    img.onerror = reject;
  })

}

I'm testing it like this:

const img = document.createElement('img');
loadImage('http://deelay.me/1500/https://fakeimg.pl/640x360', img)
    .then((img) => {
    const parent = document.getElementById('parent');
    document.getElementById('parent').append(img);
    const text = document.createElement('p');
    text.innerText = "The promise resolved now";
    parent.append(text);
  })

The deelay.me adds an artificial delay to the loading of the image, so you can see that the image is actually loaded and, using the promise, attached to the DOM only after it has been loaded.

Hope this helps.

Fiddle: https://jsfiddle.net/f4hratje/5/

Alex
  • 467
  • 4
  • 13
  • Thanks, I think I have it figured out, seemed to be an issue with the cache. This `deelay.me` service, however, came very handy, I'll use that from now on to simulate network latency. – Andreas Remdt May 10 '19 at 13:29