I have tabs logic that load html templates inside a wrapper. That's works fine, but I included an animation that animate height of the tab wrapper when tab is switched.
The problem is the following: When a template contains <img src="/some-image.png"> the $('#tab-content').load('template-url', function() {...}) callback function sometimes is executed before the browser show the images. And my animation is not working correctly.
Code example (jsFiddle):
var currentHeight = $contentHolder.height();
$contentHolder.load(path, function() {
$contentHolder.stop();
function animateHeight() {
var loadedContentHeight = $contentHolder.css('height', 'auto').height();
$contentHolder.height(currentHeight);
$contentHolder.animate({
height: loadedContentHeight
}, 800, 'linear');
}
animateHeight();
});
I tried to set little timeout, but it's not working every time. If I set more that 300ms timeout, It feels like tabs are changed too slow.
I tried to execute the animation when $('img').load(function() {}) is fired, but with no luck.
This bug occurs most often when the web page is fully refreshed and each tab content loading for first time.
The image load event is kind of broken. To know when images are loaded you will have to observe the DOM for changes. Then on every change, you have to fetch all the new images 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.
One way to listen to DOM changes is the MutationObserver event. This is supported by all modern browsers and IE11.
A better supported solution (IE9 and up) 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).
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.
In your case you only want to fire your callback when all images are loaded, so I added a check if there's still images without the data-loaded attribute. If you remove that if-clause your callback would run after each image is loaded.
// Observe the DOM for changes
observeDOM(document.body, function(){
checkNewImages();
});
var checkNewImages = function() {
var images = $('img:not([data-loaded]').each(function() {
addImageLoadedEvent( this );
});
}
var addImageLoadedEvent = function(img) {
if (img.complete) {
onImageLoaded(img);
} else {
$(img).on('load', function() {
onImageLoaded(this);
});
}
}
// The callback that is fired once an element is loaded
var onImagesLoaded = function(img) {
$(img).attr('data-loaded', 'true');
if($('img:not([data-loaded])').length === 0) {
// YourCallbackHere();
}
}
DEMO: fire event on all images loaded
You can call your animateHeight function as each image in the loaded HTML is in turn loaded. You can expand this selection if you have other objects like videos.
// Call animateHeight as each image loads
var items = $('img', $contentHolder);
items.bind('load', function(){
animateHeight();
});
Updated demo: http://jsfiddle.net/jxxrhvvz/1/
Related
I want to hide a spinner div once ALL elements are loaded and in position on my page. I put a fadeOut() function on my spinner div in the window.on('load', ..., but I can see the tab/page is still loading even though the elements/assets are not in the correct css position yet. How do I force the spinner div to remain until everything is in place, i.e. until the loading icon on the tab is finished spinning?
This is my code:
$(window).load(function() {
$('#spinner').fadeOut();
}
jQuery(document).ready(function($){
// Append the spinner div.
$("#spinner").append(spinner.el);
}
It sounds like you have a large volume of CSS and it is taking a long time for the browser to compute the style for each element after all content for the page has loaded. You could do some experiments using your timeout idea, and polling one or more elements on the page to see when the computed style matches the expected style. The last element to be assigned a computed style might vary at each page load, and/or by browser, so you would definitely need to test your method. The example below uses some information from the accepted answer here to poll an element for an expected style.
var expectedTop="5px";
function ready() {
$('#spinner').fadeOut();
}
function poll() {
var o = document.getElementById("pollElementId");
var comp = o.currentStyle || getComputedStyle(o,null);
if(comp.top==expectedTop) {
ready();
}
else {
setTimeout("poll()",500);
}
}
jQuery(document).ready(function($){
$("#spinner").append(spinner.el);
poll();
}
Here pollElementId is the id of an element in the DOM that we are positioning via CSS.
I have a situation where I must wait for a Specific image to load, and then either swap out its src, or locate the next image and hide/show it.
What I need to happen is show a placeholder image (silhouette) until its main image is ready, and then hide the silhouette and show the main image. Very common stuff.
Problem is this jquery function does not fire on a new tab, or window... but if I hit f5 it works perfectly... but then again I open a new tab, and it wont fire until I hit f5.
CSS:
.staffImage1, .staffImage2, .staffImage3, .staffImage4, .staffImage5 { display: none; }
Jquery:
$('.staffImage1, .staffImage2,.staffImage3,.staffImage4, .staffImage5')
.load(function () {
$(this).next('.sillhouette').hide();
$(this).show();
console.log("function fired")
})
I get the log message only after refresh.
Something to be aware of is I am using the "First 14k" method to increase page speed, so maybe jquery just is not ready when the images are initially loaded the first time, but are cached and work after f5?
Each image must wait until its fully loaded, they are in a slider, so I need to show the first slides image as soon as its ready,I cannot wait until all 5 images are ready, as that would slow down the first slides image.
Any advice is appreciated, thank you
This structure:
$('.staffImage1, .staffImage2,.staffImage3,.staffImage4, .staffImage5').load(...)
does not work to notify you when all the images have been loaded. .load() only works on a single image at a time. And, if the images are cached, they may already have finished loading before your jQuery even runs so you would miss the load event entirely.
The simplest work-around is to use the window load event when all page resources have finished loading:
$(window).load(function() {
// all images are loaded here
});
It is also possible to monitor just those 5 images, but that is more work. I've written code to do this before so I'll see if I can find that prior code.
Here's a jQuery plug-in function that monitors just specific images. It will call its callback when all the images in the jQuery object are loaded:
// call the callback when all images have been loaded
// if all images are already loaded or there were no images in the jQuery
// object, then the callback will be called immediately
jQuery.fn.imgsLoaded = function(fn) {
var cntRemaining = 0;
function checkDone() {
if (cntRemaining === 0) {
fn();
}
}
function imgDone() {
--cntRemaining;
checkDone();
// remove event handlers to kill closure when done
$(this).off("load error abort", imgDone);
}
this.each(function() {
if (!this.tagName.toLowerCase() === "img" && !this.complete && this.src) {
++cntRemaining;
$(this).on("load error abort", imgDone);
}
});
checkDone();
return this;
}
You could use it like this:
$('.staffImage1, .staffImage2,.staffImage3,.staffImage4, .staffImage5').imgsLoaded(function () {
$(this).next('.sillhouette').hide();
$(this).show();
console.log("function fired")
});
Working demo: http://jsfiddle.net/jfriend00/zaoweyoo/
Write jquery code
'$(document).ready(function(){
//your code
});'
I inherited a project where a page is loaded, then code attached to that page fills in a div with dynamically generated html - it basically fills an existing div with a html string.
This string contains links to images, etc.
I want to tell when all the images, etc have loaded- I cannot seem to get any jQuery standard checks
to work - ie I have tried attaching $(window).load() after the dynamic stuff has been inserted.
I am wondering if I should write $(window).load() dynamically as well, or if there is any other
method- ie $("#thediv").load (doesn't seem to work. I cannot query all the new html for image tags, etc-
too much stuff is being put in.
The $(window).load() doesn't work for dynamic content as far as I know. You can use the .load event for each image separated. Here's an example:
var container = $("<div> ... Stuff ... </div>");
var images = container.find('img');
var imageIdx = 0;
images.load(function(){
imageIdx++;
if (imageIdx == images.length){
callback();
}
});
Where callback() is the function that runs after all images where loaded.
From my comment: window load applies to the initial page load only. Not dynamic loading of content within it. Attach load handlers to each loaded image element and count them.
This is the shortest version I could come up with for you:
// After HTML load finishes
var img = 0;
var imgCount = $("#thediv img").load(function(){
if (++img == imgCount){
// We are done loading all images!
}
}).length;
$(window).ready() only applies to the content within the HTML file and you can only use load to attach an onload event handler to a specific image (not a container), something like this might work for you.
window.ImageLoadHandled = false;
window.ImageLoadCount = 0;
function ImageLoadHandler() {
// guard against calling this function twice
if(window.ImageLoadHandled) return;
window.ImageLoadHandled = true;
// All images have loaded || timeout expired...
}
$("#myAjaxedDiv img").load( function() {
window.ImageLoadCount++;
if( window.ImageLoadCount == $("#myAjaxedDiv img").length ) {
// all images in #myAjaxedDiv have loaded
ImageLoadHandler();
}
});
// if images haven't loaded after 5 seconds, call the code
setTimeout( ImageLoadHandler, 5000 )
The only problem with this is that if an image fails to load for whatever reason, the code will never be hit, which is quite risky. To counteract this I'd recommend creating a setTimeout() method to call your code after a few seconds timeout in-case there is a problem loading images (client or server side) and I've also taken #TrueBlueAussie's correction into account in the edit.
Your alternative is to preload the images with your HTML page
had this niggling issue that i cant seem to figure out.
I have a blog post on a CMS that i am building and there is some content saved into a div with it own unique ID. When the user clicks an edit button, a CKeditor is shown (containing the same text as the div). I also display a save button which when clicked, calls the processing PHP script via AJAX.
On a database update success, i use this in my AJAX call:
if (response.databaseSuccess) {
$("#container #" +response.postid).load("#container #" +response.postContentID);
}
This works perfectly and loads the updated content into the div.
Now the issue...
On page load i use this:
$(document).ready(function () {
// check each image in the .blogtest divs for their width. If its less than X make it full size, if not its poor and keep it normal
function resize() {
var box = $(".blogtest");
box.find("img.buildimage").on('load', function () {
var img = $(this),
width = img.width();
if (width >= 650) {
img.addClass("buildimage-large");
} else if (width < 500 && width > 101) {
img.addClass("buildimage-small");
}
// if image is less than X, its most likely a smiley
else if (width < 100) {
img.addClass("buildimage-smiley");
}
}).filter(function () {
//if the image is already loaded manually trigger the event
return this.complete;
}).trigger('load');
}
resize();
});
This works, and checks the images for their width and acts accordingly. After the page has fully loaded the images correctly get given their new class which changes their width.
The problem is that i cannot get this function to work on the data that is saved. So when i click save and the content is loaded via .load(), the new images are not checked.
I have tried adding the above function into the AJAX success return but it doesnt do anything.
Any ideas?
If you are trying to hook into the onload event for images that have already been added to the page, it is very easy to miss the onload event, particularly if the image is already in the browser cache (and thus will load quickly) as the onload event may have already fired before you get a chance to attach your event handler. The usual work-around is to do something like this where you check to see if it's already loaded before attaching an onload handler:
box.find("img.buildimage").each(function() {
if (this.complete) {
// image already loaded so just process it here
} else {
// image not yet loaded so attach an onload handler
$(this).on("load", function() {
// now the image is loaded so process it here
});
}
});
I'm not sure exactly what code you're using to dynamically load new content. If you're doing that with Ajax, you need to make sure you don't fire the above code until after the content has been added to the page (the success or completion handler of whatever load operation you're using).
So, if this is where you're loading new content:
if (response.databaseSuccess) {
$("#container #" +response.postid).load("#container #" +response.postContentID);
}
then, you would use a completion handler callback on the .load() function to trigger the above code:
if (response.databaseSuccess) {
$("#container #" +response.postid).load("#container #" +response.postContentID, function() {
// code here that looks at the dynamically loaded content
});
}
Does the IFRAME's onload event fire when the HTML has fully downloaded, or only when all dependent elements load as well? (css/js/img)
The latter: <body onload= fires only when all dependent elements (css/js/img) have been loaded as well.
If you want to run JavaScript code when the HTML has been loaded, do this at the end of your HTML:
<script>alert('HTML loaded.')</script></body></html>
Here is a relevant e-mail thread about the difference between load and ready (jQuery supports both).
The above answer (using onload event) is correct, however in certain cases this seems to misbehave. Especially when dynamically generating a print template for web-content.
I try to print certain contents of a page by creating a dynamic iframe and printing it. If it contains images i cant get it to fire when the images are loaded. It always fires too soon when the images are still loading resulting in a incomplete print:
function printElement(jqElement){
if (!$("#printframe").length){
$("body").append('<iframe id="printframe" name="printframe" style="height: 0px; width: 0px; position: absolute" />');
}
var printframe = $("#printframe")[0].contentWindow;
printframe.document.open();
printframe.document.write('<html><head></head><body onload="window.focus(); window.print()">');
printframe.document.write(jqElement[0].innerHTML);
printframe.document.write('</body></html>');
// printframe.document.body.onload = function(){
// printframe.focus();
// printframe.print();
// };
printframe.document.close();
// printframe.focus();
// printframe.print();
// printframe.document.body.onload = function()...
}
as you can see i tried out several methods to bind the onload handler... in any case it will fire too early. I know that because the browser print preview (google chrome) contains broken images. When I cancel the print and call that function again (images are now cached) the print preview is fine.
... fortunately i found a solution. not pretty but suitable. What it does that it scans the subtree for 'img' tags and checking the 'complete' state of those. if uncomplete it delays a recheck after 250ms.
function delayUntilImgComplete(element, func){
var images = element.find('img');
var complete = true;
$.each(images, function(index, image){
if (!image.complete) complete = false;
});
if (complete) func();
else setTimeout(function(){
delayUntilImgComplete(element, func);}
, 250);
}
function printElement(jqElement){
delayUntilImgComplete(jqElement, function(){
if (!$("#printframe").length){
$("body").append('<iframe id="printframe" name="printframe" style="height: 0px; width: 0px; position: absolute" />');
}
var printframe = $("#printframe")[0].contentWindow;
printframe.document.open();
printframe.document.write(jqElement[0].innerHTML);
printframe.document.close();
printframe.focus();
printframe.print();
});
}
Just when the html loads, not the dependent elements. (or so I think).
To fire when the rest of the page loads do jQuery(window).load(function(){ or window.onload not document onready.
You can also check to see if an image element is loaded and there... if image . load-- etc.