6

I've got a problem with some Javascript-generated content :

I use the onload() event of my html page to load HTML code from a server and inject it in the page. Since it already is html I use the innerHtml property of the nodes I want to fill with content.

Once I've injected the HTML I call a handmade adjustHeights() function that normalizes the height of the elements I created this way to make them all match the height of the tallest element.

All of this works perfectly unless the code inside innerHtml contains some sort of image or other media that takes time to load (particularly videos) because adjustHeights() is called between the injection of the code and the end of the loading time of the image/video/etc.

Is there any way to wait for every element to be loaded before calling adjustHeights() ? I've already tried the document.onreadystatechange property but the state is already on 'completed' when I start to inject code.

If it's possible I would rather not use time-based calls on adjustHeights().

To make it clearer here's an example :

var mylist = document.getElementById('mycontent');
var li;
for (var i = 0; i < res.length; i++)
{
    li = document.create('li');
    li.innerHTML=res[i];
    mylist.appendChild(li);
}
adjustHeights();
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Melekus
  • 63
  • 1
  • 7

2 Answers2

10

To do this without adding an inline onload attribute to all the images/videos/etc you will have to observe the DOM for changes. Then on every change, you have to fetch all the new media and add the onload event to them from the callback. To prevent checking each element every time, once they've been loaded you could mark them as such by adding a data-loaded="true" property for instance.


A cross-browser solution to observe DOM changes can be found in this answer: Detect changes in the DOM. I will not repeat it here (but it's included in the demo below).


Note: I use images as an example, but this should also work for videos and other media.

On every DOM change first you check for images without the data-loaded attribute that are already loaded anyway (this could happen when an image was still in the browser's cache) by checking element.complete. If so, fire the callback function and add the attribute to it.

If .complete is not the case, add an onload event to them that also fires the callback once it is loaded.

// Observe the DOM for changes
observeDOM(document.body, function(){ 
    checkNewMedia();
});

// Loop through all new media, add the event
var checkNewMedia = function() {

    // extend this by using document.querySelectorAll("img:not([data-loaded), video:not([data-loaded])") etc.
    var media = document.querySelectorAll('img:not([data-loaded]');
    for(var i = 0; i < media.length; i++) {
        addMediaLoadedEvent(media[i]);   
    }
}

// Fire the callback if complete, otherwise bind to onload
var addMediaLoadedEvent = function(element) {
    if (element.complete) {
        onMediaLoaded(element);
    } else {
        element.addEventListener('load', function(event) {
            onMediaLoaded(event.target);   
        });
    }
}

// The callback that is fired once an element is loaded
var onMediaLoaded = function(element) {
    element.setAttribute('data-loaded', 'true');

    adjustHeights(); // <-- your function
}

DEMO: fire event on each image load


If you only want to fire the event once all images are loaded, you could add an extra check that counts how many images are still not loaded:

// The callback that is fired once an image is loaded
var onMediaLoaded = function(element) {
    element.setAttribute('data-loaded', 'true');

    // only fire when there are no media elements without the 'data-loaded' attribute left
    // again, can be extended to use video, etc.
    if(document.querySelectorAll('img:not([data-loaded])').length === 0) {
        adjustHeights(); // <-- your function
    }
}

DEMO: fire event on all images loaded

Community
  • 1
  • 1
Stephan Muller
  • 27,018
  • 16
  • 85
  • 126
  • It would've been awesome to use this, but it seems the observeDOM function you linked is only IE9+ and the website I'm working on must be able to run on IE8 :/ Anyway, thanks for the awesome answer and for editing my first post, i'm still a newb here :) – Melekus Apr 30 '15 at 19:34
  • Ah, that's a shame. I figured hardly anyone uses/supports IE8 nowadays. I feel for you man. – Stephan Muller Apr 30 '15 at 19:37
  • @Melekus You could use this polyfill for browsers that don't support it: https://coderwall.com/p/oo7i_g/mutationobserver-polyfill. It's starting to become a lot of JS this way, but it might just beat having to add attributes to all images manually. – Stephan Muller Apr 30 '15 at 19:45
2

For image and other sort of media, you should use "onload" event so that you can call adjust height after that. Example:

<img src="someimage.png" onload="loadImage()" >

<script>
   function loadImage() {
     // code to adjust heights
    }
</script>

Here is the standard version:

var img = document.querySelector('img')

function loaded() {
  alert('loaded');
  // do something to adjust height
}

if (img.complete) {
  loaded()
} else {
  img.addEventListener('load', loaded)
  img.addEventListener('error', function() {
      alert('error')
  })
}
roxxypoxxy
  • 2,973
  • 1
  • 21
  • 28
  • If I understand correctly, what you're saying is that I should search for all the elements tagged as image, sound, videos, ... and add an eventListener for onload on each of them ? – Melekus Apr 30 '15 at 15:08
  • Yes if you want adjustHeights() to perform properly. Well if you don't like this approach, the simplest trick I can think of is to set a height and width of the image so that image will occupy the space even before it has been loaded and it will always have the defined height and width even if the image is smaller or larger. – roxxypoxxy Apr 30 '15 at 15:23
  • I already set the width and height of the image to match the parent element's width without altering the aspect ratio, so that's not an option :/ However I've looked through the code and think I'll do with the listeners. Thanks for the help ! – Melekus Apr 30 '15 at 15:31