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 TypeError: Illegal invocation)
HTMLImageElement.prototype.onload = function()
{
console.log(this, "loaded");
};
P.S: I also tried returning this, but doesn't seem to be the issue here... any suggestions / explanations on why my current code isn't working?
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 TypeError: Illegal invocation)
HTMLImageElement.prototype.onload = function()
{
console.log(this, "loaded");
};
P.S: I also tried returning this, but doesn't seem to be the issue here... any suggestions / explanations on why my current code isn't working?
Share Improve this question asked Aug 21, 2016 at 16:12 Bilal075_Bilal075_ 1537 bronze badges 5- 5 No. Don't mess with builtin prototypes. – Bergi Commented Aug 21, 2016 at 16:14
- ncyoung./?p=194 – mplungjan Commented Aug 21, 2016 at 16:15
- @Bergi It's an empty built-in prototype. It's an event listener, it's made for custom boundations of custom functions. – Bilal075_ Commented Aug 21, 2016 at 16:17
- @mplungjan That's interesting, thanks for sharing! – Bilal075_ Commented Aug 21, 2016 at 16:19
-
@Bilal075_ is does not matter if
onload
exists forHTMLImageElement.prototype
or not. You should never add custom functionality by modifying the prototype of native objects, and should avoid to do so for objects that you don't own (e.g. of foreign libraries), except if they give you a defined why how you should do it. – t.niese Commented Aug 21, 2016 at 16:23
4 Answers
Reset to default 3You 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
ofimg
elements, rather than creating a snapshot likequerySelectorAll
does. Then you can useArray.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 thesrc
attribute on existingimg
elements changing, then hook up aload
handler if theirplete
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.)
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.
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
}
});
});