In short I'm trying to get the dimensions of any given div's background-image so I can do some fancy sprite animations with it. The code below is a simplified version but illustrates the blocking issue.
Given a div with a styled background-image, with the class "animation", the following will return the naturalWidth of the background image in FF/Safari/Chrome, but not in any IE:
var testarray = [];
var divs = document.getElementsByClassName("animation");
console.log("testarray.length: ", testarray.length);
onload = function starttest(){
console.log("in onload");
console.log("testarray.length: ", testarray.length); //returns correct length in all browsers
console.log("testarray[0].src: ", testarray[0].src); //returns filename in all browsers
console.log("testarray[0].naturalWidth: ", testarray[0].naturalWidth); //returns 0 in IE only
}
document.onreadystatechange = function(){
console.log("in readystatechange");
console.log("document.readystate: ", document.readyState);
var thing = new Image();
testarray.push(thing);
if (document.readyState === "interactive") {
var bi = window.getComputedStyle(divs[0], false).backgroundImage.slice(4, -1);
testarray[0].src = bi;
document.body.appendChild(testarray[0]);
}
}
The above is run in an embedded script below the body tag.
Why does the loaded image have no dimensions in IE? appendChild fails silently in IE as well, though works great in other browsers. I'd prefer a native JS answer over jQuery.
Related
I am using XHR (XML Http Request) to load an html fragment. I am using responseType = "document" in order to offload the html parsing to the browser. When the ajax call completes, I am using document.adoptNode() to include the ajax html elements in my main document.
I noticed a weird bug that affects Safari only (v9.1.1 on El Capitan and iOS 9.3.2). It seems that when Safari adopts the node, it will convert css class names into lower case. For a full demonstration, see this jsfiddle: https://jsfiddle.net/theblueslate/wxo7zst5/2/
This bug doesn't occur on Chrome v51 or IE11.
The code from the jsfiddle is included here:
function buildDataLoadedCallback (containerId, useAdopt) {
return function() {
var parsedDoc = this.response;
var parsedBodyChild = parsedDoc.body.children[0];
var newNode;
if (useAdopt) {
newNode = document.adoptNode(parsedBodyChild);
} else {
newNode = document.importNode(parsedBodyChild, true);
}
var container = document.getElementById(containerId);
container.appendChild(newNode);
}
}
function requestAjaxHtmlFragment(pageName, callback) {
var xhr = new XMLHttpRequest();
xhr.responseType = "document";
xhr.addEventListener("load", callback);
/* this fragment.html file simply contains:
<div class="myClass">
<p>MyClass</p>
</div>
*/
xhr.open("GET","https://dl.dropboxusercontent.com/u/15211879/js-fiddle/" + pageName + ".html", /*async:*/true);
xhr.send();
}
var pageName = "fragment";
requestAjaxHtmlFragment(pageName, buildDataLoadedCallback(pageName + "-adopt-container", true));
Is there an obvious error that I am missing? I can't spot it, and I have raised a webkit bug: https://bugs.webkit.org/show_bug.cgi?id=159555, but I am hoping I am wrong.
Turns out this was a bug. Now fixed in WebKit: https://bugs.webkit.org/show_bug.cgi?id=159555
I think it is still useful posting this to SO. Posting increases the visibility for anybody else who is struggling with this issue, as I was.
I have this script which should show the text "Loading..." while images are loading, then change the text to "loaded" when all images are loaded. I added a button to load new images to make sure that it works for dynamically loaded images as well.
This works perfectly in Chrome but in Firefox the "Loading..." text never appears. I have no idea why this would be. The page begins loading and not all images are loaded so it should create the text "Loading.." but it doesn't. Then when all images are done loading the text "Loading" appears.
I just don't get why one message would appear and the other wouldn't. Especially because there are no qualifications that have to be met before creating the "Loading..." text, it should just fire automatically.
jsfiddle Example | Full Page Example
$(document).ready(function() {
var checkComplete = function() {
if($('img').filter(function() {return $('img').prop('complete');}).length == $('img').length) {
$('.status').text('Loaded');
} else {
$('.status').text('Loading...');
}
};
$('img').on('load',function() {
checkComplete();
});
$('#button').click(function() {
$('img.a').attr('src' , 'http://farm9.staticflickr.com/8545/8675107979_ee12611e6e_o.jpg');
$('img.b').attr( 'src' , 'http://farm9.staticflickr.com/8382/8677371836_651f586c99_o.jpg');
checkComplete();
});
checkComplete();
});
You have several issues in the code.
First off, the checkComplete() function is not written correctly. It should be this:
var checkComplete = function() {
var imgs = $('img');
if(imgs.filter(function() {return this.complete;}).length == imgs.length) {
$('.status').text('Loaded');
} else {
$('.status').text('Loading...');
}
};
The main fix here is that the filter callback needs to refer to this.complete, not to $('img').prop('complete') because you are trying to filter a single item at a time.
Second off, you are relying on both .complete and .load working correctly AFTER you've changed the .src value. This is explicitly one of the cases where they do not work properly in all browsers.
The bulletproof way to work around this is to create a new image object for the new images, set the onload handler before you set the .src value and when both onload handlers have fired, you will know that both new images are loaded and you can replace the once you have in the DOM with the new ones.
Here is a version that works in FF:
$(document).ready(function() {
$('#button').click(function() {
var imgA = new Image();
var imgB = new Image();
imgA.className = "a";
imgB.className = "b";
var loaded = 0;
imgA.onload = imgB.onload = function() {
++loaded;
if (loaded == 2) {
$("img.a").replaceWith(imgA);
$("img.b").replaceWith(imgB);
$('.status').text('Loaded');
}
}
// the part with adding now to the end of the URL here is just for testing purposes to break the cache
// remove that part for deployment
var now = new Date().getTime();
imgA.src = 'http://farm9.staticflickr.com/8545/8675107979_ee12611e6e_o.jpg?' + now;
imgB.src = 'http://farm9.staticflickr.com/8382/8677371836_651f586c99_o.jpg?' + now;
$('.status').text('Loading...');
});
});
Working demo: http://jsfiddle.net/jfriend00/yy7GX/
If you want to preserve the original objects, you can use the newly created objects only for preloading the new images and then change .src after they've been preloaded like this:
$(document).ready(function() {
$('#button').click(function() {
var imgA = new Image();
var imgB = new Image();
var loaded = 0;
imgA.onload = imgB.onload = function() {
++loaded;
if (loaded == 2) {
$("img.a")[0].src = imgA.src;
$("img.b")[0].src = imgB.src;
$('.status').text('Loaded');
}
}
// the part with adding now to the end of the URL here is just for testing purposes to break the cache
// remove that part for deployment
var now = new Date().getTime();
imgA.src = 'http://farm9.staticflickr.com/8545/8675107979_ee12611e6e_o.jpg?' + now;
imgB.src = 'http://farm9.staticflickr.com/8382/8677371836_651f586c99_o.jpg?' + now;
$('.status').text('Loading...');
});
});
Working demo of this version: http://jsfiddle.net/jfriend00/ChSQ5/
From the jQuery API .load method
Caveats of the load event when used with images
A common challenge developers attempt to solve using the `.load()` shortcut is to execute a function when an image (or collection of images) have completely loaded. There are several known caveats with this that should be noted. These are:
It doesn't work consistently nor reliably cross-browser
It doesn't fire correctly in WebKit if the image src is set to the same src as before
It doesn't correctly bubble up the DOM tree
Can cease to fire for images that already live in the browser's cache
I have a javascript function (epoch calendar) which displays a calendar when focus is set on certain text boxes. this works fine in ie8, ff (all versions as far as I can test), opera etc but doesn't work in ie7 or previous.
If i have it set up in a blank html test page it will work so I'm fairly sure it's a conflict with my css (provided to me by a designer).
I've traced the error to these lines of code -
Epoch.prototype.getTop = function (element) //PRIVATE: returns the absolute Top value of element, in pixels
{
var oNode = element;
var iTop = 0;
while(oNode.tagName != 'BODY') {
iTop += oNode.offsetTop;
oNode = oNode.offsetParent;
}
return iTop;
};
Epoch.prototype.getLeft = function (element) //PRIVATE: returns the absolute Left value of element, in pixels
{
var oNode = element;
var iLeft = 0;
while(oNode.tagName != 'BODY') {
iLeft += oNode.offsetLeft;
oNode = oNode.offsetParent;
}
return iLeft;
};
More specifically, if i remove the actual while loops then the calendar will display OK, just that its positioning on the page is wrong?
EDIT
Code below which sets 'element'
<script type="text/javascript">
window.onload = function() {
var bas_cal, dp_cal, ms_cal;
dp_cal = new Epoch('epoch_popup', 'popup', document.getElementById('<%=txtDateOfDiag.ClientID%>'));
dp_cal = new Epoch('epoch_popup', 'popup', document.getElementById('<%=txtDOB.ClientID%>'));
};
</script>
Note: I am using asp.net Master pages which is why there is a need for the .ClientID
EDIT
A further update - I have recreated this without applying css (but including the .js file provided by the designer) the code still works fine which, there must be some sort of conflict between the CSS and my JavaScript?
That would lead me to believe that the tagName does not match, possibly because you have it in upper case. You might try while(!oNode.tagName.match(/body/i)) {
what happens if you add a line of debug code like this:
var oNode = element;
var iLeft = 0;
alert(oNode);
This might give different results in different browsers; I think it may be NULL for IE.
You may want to have a look at the code that provides the value of the 'element' parameter to see if there's a browser-dependant issue there.
So I am fairly new to Javascript (a lot of experience in PHP), but I have this basic script that checks a link that redirects to a URL to see what the image height is. If it is a certain height then it adds one to a variable, otherwise nothing. I would easily do this in PHP but the images are on other servers and not my own so it doesn't work.
Anyways, ehre is the script. Let me know if you have any tips. Works well and tested in Chrome, Safari, Opera, and IE.
<script language='JavaScript'>
window.onload = function() {
var nstar = 0, urls = [];
urls[0] = "http://optout.imiclk.com/cgi/nai_status.cgi?nocache=";
urls[1] = "http://www.adbrite.com/mb/nai_optout_check.php?nocache=";
urls[2] = "http://events.adchemy.com/visitor/auuid/nai-status?nocache=";
function getImgSize(imgSrc){
var newImg = new Image();
newImg.src = imgSrc;
return{height:newImg.height, width:newImg.width}
}
for(i=0,length=urls.length;i<length;i++){
if(getImgSize(urls[i]).height==43){nstar++;}
}
document.getElementById('tracknum').innerHTML = "<b>" + nstar + "</b>";
}
</script>
Maybe the image isn't loaded yet?
Try :
image.onload=function() {
alert('W:'+image.width+', H:'+image.height)
}
I've written a quick image swap class that switches images on hover by placing adding '_grey' to the image src. The code works great throughout the site in all browsers apart from ie6. The substr doesnt seem to work properly here - any advice please!?
Code as follows -
$(document).ready(function() {
var initImg;
$('img.swapGrey').hover(function() {
initImg = $(this).attr("src");
var imgType = (initImg).substr(-4);
alert(initImg);
var greyImg = initImg.slice(0, -4) + "_grey" + imgType;
alert(greyImg);
$(this).attr("src",greyImg);
}, function() {
$(this).attr("src",initImg);
});
});
Use slice rather than substr. substr is non-standard, while slice is specified (including negative positions) in the ECMAScript 3 spec, and is supported in all the major browsers, including IE 6.
just use a positive starting position in IE.
IE doesn't support negative values for the argument of substr.
Try putting your code in load event rather than ready:
$(window).load(function(){
// your code...
});
The reason for using load event is that by the time images have completely loaded into the page unlike ready event.
More verbose than needs to be (to show the effect of the functions), but basically changes test.jpg to test_grey.jpg if test.jpg is the src of the img element.
$(document).ready(function() {
var initImg;
$('img.swapGrey').hover(function() {
initImg = $(this).attr("src");
var len = initImg.length - 4;
var imgType = initImg.slice(len);
alert(":"+imgType+":");
alert(initImg + " is " + len +":"+ imgType);
var greyImg = initImg.slice(0, -4) + "_grey" + imgType;
alert(greyImg);
$(this).attr("src", greyImg);
}, function() {
$(this).attr("src", initImg);
});
});