1

I have a string in javascript that I want to use to instantiate a child element for a particular element, eg:

let target = document.getElementById("target")
let string = ```
    <div>
        <img src="somewhere">

        <h1> This is a heading </h1>

        <p> And some text <p>
    </div>
```
target.innerHTML = string

Later, I want to find the offset of the heading:

// ...
// later
// ...

let heading = target.getElementByTagName("h1")
console.log(heading.offsetTop)

But sometimes its incorrect because sometimes the image (or any other external element) has loaded, and sometimes it hasn't loaded before I find the offset. Can anybody suggest a jquery free way to determine when the contents of target have finished loading all external resources?

user2662833
  • 1,005
  • 1
  • 10
  • 20
  • Add an onload-listener to the image, and do what you need in the handler function. – Teemu Oct 15 '17 at 09:30
  • 1
    try: `image.onload = function() { /* ... */ }`. this will trigger when the image is fully loaded and you can read out the offset – Joshua K Oct 15 '17 at 09:30
  • What about other elements that might need to load. An image is just a typical example. – user2662833 Oct 15 '17 at 09:32
  • Well, you are asking about `img`, right?, and not all elements have an _onload_ event, these does `, , – Asons Oct 15 '17 at 09:35
  • Would it be enough to subscribe to all of the onload events for all children that are of these types? – user2662833 Oct 15 '17 at 09:36
  • I wouldn't recommend to subscribe to all, just those you need. – Asons Oct 15 '17 at 09:37
  • But would it be only the tags you've listed above to worry about? I'm worried about any tags that will "change size" once any external content has been loaded, because it will mess up any scrolling events I have. – user2662833 Oct 15 '17 at 09:41
  • Well, that is way out of scope for this question. Read this (and check `MutationObserver` in the question): https://stackoverflow.com/questions/25679784/how-to-detect-resize-of-any-element-in-html5 – Asons Oct 15 '17 at 09:43
  • I did already try a MutationObserver, but it didn't seem to do anything when the image loaded. Could have been a mistake in my implementation. – user2662833 Oct 15 '17 at 09:55
  • It appears to me the issue you have is more about _...once any external content has been loaded, it might mess up any scrolling events_ than when an image been loaded. – Asons Oct 15 '17 at 10:03
  • Yep, that is the problem I'm having :) This is better wording – user2662833 Oct 15 '17 at 10:05

2 Answers2

2

Your best bet is probably to add a load listener to each of the loadable items in your target and then fire a callback when they have all loaded.

let onReady = (target, callback) => {
  // add any other tags you have that need to load to the selector
  // i.e. querySelectorAll('img, iframe'), etc.
  let loadables = [...target.querySelectorAll('img')];

  let total = loadables.length;
  let doneCount = 0;

  // since I'm using bind this needs to be a normal function, not an arrow function
  let loadObserver = function() {
    // remove the listener since we don't need it anymore
    this.removeEventListener('load', loadObserver);
    doneCount += 1;
    if (doneCount === total) {
      callback();
    }
  };

  loadables.forEach(el => {
    el.addEventListener('load', loadObserver.bind(el), true);
  });
};


  let target = document.getElementById("target");
  let string = `
  <div>
      <img id="i1" src="http://via.placeholder.com/350x50">
      <img id="i2">
      <img id="i3">
      <h1> This is a heading </h1>

      <p> And some text <p>
  </div>
  `;
  target.innerHTML = string;

  // note that you have to call onReady right away after setting the
  // innerHTML, since JS is single threaded calling it now will add the
  // load listeners before any of the images start to load. If you call
  // it later, after the event loop has run, it might not work.
  onReady(target, () => {
    let heading = target.querySelector("h1");
    console.log(heading.offsetTop);
  });






// simulate slow loading images
let loadAfter = (el, ms) => {
  setTimeout(() => {
     el.src = 'http://via.placeholder.com/350x50';
  }, ms);
};

loadAfter(document.getElementById('i2'), 2000);
loadAfter(document.getElementById('i3'), 3000);
img {
  border: 1px solid #000;
  background: #ccc;
}

#target {
  border: 1px dashed #f00;
}
<div id="target"></div>

A couple of other things to note about your code, in JS template literals just have a single backtick unlike HEREDOCs in other languages that require 3. Also you used the nonexistent getElementByTagName method. There is a getElementsByTagName method (note the s in Elements), but it returns a collection, not a single node. You could use target.getElementsByTagName('h1')[0] to get the element you are looking for, but instead in my code I used querySelector, which always returns a single element.

Useless Code
  • 12,123
  • 5
  • 35
  • 40
-1
"<img src='somewhere' onload='imageIsLoaded(this.id)'>"  

imageIsLoaded() can be a javascript function.

Wahid Masud
  • 993
  • 10
  • 35