I have seen posts asking how to display an image selected via a file input control. When I try these solutions, they work, yet I have a further issue I am unable to discover an answer to on my own: When I select an image for the first time, the image will not display because no image width and height data is available (image data is available via the FileReader obj, yet not the image width and height). The second time the image is selected (or after a reload), the width and height become available. Why is this occurring and what can I do to fix it? thanks.
Here's some code I've been using - this is one of the FileReader functions. I started by using reader.onload, but then thought the image width and height were not available because it hadn't loaded fully yet. Therefore onloadend seemed a better choice. It doesn't work any better here, though:
reader.onloadend = function(e) {
var img = new Image();
img.src = e.target.result;// or reader.result - doesn't matter
// A function that does math to proportionally scale the image
var new_size = scaleImageSize(150, 150, img.width, img.height);
img.width = new_size[0];
img.height = new_size[1];
// And here I am trying both ways to display the image: the first
// in a div tag, the second straight into an img tag. I only want
// to use one of these - preferably the div tag. I have also tried
// drawing the image onto an HTML5 canvas. All to no avail.
document.getElementById("file_display_area").appendChild(img);
document.getElementById("img_file_display_area").src = img.src;
console.log(img.width + '|' + img.height + '||' + new_size[0] + '|' + new_size[1]);
}
I have an Html/JavaScript application that contains N columns that need to be large enough contain all of the possible LI elements from all of the columns.
The simple solution seems to count the heights of all of the items in each column, compensate for padding, and then set the height to that total for each of the columns.
This works great when the LI elements contain plain text. Unfortunately, when the LI elements contain images instead, various browsers have problems. For example, when I first load the page in FireFox, it looks like the screenshot below, but upon another refresh, it works fine. It doesn't work as expected in Chrome either.
My application does not pre-populate the LI elements when the page loads - it uses JavaScript, as follows:
function populateUnsetAnswers(unsetCategoryAnswers) {
for (i in unsetCategoryAnswers) {
if (unsetCategoryAnswers.hasOwnProperty(i.toString())) {
$('#categoryQuestionArea #possibleAnswers').append(
categoryAnswerLiTag(unsetCategoryAnswers[i])
);
}
}
}
function categoryAnswerLiTag(unsetCategoryAnswer) {
var html = '<li id="' + unsetCategoryAnswer.id + '">';
if (unsetCategoryAnswer.image) {
html += '<img class="categoryAnswerImage" title="';
html += unsetCategoryAnswer.text;
html += '" src="/trainingdividend/rest/streaming/';
html += unsetCategoryAnswer.image.fileName;
html += '" style="height: ';
html += unsetCategoryAnswer.image.height;
html += ';';
html += '" />';
} else {
html += unsetCategoryAnswer.text
}
html += '</li>';
return html;
}
When the page is done loading, an ajax request fetches all of the objects to be put into LI elements, and then calls the first function above.
After all of the LI elements are created, I call this function right after it:
function resize() {
var currentHeight, totalHeight;
totalHeight = 0;
$("#categoryQuestionArea ul").children().each(function() {
currentHeight = $(this).height();
totalHeight += currentHeight + 13;
});
$("#categoryQuestionArea ul").height(totalHeight);
$("#categoryQuestionArea div#separator").css("padding-top", (totalHeight / 2) + "px");
}
Is there any way to tell jQuery, "Don't call resize() until all of the LI's are fully loaded and the images have rendered" ?
I think what's happening is that on the initial page load, the height of these LI elements is 0 or a small value because it doesn't contain the image, so my resize function is calculating the wrong result (I tested this with some alert statements). As long as the LIs are populated and the images have loaded, the total height is calculated just fine.
Any help? Thanks
To literally answer the question you asked, if you want to only call resize() when all images have finished loading, then you need to install onload handlers for those images and when you've recorded that the last one is now loaded, you can call the resize() function. You could do that like this (code explanation below):
var remainingAnswerImages = 0;
function categoryAnswerImageLoadHandler() {
--remainingAnswerImages;
if (remainingAnswerImages === 0) {
resize();
}
}
function populateUnsetAnswers(unsetCategoryAnswers) {
// add one extra to the image count so we won't have any chance
// at getting to zero before loading all the images
++remainingAnswerImages;
var possibleAnswers$ = $('#categoryQuestionArea #possibleAnswers');
for (i in unsetCategoryAnswers) {
if (unsetCategoryAnswers.hasOwnProperty(i.toString())) {
possibleAnswers$.append(categoryAnswerLiTag(unsetCategoryAnswers[i]));
}
}
// remove the one extra
--remainingAnswerImages;
// if we hit zero on the count, then there either were no images
// or all of them loaded immediately from the cache
// if the count isn't zero here, then the
// categoryAnswerImageLoadHandler() function will detect when it does hit zero
if (remainingAnswerImages === 0) {
resize();
}
}
function categoryAnswerLiTag(unsetCategoryAnswer) {
var obj = document.createElement("li");
obj.id = unsetCategoryAnswer.id;
if (unsetCategoryAnswer.image) {
// count this image
++remainingAnswerImages;
var img = new Image();
img.onload = img.onerror = img.onabort = categoryAnswerImageLoadHandler;
img.title = unsetCategoryAnswer.text;
img.style.height = unsetCategoryAnswer.image.height;
img.src = "/trainingdividend/rest/streaming/" + unsetCategoryAnswer.image.fileName;
obj.appendChild(img);
} else {
obj.innerHTML = unsetCategoryAnswer.text;
}
return obj;
}
By way of explanation, this code makes the following changes:
Add a variable remainingAnswerImages to keep track of how many images still need to be loaded.
Add an onload handler for each <img> tag that is created so we can keep track of when it's loaded.
Each time we generate the HTML for an tag with the onload handler, increment remainingAnswerImages.
When you're done adding all the HTML, check the remainingAnswerImages count to see if it's zero (this would only be the case if there were no images or if all images loaded immediately from the browser cache). If so, call resize() immediately.
In the onload handler which will be called for each image, decrement remainingAnswerImages and if the count has reached zero, call resize().
While adding images, add one extra to remainingAnswerImages as a gate to keep from getting to a zero count until we're done adding images. When done adding images, take that one extra out.
I also rewrote the categoryAnswerLiTag() function to just create the DOM objects directly rather than concat a bunch of strings together into HTML. In this case, the code is a lot cleaner to read and maintain.
I also moved the $('#categoryQuestionArea #possibleAnswers') out of your for loop since it resolves to the same thing every time. Better to do it once before the loop. Also, in most cases, this could be simplified to $('#possibleAnswers') since ids are supposed to be unique in the page.
Here is a jquery plugin that checks the images have loaded: https://github.com/alexanderdickson/waitForImages
Sample usage for your case would be:
$('#categoryQuestionArea').waitForImages(function() {
resize();
});
I would also just check for the total height of the <ul> instead of looping through the list items as you would have to manually change the script if either the padding, margins, or borders on the list items changes later on.
If you do have problems with images on the first page load maybe it is due to the fact that they are not cached and therefore not immediately available. So measuring their height will lead to bad results... did you debug the height that was fetched via jQuery (for example
currentHeight = $(this).height();
console.log(currentHeight);
The only way to do that is I think to observe the load events of all images (and probably the error as well) and count whether all request have been finished
try using
$('img').load(function(){
//put code here
});
I guess your HTML is screwed up. Particularly, your <img> tags.
Add the width and height attributes to your <img> tags. Everything will be magically solved.
See this jsfiddle to understand what I mean: http://jsfiddle.net/Ralt/Vwg7P/
Even though there is no image in there, the width and height attributes will occupy the space required for the image. As soon as the DOM is loaded.
This is rather a CSS problem, most probably due a fixed height, with items either floated or absolutely positioned.
There are number of ways to fix this.
Give a min-height instead of fixing a height.
#container { min-height: 100px; }
Clear the float and do not set any heights
#container { overflow: hidden; }
Use Scripts to add up to the height, once every element is added. Like the jQuery snippet below
$("#container").append($("#theimg"));
$("#container").height($("#container").height()+$("#theimg").height());
I think I might have a solution for you.
The main idea of my solution lies in CSS. You want to have 3 columns of the same height, right? You can have something like this: http://jsfiddle.net/agilius/NvzZp/46/
There is quite a lot of CSS there, but the main idea is this:
I simulate a 3 column layout under the actual content, with the .inner and .column classes.
The content is placed over (via z-index 2 > .inner zindex 1), with the same width as the columns that are under.
When content is added to the content zones, the height of the main #container updates.
Since .inner is top,left,right,bottom = 0, it updates, and since the .columns have 100% height, they update their height to match the #containers height.
Observations.
You can have padding, borders, margins in the .column class as you see fit.
No javascript is required.
Another simple Equal Height CSS solution:
LOGIC is very simple -
all of the columns/LI are floated
with .eH{ padding-bottom: X; margin-bottom: -X } and
wrapper/UL is .eW{overflow: hidden}
X= large arbitrary amount of px for factor of safety
EXAMPLE:
http://jsfiddle.net/rahen/TXVYD/4/
This sounds exactly like one of the problems i had when coding SudoSlider.
Below i've copied the code i solved it with. Just call autoheightwidth(i, 0, true) inside your resize() function.
The basic idea is that you do not know when the browser has completed loading the images, so instead of relying on a single height adjustment, you adjust the height every time something happens (mostly just that an image has been loaded).
It should work if you change the references of "obj" and "li" in the first 2 methods.
It's not very readable, but there was a big focus on size when i coded it.
// Automaticly adjust the height and width, i love this function.
// Before i had one function for adjusting height, and one for width.
function autoheightwidth(i, speed, axis) // Axis: true == height, false == width.
{
obj.ready(function() {// Not using .load(), because that only triggers when something is loaded.
adjustHeightWidth (i, speed, axis);
// Then i run it again after the images has been loaded. (If any)
// I know everything should be loaded, but just in case.
runOnImagesLoaded (li.eq(i), falsev, function(){
adjustHeightWidth (i, speed, axis);
});
});
};
function adjustHeightWidth (i, speed, axis)
{
var i = getRealPos(i); // I assume that the continuous clones, and the original element is the same height. So i allways adjust acording to the original element.
var target = li.eq(i);
// First i run it. In case there are no images to be loaded.
var b = target[axis ? "height" : "width"]();
obj.animate(
axis ? {height : b} : {width : b},
{
queue:falsev,
duration:speed,
easing:option[8]/*ease*/
}
);
}
function runOnImagesLoaded (target, allSlides, callback) // This function have to be rock stable, cause i use it ALL the time!
{
var elems = target.add(target.find('img')).filter('img');
var len = elems.length;
if (!len)
{
callback();
// No need to do anything else.
return this;
}
function loadFunction(that)
{
$(that).unbind('load').unbind('error');
// Webkit/Chrome (not sure) fix.
if (that.naturalHeight && !that.clientHeight)
{
$(that).height(that.naturalHeight).width(that.naturalWidth);
}
if (allSlides)
{
len--;
if (len == 0)
{
callback();
}
}
else
{
callback();
}
}
elems.each(function(){
var that = this;
$(that).load(function () {
loadFunction(that);
}).error(function () {
loadFunction(that);
});
/*
* Start ugly working IE fix.
*/
if (that.readyState == "complete")
{
$(that).trigger("load");
}
else if (that.readyState)
{
// Sometimes IE doesn't fire the readystatechange, even though the readystate has been changed to complete. AARRGHH!! I HATE IE, I HATE IT, I HATE IE!
that.src = that.src; // Do not ask me why this works, ask the IE team!
}
/*
* End ugly working IE fix.
*/
else if (that.complete)
{
$(that).trigger("load");
}
else if (that.complete === undefined)
{
var src = that.src;
// webkit hack from http://groups.google.com/group/jquery-dev/browse_thread/thread/eee6ab7b2da50e1f
// data uri bypasses webkit log warning (thx doug jones)
that.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=="; // This is about the smallest image you can make.
that.src = src;
}
});
}
I think the Browser does not know the images' dimensions, because they are not loaded.
Either try to wrap the invocation of resize in a
jQuery(document).load( function funcName() {
...
} )
or give the image width and height attributes in the HTML img tags.
Maybe both
As a quick explanation, I created an image that resizes to fill the background using this function which works great:
function resize_bg(element_id){
$("#"+element_id).css("left","0");
var doc_width = $(window).width();
var doc_height = $(window).height();
var image_width = $("#"+element_id).width();
var image_height = $("#"+element_id).height();
var image_ratio = image_width/image_height;
var new_width = doc_width;
var new_height = Math.round(new_width/image_ratio);
if(new_height<doc_height){
new_height = doc_height;
new_width = Math.round(new_height*image_ratio);
var width_offset = Math.round((new_width-doc_width)/2);
$("#"+element_id).css("left","-"+width_offset+"px");
}
$("#"+element_id).width(new_width);
$("#"+element_id).height(new_height);
return true;
}
So no problem for the full background image. The problem appears, when I change the image source using Javascript. In other words, I have 1 image set as background but on hover of certain elements on the page, the image changes but it doesn't change the resize right. So the first image on load is resized and positioned correctly, but when I switch the image using .attr('src',newimg) the image is not resized correctly even though I call the function again.
Here is the code I use to change the image and resize it:
$('#menu_work li a').hover(function(){
$('#content').hide();
var img_src = $(this).next('img').attr('src');
$('#full_screen_img').attr('src', img_src );
resize_bg();
$('#full_screen_img').show();
},function(){
$('#full_screen_img').hide();
$('#content').show();
});
Thanks for any help.
It appears that you have left out the element_id argument when calling resize_bg() in the hover event handler. As a result, resize_bg() can't find the element you want to resize.
#maxedison is right, you forgot to pass the element id.
Another problem is that when you change the src, the new image might not be loaded yet, so you won't get the right dimensions in resize_bg until it is.
In that case you'll need to resize the image once it's loaded:
$('#full_screen_img').attr('src', img_src ).load(function() {
resize_bg('<ELEMENT_ID>');
});
resize_bg('<ELEMENT_ID>');
On another note, I'd recommend you change resize_bg to get a jQuery object instead of an id, or even write a plugin ($.fn.resize_bg) if it's a functionality you want to use often.
On my website, users can upload large images. I display these images like this:
<img id="userImage" src="userImage.ashx?width=740&id=4fc265d4-a83c-4069-8d6d-0fc78ae2840d">
userImage.ashx is a handler that returns image files based on id, so in this example the image for user 4fc265d4-a83c-4069-8d6d-0fc78ae2840d is returned. You can also set other attributes - in this example only width is given. The image is resized so that it is 740px wide.
I set the src of the image in javascript, once the rest of the page has loaded. By doing this I know how wide the image has to be to fill all the available space:
var width = document.getElementById("userImageHolder").getComputedSize().width;
document.getElementById("userImage").src = "flash/userImage.ashx?type=micrositePhoto&id=" + userId + "&width=" + width;
This all works, but the image doesn't load until everything else on the page has loaded. I have a complex solution to a simple problem.
Is there a better way to do this? What is the best way to shrink/stretch images to fill an area that is only known once the page loads?
Figure out what the upper limit is for width and height and generate the image to that size, then use max-width/max-height to allow the browser to auto scale it based on the size of the browser window.
Try to preload your images in a onDOMReady handler, and then insert in an onLoad one. While this can't guarantee the images to be loaded before everything else, they can at least start loading earlier.
Someting like this (using jQuery):
$(document).ready(function(){
var imageArray = [],
imageSrc = [];
//Fill image src array from somewhere
var len = imageSrc.length;
for(var i = 0; i < len; i++){
var img = new Image();
img.src = imageSrc[i];
img.onload = function(){
//Do something with your loaded image
imageArray.push(this);
}
}
});
I've got a web application that loads some content from an external source to the dom via an ajax call, one of the things that comes back is a set of images (different sizes and aspect ratios) to be displayed in an profile photo section. I'd like for each of the images to be resized to fit within a 64px x 64px area and I'd like to maintain aspect ratio. I was able to do this in firefox, chrome, and safari, but I've no luck getting this to work in IE 7 or 8. The problem I've had is finding a jquery event that reliably gets triggered after the image loads since the image was added after the page load. Here's what works in the listed browsers:
$(window).load(function () {
$('.profileThumbnail').each(function (i) {
var divHeight = $(this).height();
var divWidth = $(this).width();
if (divHeight > divWidth) {
$(this).css('height', '64px');
$(this).css('width', 'auto');
}
else {
$(this).css('height', 'auto');
$(this).css('width', '64px');
}
divHeight = $(this).height();
var divParentHeight = $(this).parent().parent().height();
var divNewHeight = (divParentHeight - divHeight) / 2;
$(this).parent().css('top', divNewHeight);
divWidth = $(this).width();
var divParentWidth = $(this).parent().parent().width();
var divNewWidth = (divParentWidth - divWidth) / 2;
$(this).parent().css('left', divNewWidth);
});
});
I'm also trying to center (horizontally and vertically) them which is what the rest of that code does, but I think I've got all of that working if I can find a way to trigger this code after the image loads in IE.
keep in mind this needs to work both on the first visit (not cached) and subsequent visits (cached). I'm looking for a jquery, javascript, or css solution as I want to avoid the roundtrip/bandwidth for each image.
Have you tired to add a load event to the images yourself which triggers when the image is loaded? This is how image preloaders work.
var img = document.createElement("img");
img.onload = function(){ alert('loaded'); }
img.onerror = function(){ alert('error'); }
img.src = "foo.png";
You can add the onload to the image elements themselves if you are not doing the preload approach.
The problem I've had is finding a jquery event that reliably gets triggered after the image loads since the image was added after the page load.
Instead of setting an onload listener for the window, set an onload listener for the images you are loading remotely. Set the listener after you create the image object and before you insert it into the body. The listener can basically be all the stuff insife of the .each() in the code you posted,