This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
jQuery callback on image load (even when the image is cached)
So the .load() method has been deprecated in jQuery. load() is a shortcut for `bind('load', function).
From http://api.jquery.com/load-event/:
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`
How does one perform a function on image load consistently? My Current code looks like so:
$(newImage).bind('load', function() {
createSlices(slide);
});
However after reading the docs (although I haven't noticed any problems) I am worried it may not fire for some users.
Try this plugin man: called waitforimages plugin: https://github.com/alexanderdickson/waitForImages (scroll down the link you will see various advanced use) - demo http://jsfiddle.net/FZ7Xy/
Hope it fits the need :)
Code
$('selector').waitForImages(function() {
// All descendant images have loaded, now slide up.
$(this).slideUp();
});
var img = new Image();
img.onload = function() {
}
img.src= src-image
the above code works on all browsers and even without jquery.
You're right to worry, IE7/8 in particular will sometimes fail to fire the load event if the image is cached.
One technique is to immediately check if the image is complete after binding the load event:
$(newImage).bind('load error', function() {
createSlices(slide);
});
// check if the image is immediately complete (cached)
if (newImage.complete || typeof newImage.complete === 'undefined') {
createSlices(slide);
}
I've tested with success in:
IE 7/8/9/10
FF4+
Chrome (latest)
Safari 5+
There is a great library that handles exactly this: https://github.com/desandro/imagesloaded
I've used it in production and it works wonderfully. Extremely reliable, not a single complaint.
Related
Some websites have lots of images, so lazyloading seems appropiate to reduce load times and data consumption. But what if you also need to support printing for that website?
I mean, you can try to detect the print event and then load the images, with something like this:
HTML
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">
Note: this is a one by one pixels gif dummy image.
JavaScript
window.addEventListener('DOMContentLoaded', () => {
img = document.querySelector('img');
var isPrinting = window.matchMedia('print');
isPrinting.addListener((media) => {
if (media.matches) {
img.src = 'http://unsplash.it/500/300/?image=705';
}
})
});
Note: if you try this in a code playground, remove the DOMContentLoaded event (or simply fork these: JSFiddle | Codepen).
Note: I didn't event bother with the onbeforeprint and onafterprint for obvious reasons.
This will work fine if the image is cached, but then again that's precisely not the point. The image/s should all load and then appear in the printing screen.
Do you have any ideas? Has anyone successfully implemented a print-ready lazyloading plugin?
Update
I've tried redirecting the user after the print dialog is detected, to a flagged version of the website a.k.a website.com?print=true where lazyloading is deactivated and all images load normally.
This method is improved by applying the window.print() method in this flagged print-ready version of the page, opening a new print dialog once all images are finished loading, and showing a "wait for it" message in the meantime at the top of the page.
Important note: this method was tested in Chrome, it does not work in Firefox nor Edge (hence why this is not an answer, but a testimony).
It works in Chrome beacuse the print dialog closes when you redirect to another website (in this case same url but flagged). In Edge and Firefox the print dialog is an actual window and it does not close it, making it pretty unusable.
Based on your desired functionality, I'm not quite sure what you want to do is feasible. As a developer we don't really have control over a users browser. Here are my thoughts as to why this isn't fully possible.
Hooking the event to go and load your missing images won't let you guarantee images will make it from the server into your page. More specifically, the PDF generated for your print preview is going to get generated before your image(s) is done loading, the img.src = "..." is asynchronous. You'd run into similar issues with onbeforeprint as well, unfortunately. Sometimes it works, sometimes it does not (example, your fiddle worked when testing in safari, but did not in Chrome)
You cannot stall or stop the print call -- you can't force the browser to wait for your image to finish loading in the lazy loading context. (I read something about using alerts to achieve this once, but it seemed really hacky to me, was more of a deterrent to printing than stalling)
You cannot force img.src to get that data synchronously in a lazy-load context. There are some methods of doing this, but they are clever hacks -- referenced as pure evil and may not work in always. I found another link with a similar approach
So we have a problem, if the images are not loaded by the time print event is fired, we cannot force the browser to wait until they are done. Sure, we can hook and go get those images on print, but as above points out, we cannot wait for those resources to load before the print preview pops up.
Potential solution (inspired by links in point three as well as this link)
You could almost get away with doing a synchronous XMLHttpRequest. Syncrhonous XMLHTTPRequests will not let you change the responseType, they are always strings. However, you could convert the string value to an arrayBuffer encode it to a base-64 encoded string, and set the src to a dataURL (see the link that referenced clever hacks) -- however, when I tried this I got an error in the jsfiddle -- so it would be possible, if things were configured correctly, in theory. I'm hesitant to say yes you can, since I wasn't able to get the fiddle working with the following (but it's a route you could explore!).
var xhr = new XMLHttpRequest();
xhr.open("GET","http://unsplash.it/500/300/?image=705",false);
xhr.send(null);
if (request.status === 200) {
//we cannot change the resposne type in synchronous XMLHTTPRequests
//we can convert the string into a dataURL though
var arr = new Uint8Array(this.response);
// Convert the int array to a binary string
// We have to use apply() as we are converting an *array*
// and String.fromCharCode() takes one or more single values, not
// an array.
var raw = String.fromCharCode.apply(null,arr);
// This is supported in modern browsers
var b64=btoa(raw);
var dataURL="data:image/jpeg;base64,"+b64;
img.src = dataURL;
}
Work around to enhance the user experience
Something you could do is have some text that only displays in the print version of your page (via #print css media) that says "images are still loading, cancel your print request and try again" and when the images are finished loading, remove that "still waiting on resources try again message" from the DOM. Farther, you could wrap your main content inside an element that inverses the display to none when content is not loaded, so all you see is that message in the print preview dialog.
Going off of the code you posted this could look something like the following (see updated jsfiddle):
CSS
.printing-not-ready-message{
display:none;
}
#media print{
.printing-not-ready-message{
display:block;
}
.do-not-print-content{
display:none;
}
}
HTML
<div class="printing-not-ready-message">
Images are still loading please cancel your preview and try again shortly.
</div>
<div class="do-not-print-content">
<h1>Welcome to my Lazy Page</h1>
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">
<p>Insert some comment about picture</p>
</div>
JavaScript
window.addEventListener('DOMContentLoaded', () => {
img = document.querySelector('img');
var isPrinting = window.matchMedia('print');
isPrinting.addListener((media) => {
if (media.matches) {
img.src = 'http://unsplash.it/500/300/?image=705';
//depending on how the lazy loading is done, the following might
//exist in some other call, should happen after all images are loaded.
//There is only 1 image in this example so this code can be called here.
img.onload = ()=>{
document.querySelector(".printing-not-ready-message").remove();
document.querySelector(".do-not-print-content").className=""
}
}
})
});
I'm the author of the vanilla-lazyload script and I've recently developed a feature that makes print of all images possible!
Tested cross browser using this repo code which is live here.
Take a look and let me know what you think!
I'm open to pull requests on GitHub of course.
I wrote a lazy loading jquery plugin that supports showing images on print using the window.onbeforeprint events and mediaQueryListeners.
https://github.com/msigley/Unveil-EX/
//declare custom onbeforeprint method
const customOnBeforePrint = () => {
const smoothScroll = (h) => {
let i = h || 0;
if (i < 200) {
setTimeout(() => {
window.scrollTo(window.scrollY, window.scrollY + i);
smoothScroll(i + 10);
}, 10);
}
};
let height = document.body.scrollHeight;
let newHeight;
while (true) {
smoothScroll(100);
if (newHeight === height) break;
height = newHeight;
}
};
//override the onbeforeprint method
window.onbeforeprint = customOnBeforePrint;
Copy&Paste that block into devtool's console and then try to click print button. That workaround is working for me.
For whoever is in the same boat as I was: when using the browser native loading="lazy", you can simply remove that attribute when printing is going to happen. Below is my jQuery implementation.
window.onbeforeprint = function () {
$('img').each(function () {
$(this).removeAttr('loading')
});
}
Chrome will then just load all images and they will show up when printing.
I'm encountering a strange issue. I am developing a books application and using javascript onload. I read somewhere that its best to include your javascript at the end of the html. This works for most of the html loaded. However some complain that onload init() not found. This gets solved if i include the javascript in the html head. But than other htmls start behaving strangely. onload gets called before the page is fully loaded. i dont get the correct scroll width. Please suggest what could be worng. Whats the best way of including javascripts. Thanks
html is as follows
columizer id use css column-width which i've defined like this.
css style below
#columnizer
{
width:290px;
height:450px;
column-width:290px;
column-gap:10px;
word-wrap:break-word;
}
Javascript onload is defined like this.
function init()
{
docScrollWidth = document.getElementById('columnizer').scrollWidth;
document.getElementsByTagName('body')[0].style.width = docScrollWidth + "px";
window.external.notify(str);
}
Since the actual answer was in my comment, I'll add that to my answer:
My guess is that you're doing something like window.onload = init(); instead of window.onload = init; and the init function will have to be declared before you do that assignment. You assign function references without the parens. Using the parens causes it to get executed immediately.
You say you're using this code:
docScrollWidth = document.getElementsByTagName('body')[0].style.width
The main problem with this is that style.width ONLY reads a style attribute set directly on the body object. It doesn't get the width of the object as calculated by layout or CSS rules.
So, what you should use instead really depends upon what you're trying to do. The body width will nearly always be the same or more than the window width unless your content is entirely fixed width. So, that makes me wonder what you're trying to accomplish here? What you should use instead depends upon what you're really trying to do.
FYI, document.body is a direct reference to the body object so you don't need document.getElementsByTagName('body')[0].
First, let me define the problem. The window.onload event is used by programmers to kick-start their web applications. This could be something trivial like animating a menu or something complex like initialising a mail application. The problem is that the onload event fires after all page content has loaded (including images and other binary content). If your page includes lots of images then you may see a noticeable lag before the page becomes active. What we want is a way to determine when the DOM has fully loaded without waiting for all those pesky images to load also.
Mozilla provides an (undocumented) event tailor-made for this: DOMContentLoaded. The following code will do exactly what we want on Mozilla platforms:
// for Mozilla browsers
if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", init, false);
}
So what about Internet Explorer?
IE supports a very handy (but non-standard) attribute for the tag: defer. The presence of this attribute will instruct IE to defer the loading of a script until after the DOM has loaded. This only works for external scripts however. Another important thing to note is that this attribute cannot be set using script. That means you cannot create a script using DOM methods and set the defer attribute – it will be ignored.
Using the handy defer attribute we can create a mini-script that calls our onload handler:
<script defer src="ie_onload.js" type="text/javascript"></script>
The contents of this external script would be a single line of code to call our onload event handler:
init();
There is a small problem with this approach. Other browsers will ignore the defer attribute and load the script immediately. There are several ways round this. My preferred method is to use conditional comments to hide the deferred script from other browsers:
<!--[if IE]><script defer src="ie_onload.js"></script><![endif]-->
IE also supports conditional compilation. The following code is the JavaScript equivalent of the above HTML:
// for Internet Explorer
/*#cc_on #*/
/*#if (#_win32)
document.write("<script defer src=ie_onload.js><\/script>");
/*#end #*/
So far so good? We now need to support the remaining browsers. We have only one choice – the standard window.onload event:
// for other browsers
window.onload = init;
There is one remaining problem (who said this would be easy?). Because we are trapping the onload event for the remaining browsers we will be calling the init function twice for IE and Mozilla. To get around this we should flag the function so that it is executed only once. So our init method will look something like this:
function init() {
// quit if this function has already been called
if (arguments.callee.done) return;
// flag this function so we don't do the same thing twice
arguments.callee.done = true;
// do stuff
};
I’ve provided a sample page that demonstrates this technique.
Code example:
var img = $("img");
var n = img.length;
var i = 0;
function loadImg(i) {
if( i < n ) {
$(img[i]).load( function() {
console.log("img["+i+"] loaded");
i++;
loadImg(i);
});
} else {
console.log("img["+i+"] loaded");
return false;
}
}
loadImg(0);
This code works, but when image already in a browser cache - jQuery.load is not firing.
How can I check to see if the picture is in a browser cache and forcibly fire jQuery.load?
Thanks.
Paul Irish has written a neat little plugin for that. It does exactly what you want, including images loaded from cache:
https://github.com/paulirish/jquery.imgloaded
I've updated your fiddle with the above plugin included. This should answer your question:
http://jsbin.com/acuhaf/9/edit
The jQuery documentation says:
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
You may want to use other library to preload images. I would recommend PreloadJS
I have a gallery I quickly coded up for a small site, and under Firefox 3 and Safari 3 works fine. But when I test on my old best friend IE7, it seems to not fire the imageVar.onload = function() { // code here }.. which I want to use to stop the load effect and load the image.
Please bear in mind...
I know the thumbnails are just scaled down version of the larger images. When the images are finalised by the client I am going to create proper thumbnails.
This is my first attempt to try and get out of procedural JavaScript for the most part.. so please go easy and kindly let me know where my code sucks!
For successful use of Image.onload, you must register the event handler method before the src attribute is set.
Related Information in this Question:
Javascript callback for Image Loading
Cross browser event support is not so straightforward due to implementation differences. Since you are using jQuery at your site, you are better off using its events methods to normalize browser support:
instead of:
window.load = function(){
//actions to be performed on window load
}
imageViewer.image.onload = function(){
//actions to be performed on image load
}
Do:
$(window).load(function(){
//actions to be performed on window load
});
$(imageViewer.image).load(function(){
//actions to be performed on image load
});
Just to add to the suggestion by Eran to use the jQuery's built in event handlers you can run code when the document is loaded and the DOM is created but before the images are downloaded with:
$(document).ready(function(){
//your code
});
I have an img tag in my webapp that uses the onload handler to resize the image:
<img onLoad="SizeImage(this);" src="foo" >
This works fine in Firefox 3, but fails in IE7 because the image object being passed to the SizeImage() function has a width and height of 0 for some reason -- maybe IE calls the function before it finishes loading?. In researching this, I have discovered that other people have had this same problem with IE. I have also discovered that this isn't valid HTML 4. This is our doctype, so I don't know if it's valid or not:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
Is there a reasonable solution for resizing an image as it is loaded, preferably one that is standards-compliant? The image is being used for the user to upload a photo of themselves, which can be nearly any size, and we want to display it at a maximum of 150x150. If your solution is to resize the image server-side on upload, I know that is the correct solution, but I am forbidden from implementing it :( It must be done client side, and it must be done on display.
Thanks.
Edit: Due to the structure of our app, it is impractical (bordering on impossible) to run this script in the document's onload. I can only reasonably edit the image tag and the code near it (for instance I could add a <script> right below it). Also, we already have Prototype and EXT JS libraries... management would prefer to not have to add another (some answers have suggested jQuery). If this can be solved using those frameworks, that would be great.
Edit 2: Unfortunately, we must support Firefox 3, IE 6 and IE 7. It is desirable to support all Webkit-based browsers as well, but as our site doesn't currently support them, we can tolerate solutions that only work in the Big 3.
If you don't have to support IE 6, you can just use this CSS.
yourImageSelector {
max-width: 150px;
max-height: 150px;
}
IE7 is trying to resize the image before the DOM tree is fully rendered. You need to run it on document.onload... you'll just need to make sure your function can handle being passed a reference to the element that isn't "this."
Alternatively... and I hope this isn't a flameable offense... jQuery makes stuff like this really, really easy.
EDIT in response to EDIT 1:
You can put document.onload(runFunction); in any script tag, anywhere in the body. it will still wait until the document is loaded to run the function.
I've noticed that Firefox and Safari both fire "load" events on new images no matter what, but IE 6&7 only fire "load" if they actually have to get the image from the server -- they don't if the image is already in local cache. I played with two solutions:
1) Give the image a unique http argument every time, that the web server ignores, like
<img src="mypicture.jpg?keepfresh=12345" />
This has the downside that it actually defeats caching, so you're wasting bandwidth. But it might solve the problem without having to screw with your JavaScript.
2) In my app, the images that need load handlers are being inserted dynamically by JavaScript. Instead of just appending the image, then building a handler, I use this code, which is tested good in Safari, FF, and IE6 & 7.
document.body.appendChild(newPicture);
if(newPicture.complete){
doStuff.apply(newPicture);
}else{
YAHOO.util.Event.addListener(newPicture, "load", doStuff);
}
I'm using YUI (obviously) but you can attache the handler using whatever works in your framework. The function doStuff expects to run with this attached to the affected IMG element, that's why I call it in the .apply style, your mileage may vary.
Code for jQuery. But it's easy to make dial with other frameworks. Really helpful.
var onload = function(){ /** your awesome onload method **/ };
var img = new Image();
img.src = 'test.png';
// IE 7 workarond
if($.browser.version.substr(0,1) == 7){
function testImg(){
if(img.complete != null && img.complete == true){
onload();
return;
}
setTimeout(testImg, 1000);
}
setTimeout(testImg, 1000);
}else{
img.onload = onload
}
The way I would do it is to use jQuery to do something like:
$(document).load(function(){
// applies to all images, could be replaced
//by img.resize to resize all images with class="resize"
$('img').each(function(){
// sizing code here
});
});
But I'm no javascript expert ;)
setTimeout() may be a workaround if you are really stuck. Just set it for 2 or 3 seconds - or after the page is expected to load.
EDIT: You may want to have a look at this article - all the way at the bottom about IE mem leaks...
Edit: Due to the structure of our app,
it is impractical (bordering on
impossible) to run this script in the
document's onload.
It is always possible to add handlers to window.onload (or any event really), even if other frameworks, library or code attaches handlers to that event.
<script type="text/javascript">
function addOnloadHandler(func) {
if (window.onload) {
var windowOnload = window.onload;
window.onload = function(evt) {
windowOnload(evt);
func(evt);
}
} else {
window.onload = function(evt) {
func(evt);
}
}
}
// attach a handler to window.onload as you normally might
window.onload = function() { alert('Watch'); };
// demonstrate that you can now attach as many other handlers
// to the onload event as you want
addOnloadHandler(function() { alert('as'); });
addOnloadHandler(function() { alert('window.onload'); });
addOnloadHandler(function() { alert('runs'); });
addOnloadHandler(function() { alert('as'); });
addOnloadHandler(function() { alert('many'); });
addOnloadHandler(function() { alert('handlers'); });
addOnloadHandler(function() { alert('as'); });
addOnloadHandler(function() { alert('you'); });
addOnloadHandler(function() { alert('want.'); });
</script>
This answer has a slightly different version of my addOnloadHandler() code using attachEvent. But I discovered in testing that attachEvent doesn't seem to guarantee the handlers fire in the order you added them, which may be important. The function as presented guarantees handlers are fired in the order added.
Note that I pass evt into the added event handlers. This is not strictly necessary and the code should work without it, but I work with a library that expects the event to be passed to the onload handler and that code fails unless I include it in my function.
You can do something like :
var img = new Image();
img.src = '/output/preview_image.jpg' + '?' + Math.random();
img.onload = function() {
alert('pass')
}