Browser image cache and jQuery.load() issue - javascript

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

Related

How to lazy load images and make them available for print

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.

javascript to cancel image loading

I am looking for a way to cancel image loading using javascript. I've looked at other questions and hiding them is not enough. Also, the rest of the page must load (window.stop() is out of the question).
The page that is being loaded is not under my control, only one thing is guaranteed - the first <script> on the page is my javascript (lightweight - no jquery).
I have tried setting all img sources to nothing, that did not help since the dom is created after the page is parsed, and all browsers have the same behavior - the img is loaded once it is parsed.
Not possible with modern browsers. Even if you alter the src attribute of image tag with JavaScript browsers still insist on loading the images. I know this from developing the Lazy Load plugin.
The only way I can see to stop images loading is to not have an src attribute present in the image itself, and using a custom data-* attribute to hold the location of the image:
<img data-src="http://path.to/image.png" />
Obviously this doesn't gracefully degrade for those (admittedly rare) JavaScript disabled browsers, and therefore requires a noscript fall-back:
<img data-src="http://path.to/image.png" />
<noscript><img src="http://path.to/image.png" /></noscript>
And couple this with a simple function to load the images when you, or your users, are ready for them:
/* simple demo */
function imagePopulate(within, attr) {
within = within && within.nodeType == 1 ? within : document;
attr = attr || 'data-src';
var images = within.getElementsByTagName('img');
for (var i = 0, len = images.length; i < len; i++) {
if (images[i].parentNode.tagName.toLowerCase() !== 'noscript') {
images[i].src = images[i].getAttribute(attr);
}
}
}
document.getElementById('addImages').onclick = function(){
imagePopulate();
};
JS Fiddle demo.
I can't be sure for all browsers, but this seems to work in Chrome (in that there's no attempt, from looking at the network tab of the developer tools, to load the noscript-contained img).
It can be done with webworkers. See the following example:
https://github.com/NathanWalker/ng2-image-lazy-load.
Stopping a web worker cancels the image loading in browser
Recalling the onload event:
window.onload=function(){
imgs = document.getElementsByTagName('img');
for(i = 0; i < imgs.length(); i++){
imgs[i].src = '#';
}
};
If you want to only cancel the loading of the image , you can use sємsєм's solution
but i do not think it will work by using an window onload event .
You will probably need to provide a button to cancel the image load. Also i suggest, instead of setting the src attribute to "#" , you can remove the src attribute itself using
removeAttribute()
[Make sure you disable the cache while testing]
You need a proxy.
Your script can redirect to another server using something like
location.replace('http://yourserver.com/rewrite/php?url='+escape(this.href));
perhaps you tell us why you want to cancel image loading and whose site you are loading on so we can come up with a better solution
If there is nothing on the page other than images, you could try
document.write('<base href="http://yourserver.com/" />');
which will mess with all non-absolute src and hrefs on the page.
UPDATE Horrible hack but perhaps this almost pseudo code (I am off to bed) will do someting
document.write('<script src="jquery.js"></script><div id="myDiv"></div><!-'+'-');
$(function() {
$.get(location.href,function(data) {
$("#myDiv").html(data.replace(/src=/g,'xsrc='));
});
})
The closest you can get to what you maybe want is to have a quickly loaded placeholder image (ie. low resolution version of your image) and a hidden image (eg. {display:none}) in which the large image gets loaded but not displayed. Then in the onload event for the large image swap the images over (eg. display:block for the large image display:none for the smaller). I also use an array (with their url), to reuse any images that have already been opened.
BTW if you open an image in a new webpage when it gets closed then the image loading will be cancelled. So maybe you can do something similar in a webpage using an iframe to display the image.
To close the iframe and therefore unload the image, remove the frame from the DOM
(another advantage is that browsers spawn another process to deal with iframes, so the page won't lock up while the image loads)

jQuery alternative to bind('load') for use with images [duplicate]

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.

JavaScript & IE7 - Why won't my *.onload = function() { } fire?

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
});

img onload doesn't work well in IE7

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')
}

Categories

Resources