In Firefox 8.0 on ubuntu 11.10, the onload function, draw, is called although img.complete is false. I managed to solve it somewhat with the setTimeout hack, but it's not pretty.
I tried setting the img.onload before setting img.src. Although I always get img.complete as true this way, img.width is zero, and img.src si also empty, so it doesn't work.
Any ideas how to implement this properly?
var draw=function(img,ctx,x,y)
{ if(!img.complete)
{ setTimeout(function(){draw(img,ctx,x,y);},50);
}
else
{
ctx.drawImage(img,x,y);
}
}
for(i=0;i<9;i++)
{ img=new Image();
img.src="/media/"+url[i];
img.onload=(draw)(img,ctx,tile.x*offset[i].x,tile.y*offset[i].y);
}
The complete property is buggy in Firefox. Once it's true, it's always true (even if you change the image).
I got around it by testing a new Image object.
new Image for every .complete doesn't work, either.
Try checking img.width for greater than zero instead.
Related
I have an issue with DOMnode.getBoundingClientRect() because the data returned is not consistent. I am using windows.onload (for the lack of a better option) to trigger my function which takes a getBoundingClientRect().height of an element to do some calculation with it.
Issue is, the height of the element is not always consistent. window.onload even should fire last in the page lifecycle as far as i know and both the content and the stylesheets and external resources should be loaded by then, however the page apparently still hasn't rendered it properly yet since I sometimes get height values of like 400, 528, 630 and sometimes its correct (700) for an element I am trying to read.
Thoughts?
Is there some other way to detect when the a div (or a page) has had itself rendered fully?
window's load does indeed mean all resources (including stylesheets and images) have either loaded or failed, but:
It doesn't necessarily mean the browser has completely rendered the result, and
If other JavaScript code is running on load and changes things, those changes may also impact what you're seeing (of course)
Solving #2 will be specific to the code running, but if your problem is just #1, a short setTimeout should fix it:
window.onload = function() {
setTimeout(function() {
// Your code here
}, 60);
};
You may have to play with the delay. 0 tends to work for Chrome, but Firefox tends to want values in the at least 50-60ms range.
Or possibly just requestAnimationFrame may be sufficient:
window.onload = function() {
requestAnimationFrame(function() {
// Your code here
});
};
You might combine one or the other of the above with a check that document.readyState is "complete", since that's the very last thing the browser does (other than JavaScript code set to run on completion, of course). So for instance:
window.onload = function() {
function ready() {
// Your code here
}
function checkReady() {
if (document.readyState === "complete") {
ready();
} else {
setTimeout(checkReady, 0);
}
}
};
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?
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.)
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
}
});
});
I've read numerous posts and articles and tried both the JS-only onLoad method and using JQuery's .load(function() { way of going about things, but I can't find a good way to load an image quietly in the background and then do something once I know its actual width.
At this point I've got it all the way down as simple as I can get it, and it still returns "0":
bgImg = new Image();
bgImg.src = "./img/car-and-truck.gif";
bgImg.onload = imageLoaded();
function imageLoaded() {
alert(bgImg.width);
}
Use this instead:
bgImg.onload = imageLoaded;
You are calling imageLoaded immediately which you do not want to do.
If i had an image with a url such as img/160x180.jpg how using this alone in jquery/javascript can i get the width and height of it. i have tried
alert($('<img src="img/160x180.jpg"/>').naturalWidth)
in the example below it returns undefined.
http://jsfiddle.net/D7dx6/
Updated:
alert($('<img src="http://static.jquery.com/files/rocker/images/logo_jquery_215x53.gif"/>')[0].width);
edit — that doesn't really make sense; it needs to be in an event handler:
$('<img/>', {
'load': function() { alert(this.width); },
'src': 'http://static.jquery.com/files/rocker/images/logo_jquery_215x53.gif'
});
That code, it should be noted, might have problems on IE because it sometimes drops the ball if the "src" is set and the image is found in cache before the "load" handler is established. In that case, you can do this:
$('<img/>', { 'load': function() { alert(this.width); } }).prop('src', 'http://...');
There's no "naturalWidth" property.
Though it's pretty much irrelevant overhead in this case, you can do this without jQuery like this:
var img = new Image();
img.onload = function() {
alert(img.width);
};
img.src = "http://placekitten.com/300/400";
Now one thing to watch out for is that if you're looking at actual page elements (that is, <img> tags on the page), they might have "width" attributes that override the true size. Fetching the image again will generally pull it out of cache, though there's some potential pain there for huge images on mobile devices.
(edit — Graham points out that there is a "naturalWidth", but it's not well-supported at this time.)
naturalWidth and naturalHeight aren't well supported yet: see https://developer.mozilla.org/en/DOM/HTMLImageElement.
The pure-JavaScript way to determine the original dimensions of an image, whether or not it currently exists in the DOM, is to use the Image object.
var img = new Image();
img.src = 'path/to/img.jpg';
// you'll probably want to use setTimeout to wait until the imh.complete is true
console.log(img.width);
I want to know if looking at an image's height property is a reliable way to see if it has already loaded.
Let's say I've got a reference to an image, like this:
var img = document.getElementById('myImg');
If I want to run some code that relies on the image being loaded, I can put it in a callback, and run img.onload = myCallback;.
But if the image has already loaded before this code runs, then the callback won't execute.
This is one possible solution:
if (img.height && img.height > 0) {
myCallback();
}
else {
img.onload = myCallback;
}
In other words: run myCallback now if the image is already loaded, otherwise wait until it's loaded.
But I have a feeling this code might be fragile... Will it work in all cases? Is there a safer or more elegant way of doing this?
You're looking for the complete property. However, I don't think this works reliably in all Firefox versions, so it's good to add your height check as backup:
if (img.complete) {
myCallback();
} else if (img.height && img.height > 0) {
myCallback();
} else {
img.onload = myCallback;
}