I have a newbie question about running JavaScript on my website, and how images load.
Essentially, I developed a page which will generate a simple, on-screen invoice for a small business owner. The owner can go to the page, click a button, and they are asked for a variety of inputs to populate the invoice. They're asked to enter an image for their logo, a list of work they did (& what they charged), contact information at the bottom, and more.
It's primitive and I know there's many services out there that do this professionally, but I own a small business & wanted to learn HTML & JS.
My particular issue is about how the logo image displays when the user inputs the URL. The JS prompts for the logo URL after the user clicks the start button. Then the script asks the user if they're happy with the image as displayed (i.e. shape & size).
When I load the site on IE11, the image displays as soon as the user enters the URL. This way, they can see it immediately & decide if they're happy with it or not. The problem is, when I run it on Google Chrome, the image doesn't load until the entirety of the invoice-generation program has run. In other words, the image doesn't display until all the JS scripts that make up the program are executed.
Needless to say this limits my users to one attempt at displaying their logo since they don't see it on-screen until after the program is over. Why does it display immediately in IE 11 but not Chrome, and how can I get it to display immediately in Chrome (if possible)?
The link to the page is Generate Invoice
Here is the JS code that prompts for the image, displays it and re-sizes it as the user likes.
var url = prompt("Enter the URL for a picture of your logo:");
// create the IMG element to display the logo
var image = document.createElement("IMG");
// assign the user-input url to the variable holding the IMG element
image.src=url;
// Create a variable to assign the DIV to (by ID)
var imageLogo = document.getElementById("addLogoAndMessage");
// Clear out the DIV (i.e. make the button go away)
imageLogo.innerHTML = "";
// Append the image to the variable associated with the DIV
imageLogo.appendChild(image);
// custom width for CE logo is 220px
var logoWidth = prompt("Set a width for your logo.");
var logoWidth1 = logoWidth + "px";
imageLogo.appendChild(image).style.width = logoWidth1;
// custom height for CE logo is 200px
var logoHeight = prompt("Set a height for your logo. To maintain aspect ratio, just hit Enter.");
var logoHeight1 = logoHeight + "px";
if (logoHeight == "" || logoHeight == "undefined") {
imageLogo.appendChild(image).style.height = "auto";
} else {
imageLogo.appendChild(image).style.height = logoHeight1;
}
var sizeOK = prompt("Are you happy with the shape and size of your logo (y/n)?");
while (sizeOK != "n" && sizeOK != "y") {
sizeOK = prompt("Are you happy with the shape and size of your logo (y/n)?");
}
while (sizeOK == "n") {
var logoWidth = prompt("Set a new width for your logo.");
var logoWidth1 = logoWidth + "px";
imageLogo.appendChild(image).style.width = logoWidth1;
var logoHeight = prompt("Set a new height for your logo. To maintain aspect ratio, just hit Enter.");
var logoHeight1 = logoHeight + "px";
if (logoHeight == "" || logoHeight == "undefined") {
imageLogo.appendChild(image).style.height = "auto";
} else {
imageLogo.appendChild(image).style.height = logoHeight1;
}
// Ask after every width,height set so user can stop when they like the image
sizeOK = prompt("If you're still not happy with your logo, enter 'n'. Otherwise, just hit Enter.");
}
Thank you for any thoughts or input.
It could be that Chrome waits for the image to finish loading before it performs a document reflow ( = before it updates the layout). Because the JavaScript is executed much faster than the image could have possibly loaded, you won't see the image until the JS has finished.
You could try attaching an onload event to the image that fires when it has finished loading. So, use
image.onload = imageHasLoaded;
image.src = url;
And then place the rest of the code within a seperate function
function imageHasLoaded() {
//the rest of your code, to be executed once the image is displayed
}
Hope that helps!
Related
I encounter a problem while performing changes on an img element via javascript:
I build a framework to cycle through different images via the arrow keys of the keyboard. I do this by loading all image urls into an array and then changing the src attribute of the img element accordingly.
So far everything works fine. But now I want to display the naturalHeight and naturalWidth of the current image. Unfortunately when I cycle through the images the sizes of the image preceeding the current image is displayed, although the element shows the correct image.
has this something to do with load order and rendering?
I would be very thankful if someone could help me on that issue.
best regards
Max
On the comments:
I simply load the images by:
imageLeft.setAttribute("src", imagesOld[rowCounter]);
imageRight.setAttribute("src", imagesNew[rowCounter]);
I have a function for updating the size information:
function updateSizeInformation() {
var imageLeftX = $find("<%= txtXImageLeft.ClientID %>");
var imageLeftY = $find("<%= txtYImageLeft.ClientID %>");
var imageRightX = $find("<%= txtXImageRight.ClientID %>");
var imageRightY = $find("<%= txtYImageRight.ClientID %>");
var imageLeft = document.getElementById("imageLeft");
var imageRight = document.getElementById("imageRight");
imageLeftX.set_value(imageLeft.naturalWidth);
imageLeftY.set_value(imageLeft.naturalHeight);
imageRightX.set_value(imageRight.naturalWidth);
imageRightY.set_value(imageRight.naturalHeight);
}
And a function to fit the image to the parent div:
function fitImagesToContainers() {
var divLeft = document.getElementById("imageContainerLeft");
var divRight = document.getElementById("imageContainerRight");
var imageLeft = document.getElementById("imageLeft");
var imageRight = document.getElementById("imageRight");
if (imageLeft.naturalWidth > imageLeft.naturalHeight) {
imageLeft.setAttribute("width", divLeft.clientWidth);
imageLeft.setAttribute("height", divLeft.clientWidth * (imageLeft.naturalHeight / imageLeft.naturalWidth));
} else if (imageLeft.naturalWidth < imageLeft.naturalHeight) {
imageLeft.setAttribute("height", divLeft.clientHeight);
imageLeft.setAttribute("width", divLeft.clientHeight * (imageLeft.naturalWidth / imageLeft.naturalHeight));
}
}
When you set the "src" attribute of an image element the page has not loaded, it normally instigates an asynchronous retrieval of the image file resource. After the image onload event fires the image element properties should represent that of the retrieved image, but be unpredictable before then.
If the code you provided executes synchronously it will run into the problem you report.
In Mozilla Firefox (at least) the image attributes can be that of the previous image if inspected immediately in the same script execution, because the new/next image has yet to be retrieved. Exactly what happens if the page has previously loaded the same image may be unpredictable - I found it giving correct dimension values immediately.
I tested this with standard javascript and do not wish to suggest how to program onload event handlers in jQuery . I did come across this other StackOerflow question on javascript, image onload() doesnt fire in webkit if loading same image which may be of interest.
For a mockup-webpage used for research on interaction on websites, I created a mockup message-stream using JavaScript. This message stream is loaded in an IFrame and should show images at pre-set intervals and scroll to the bottom of the page after placing a new image at the bottom of the page. Getting the images to appear is working quite well with the provided script. However, both Chrome and IE seem to have trouble scrolling the page to the bottom. I would like to scroll to the bottom of the page as soon as the image is attached, but have for now added a 5 ms delay because that seemed to work sometimes. My questions are:
Is it okay to use document.body.scrollHeight for this purpose?
Can I make the scroll occur directly, or do I need a small interval before scrolling?
How to make the code scroll to the bottom of the IFrame directly after adding an image?
The following functions are used and trypost() is started onLoad:
function scrollToBottom(){
window.scrollBy(0,document.body.scrollHeight);
}
function trypost(){
point = point + 1;
if(point < interval.length){
//create and append a new image
var newImg = document.createElement("IMG");
newImg.src = "images/"+images[point]+".png";
document.getElementById('holder').appendChild(newImg);
//create and append a return
var br = document.createElement("br");
document.getElementById('holder').appendChild(br);
//time scroll to bottom (after an arbitrary 5 seconds)
var stb = window.setTimeout(scrollToBottom, 5);
//time next post
var nextupdate = interval[point]*400;
var tp = window.setTimeout(trypost, nextupdate);
}
}
My script section contains at least the following variables:
var point = -1;
var interval = [10, 10, 15];
var images = ["r1", "a1", "r2"];
This questions is a continuation of the project described in How to proper use setTimeout with IE?
To answer one of your questions, document.body.scrollHeight is appropriate for this purpose, but not if you're actually calling for document. That'll give you the scroll height of the document the iFrame is in, not the iFrame's document. The iFrame's document can be called upon by [insert variable for iFrame here].contentDocument.
Here's how I did it (and by that, I mean I tested it out with my own stuff to make sure it worked):
let i = document.querySelector('iframe')
i.contentWindow.scrollTo(0, i.contentDocument.body.scrollHeight);
That being said, the other answer by Thomas Urban will also work most of the time. The difference is only if your page has a really long scroll height. Most pages won't be longer than 999999 (for all I know that's impossible and that's why they chose that number), but if you have a page longer than that, the method I showed here would scroll to the bottom and the 999999 would scroll to somewhere not yet at the bottom.
Also note, if you have more than one iFrame, you're gonna want to query it in a different way than I did, like by ID.
Scrolling to bottom is always like scrolling to some ridiculously large top offset, e.g. 999999.
iframe.contentWindow.scrollTo( 0, 999999 );
In addition see this post: Scrolling an iframe with javascript?
If scrolling occurs too early it's probably due to images not being loaded yet. Thus, you will have to scroll as soon as added image has been loaded rather than on having placed it. Add
newImg.onload = function() { triggerScrolling(); };
after creating newImg, but before assigning property src.
If several events are required to trigger scrolling you might need to use some "event collector".
function getEventCollector( start, trigger ) {
return function() {
if ( --start == 0 ) { trigger(); )
};
}
You can then use it like this:
var collector = getEventCollector( 2, function() { triggerScrolling(); } );
newImg.onload = collector;
window.setTimeout( collector, 100 );
This way triggerScrolling() is invoked after 100ms at least and after image has been loaded for collector has to be invoked twice for triggerScrolling() being invoked eventually.
I'm making a html-5 based report generator. I created a button to upload a [HTML] page containing multiple paragraphs and tables, which is continuous.
Now my task is to display the whole contents into separated a4-sized pages, just like in Microsoft Word.]
This is the sketch: >>>LINK<<<
Here are part of my codes.
function xx (){
var fi = document.getElementById('fi').files[0];
reader.onload = function (e){
var reader = new FileReader();
var inner ="";
inner += this.result;
inn.innerHTML ="<center><div class='bg' id='0'><div id='testmain'>"+inner+"</div></div></center>";
}
reader.onerror = function (e){
dd.innerHTML = "error<br>";
}
reader.readAsText(fi);
}
After displaying the result of pages, users can click a specific part of the paper, just like a paragraph, then a pagebreak is created and the pages changes, the remaining content are pushed starting from top of next page.
Could you please give me some ideas about how to realize it?
Instead of using comments as chat to present my suggestion, here's my answer:
I once tried to do such a thing, back in html4. Here's the logic I was using. Create a div that has the exact size of your page CONTENT (after margins and all) put all your content in it and cycle through its direct children. If the current child's bottom is lower than his parent, take it and all the following children and put them in a new div CONTENT. Rinse and repeat.
For this, you will need to calculate the height of the container and cross-check it against the offset+height of the elements. My vanillaJS is a bit rusty as for browser specifics and all... So I will display the logic using jQuery but most of it can easily be made in pure JS. The code will assume that we have a div.page that has the right CSS to make it exactly the size of a content page, and that will not resize to content (overflow:hidden) and the document will contain one of those div with all the content of what should be in the pages...
$(document).ready(function(){
var $page = $('div.page');
var newPage = true;//To track if we loop
while(newPage){
newPage = false;
$page.children().each(function(){
if($(this).offset().top+$(this).outerHeight() > $page.offset().top+$page.height()){
$page = $('<div>').addClass('page').appendTo('body');
$(this).nextAll().appendTo($page);
$(this).prependTo($page);//Don't forget the element too.
newPage = true;
}
});
}
});
I've built a modal image gallery. When the user clicks next to see next image I run something like:
document.getElementById('gallery-image').src = newSRC;
My problem is that there is a delay from when the user clicks next to where the image actually changes. (due to the new image being loaded). I want to "empty" the image element the moment the user clicks next. I tried doing this:
document.getElementById('gallery-image').src = '';
setTimeout(function(){
document.getElementById('gallery-image').src = newSRC;
}, 100);
to no avail.
How can I "empty" the IMG element until the new image starts loading on it?
There are three main ways to do this. Your question seemed to suggest you want to create an effect similar to a slide projector changing slides, so I have written this answer with that in mind.
Temporarily set the src property to a 1x1 transparent GIF instead of the empty string. This is most similar to what you tried to do and perhaps is the simplest method.
The data URI below is from Blank image encoded as data-uri and should work in Internet Explorer 8 or higher and the other major browsers. Using a data URI avoids the need to store a separate file on your web server and preload it when your page loads. You may need to set the width and height attributes of the img element to preserve the layout of your page.
This code does not preload the image at the start of the time delay (although it would be possible to do so), thus the apparent delay to the viewer is the sum of the programmed time delay and the time taken to load the image. The loading time will only be close to zero if the browser has already cached the image or if the web server is on the local network.
var gi = document.getElementById('gallery-image');
gi.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
setTimeout(function(){
document.getElementById('gallery-image').src = newSRC;
}, 100);
Temporarily set display: none;, and use an onload function to undo the change once the time delay has elapsed and the image has loaded. The code below starts loading the image at the start of the time delay, which depending on the viewer's connection speed, may result in a more consistent apparent delay.
var gi = document.getElementById('gallery-image');
var delayElapsed = false;
var imageLoaded = false;
gi.style.display = 'none';
setTimeout(function(){
delayElapsed = true;
maybeShowImage();
}, 100);
gi.onload = function() {
imageLoaded = true;
maybeShowImage();
};
gi.src = newSRC;
function maybeShowImage() {
if (!delayElapsed || !imageLoaded) {
return;
}
gi.style.display = '';
}
Do the above, but set visibility: hidden; instead. This may have the advantage of not affecting the page layout where display: none; would.
// [...]
gi.style.visibility = 'hidden';
// [...]
function maybeShowImage() {
if (!delayElapsed || !imageLoaded) {
return;
}
gi.style.visibility = '';
}
You could hide the image and then reshow it...
// Hide
document.getElementById('gallery-image').style.display = "none";
// Show
document.getElementById('gallery-image').style.display = "";
while PhotoSwipe has been fantastic so far just these minor issues that I can't seem to get around
I initialize PhotoSwipe as follows
formPhoto.gallery = window.Code.PhotoSwipe.attach( images, options);
And inside a Gallery, a user can choose whether to delete an image or not via
Once the delete button is pushed this is run
formPhoto.gallery.cache.images.splice(e.target.currentIndex,1);
delete formPhoto.activeObj.value[e.target.originalImages[e.target.currentIndex].id];
if(formPhoto.gallery.cache.images.length == 0)
formPhoto.gallery.hide();
else
formPhoto.gallery.carousel.show( 0 );
Now this works mostly fine, except in 2 cases.
If you are below 3 photos, it breaks the slide event (on slide right) - The image slides onto a black screen. If you delete and only have 1 image left, you can't even view the image properly it just bounces back onto a black screen.
If you add images back into the gallery again, the old images that were deleted are shown again
It is reinitiated using
images = [];
for(var x in formPhoto.activeObj.value)
images.push({url: formPhoto.activeObj.value[x].file, id:x});
formPhoto.gallery = window.Code.PhotoSwipe.attach( images, options);
If you want, I can try grab a recording of whats going on. I'm not sure how to solve this, I've looked around on the https://github.com/codecomputerlove/PhotoSwipe/issues and google but nothing helpful.
All I really want to do is just remove an image from the Gallery (its viewed in Exclusive Mode only)
Ok, I ended up writing a temporary solution.. its a bit hacky, but I just manually remove the DOM from the carousel
jQuery(formPhoto.gallery.carousel.contentEl).find("[src*=\"" + formPhoto.activeObj.value[e.target.originalImages[e.target.currentIndex].id].file + "\"]").parent().remove();
//we look for the image that contains the same filename as the one we're trying to delete.
//so we just remove that.
formPhoto.gallery.cache.images.splice(e.target.currentIndex,1);
delete formPhoto.activeObj.value[e.target.originalImages[e.target.currentIndex].id];
e.target.originalImages.splice(e.target.currentIndex, 1);
formPhoto.activeObj.object.find("[type=amountadded]").html(formPhoto.activeObj.valueLength() + " photos");
if(formPhoto.gallery.cache.images.length == 0)
formPhoto.gallery.hide();
else {
//real hacky job. Atleast it looks like a real cool effect occured.
formPhoto.galleryInitiate(formPhoto.activeObj, e.target.originalImages);
}
Also fixed the issue of the images reappearing, was because the newly generated files had the same filenames. Added a date component to the file names for the mean time.
This is the handler for a delete button
function ps_delete_image(btn) {
var inst = PhotoSwipe.instances[0];
var curImg = $photoSwipe.getCurrentImage();
inst.cache.images.splice(inst.currentIndex, 1);
inst.originalImages.splice(inst.currentIndex, 1);
if(inst.cache.images.length == 0) inst.hide();
else {
if (inst.currentIndex == inst.cache.images.length) inst.carousel.show(inst.currentIndex - 1);
else inst.carousel.show(inst.currentIndex);
}
// remove delete button if 3 or less is left
if(inst.cache.images.length <= 3) {
$(btn).remove();
}
}
To overcome issue with 3 and less images I just remove delete button.