Is it possible to bind an onload event to each image, declaring it once? I tried, but can\'t manage to get it working... (this error is thrown: Uncaught
You can't set a handler on the prototype, no.
In fact, I'm not aware of any way to get a proactive notification for image load if you haven't hooked load
on the specific image element, since load
doesn't bubble.
I only two know two ways to implement a general "some image somewhere has loaded" mechanism:
Use a timer loop, which is obviously unsatisfying on multiple levels. But it does function. The actual query (document.getElementsByTagName("img")
) isn't that bad as it returns a reference to the continually updated (live) HTMLCollection
of img
elements, rather than creating a snapshot like querySelectorAll
does. Then you can use Array.prototype
methods on it (directly, to avoid creating an intermediary array, if you like).
Use a mutation observer to watch for new img
elements being added or the src
attribute on existing img
elements changing, then hook up a load
handler if their complete
property isn't true. (You have to be careful with race conditions there; the property can be changed by the browser even while your JavaScript code is running, because your JavaScript code is running on a single UI thread, but the browser is multi-threaded.)
I've written something similar some time ago to check if an image is loaded or not, and if not, show a default image. You can use the same approach.
$(document).ready(function() {
// loop every image in the page
$("img").each(function() {
// naturalWidth is the actual width of the image
// if 0, img is not loaded
// or the loaded img's width is 0. if so, do further check
if (this.naturalWidth === 0) { // not loaded
this.dataset.src = this.src; // keep the original src
this.src = "image404.jpg";
} else {
// loaded
}
});
});
You get that error because onload
is an accessor property defined in HTMLElement.prototype
.
You are supposed to call the accessor only on HTML elements, but you are calling the setter on HTMLImageElement.prototype
, which is not an HTML element.
If you want to define that function, use defineProperty
instead.
Object.defineProperty(HTMLImageElement.prototype, 'onload', {
configurable: true,
enumerable: true,
value: function () {
console.log(this, "loaded");
}
});
var img = new Image();
img.onload();
Warning: Messing with builtin prototypes is bad practice.
However, that only defines a function. The function won't be magically called when the image is loaded, even if the function is named onload
.
That's because even listeners are internal things. It's not that, when an image is loaded, the browser calls the onload
method. Instead, when you set the onload
method, that function is internally stored as an event listener, and when the image is loaded the browser runs the load
event listeners.
Instead, the proper way would be using Web Components to create a custom element:
var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function() {
var img = document.createElement('img');
img.src = this.getAttribute('src');
img.addEventListener('load', function() {
console.log('loaded');
});
this.appendChild(img);
};
document.registerElement('my-img', {prototype: proto});
<my-img src="/favicon.ico"></my-img>
There is not much browser support yet, though.
This provides a notification for any image loading, at least in Opera (Presto) and Firefox (haven't tried any other browser). The script tag is placed in the HEAD
element so it is executed and the event listener installed before any of the body content is loaded.
document.addEventListener('load', function(e) {
if ((!e.target.tagName) || (e.target.tagName.toLowerCase() != 'img')) return;
// do stuff here
}, true);
Of course, by changing the filtering on tagName
it will also serve to respond to the loading of any other element that fires a load event, such as a script tag.