Getting size of javascript preloaded image - javascript

I'm trying to preload a number of images generated on the server to a small website. The preloading is done using setWindowTimeout and uses an Image object, sets the onload callback and then applies the new request uri.
For some requests, the server may have to signal that the image is 'unchanged' and I'm doing it by sending done a small 1x1 pixel gif (seems like I need to send an actual image, returning empty content will cause the Image object to not fire onload). In my onload handler I would like to determine the size of the fetched image and then determine if I should update the visual image with the given image.
Below is a snippet of my current solution (the output is a div to help debug):
refresh: function() {
var newSrc = '/screenshot.ashx?tick=' + new Date().getTime();
var imgObj = new Image();
var self = this;
// this is called when load is done
imgObj.onload = function() {
//if (imgObj.complete)
// return;
if (imgObj.width > 1 && imgObj.height > 1) {
output.innerHTML += '<br/>Updating image:' + newSrc;
img.src = newSrc; //fiddler shows no reload here, read from cache
}
else {
output.innerHTML += '<br/>Empty image:' + newSrc;
}
self.setupNewRefresh();
};
output.innerHTML += '<br/>Loading image:' + newSrc;
imgObj.src = newSrc;
},
I seem to have two problems:
a) the imgObj.complete is false when the function is first called but it is only called once (hence my commenting it out) and
b) I can't seem to rely on the width or height property of the image when loaded. From my tests of fetch the 1x1 pixel, it sometimes reads out 50 which seems to be default when creating a new Image() and it sometimes reads out the correct size of 1.
My ultimate goal is to have a small chunk of javascript logic that queries the server for a new image periodically, does nothing if nothing new has happened (1 pixel image) or loads the new image. I might be going about it the wrong way here or have overlooked important properties or calls, so I'm happy to receive feedbacks.
EDIT: upon suggestion from mplungjan, I preloaded into a hidden div instead, which helped me with problem b). Still no solution to problem a) though; reports complete = false once and is not called again

I'll go ahead and answer my own question.
Preloading the image into a hidden element in the DOM instead of a code-level element was the trick for actually getting correct sizes (thanks mplungjan). The a) issue, namely the complete event, remains unsolved.
As a side note I ended up using the XMLHttpRequest instead as it allowed by to look at the size of the payload returned.

Related

Adding img.crossOrigin = "*" interferes with img.complete

I have a function that reads map tile images. I want to keep track of whether or not a certain image has already been cached. I'm using this function from this thread:
function is_cached(src) {
var image = new Image();
image.src = src;
return image.complete;
}
This was working great. But then I needed to do some image processing. In order to copy the image data to a canvas and process it pixel by pixel, I need to use CanvasRenderingContext2D.drawImage(image, 0, 0). But it bugs me with a cross-origin error. So I can add a image.crossOrigin = "*", which solves that problem, and I can write to a canvas and do the image processing I need. That bit looks like this:
imageOutput.crossOrigin = "*"
var demCtx;
imageOutput.onload = function(){
var c = document.createElement('canvas')
c.width = c.height = 256
demCtx = c.getContext('2d')
demCtx.drawImage(imageOutput, 0, 0)
var imageData = demCtx.getImageData(0, 0, 256, 256)
}
The issue that arises is that every time I run the larger function which contains these two bits of code, the is_cached function returns false every time, except the first time. But I know that even though is_cached is returning false, the images are indeed cached, as they are loading with 0 lag (as opposed to when a novel image is called and it takes a moment to grab it from the server).
Why might .crossOrigin = "*" be interfering with the .complete status of an image?
This is happening within an ObservableHQ notebook. Might that have something to do with it? ObservaleHQ gets weird sometimes.
ObservableHQ Notebook with the problem
You can find this code in the getTileUrl cell at the bottom. This notebook is not yet finished. You can see the cached status at the Tile Previously Cached line after you click around the map of submit changes to the inputs.
Thanks for reading.
Maybe fetch api can enforce cache using the param {cache:"force-cache"}, however images should be cached as expected. You can fetch the image and pass its blob as an image source.
replace your imageOutput.src with
imageOutput.src = URL.createObjectURL(await fetch(imageUrl, {cache:"force-cache"}).then(r => r.blob()));
make your getTileURL function async as we have to await fetch and blob to be ready to be passed as image source
async function getTileURL(latArg, lngArg, zoomArg) {
Use devtools to inspect network and see tile images coming from disk cache
edit:
just try your original code and inspect network via devtools. The tiles images are cache as expected. So no need to hack into fetch blob src.

Javascript: How to check if image is already cached

I just started to learn a little bit of JavaScript and i wondered if there is a way to check if an image is already loaded to cache.
In my script im loading some random images from another webpage and display them.
When the same image will be displayed the second time, the script won't use the already loaded image, but instead load the same image again, so that there are two of them stored in my cache.
Now I want to check if the image is already stored in cache and if so, use the cached one instead of loading it again.
My code:
<script>
var img = document.createElement('img');
var index;
//On Click create random 3digit number between 1 and 100
document.getElementById('image').onclick = function(){
var index = '' + Math.floor(Math.random() * 100 +1);
while(index.length < 3) {
index = '0' + index;
}
loadImages(index);
};
//Load the image with the created number
function loadImages(id) {
var src = 'someWebPage/' + id +'.png';
img.onload = function () {
document.getElementById('image').getContext("2d").drawImage(img, 0, 0, 300, 300);
}
img.src = src;
}
</script>
Picture of my cache:
As you can see 030.png and 032.png are twice in cache.
Hope you can give me some advice.
EDIT:
Just for anyone else that faces this problem, it actually isnĀ“t one at all.
Chrome already did everything right, i only did not notice.
As you can see in the column Size the pictures were already loaded from my cache.
The way caching (in this context) is handled is by the browser negotiating with the server using a set of headers to basically tell the server "I already have this version of this resource", to which the server can then respond "OK, that is still valid, no need to download anything new". So you shouldn't be concerned about the caching in the JavaScript side, but instead make sure you are setting the correct Cache-Control headers on the server side. There are likely already questions/answers for your server/framework of choice on how to setup the caching there.

Show div once all images are loaded following AJAX response

Before I start on this, I really want to avoid using Jquery for reasons I won't go into, so please don't advise me to, as that is not what I am looking for.
I have web page in development which sends multiple ajax requests off and each one returns a lump of html containing an outer div containing inner divs and images. The issue I have is the html returned is showing on the screen before the images within it are finished rendering, giving me a couple of seconds of broken images, which looks amateur.
Is there a way that anyone knows of (without JQuery), that I can programmatically inspect everything within the outer div (possibly using recursion as there are several embedded inner divs etc) and only show the div if all the contents have finished rendering?
var fakeResponse = "<div class=\"outer\"><div class=\"inner\"><img src=\"http://upload.wikimedia.org/wikipedia/commons/5/58/Sunset_2007-1.jpg\" /></div></div>",
fakeDiv = document.createElement("div"), // here we will store the response, so we can use the .getXXX functions
images,
imagesReady = 0,
i,
length,
callback = function() { // this will help us to determine if all images have been loaded and to show the response if the preloading has finished
imagesReady++;
if (imagesReady == length) {
document.body.appendChild(fakeDiv.firstChild);
}
};
fakeDiv.innerHTML = fakeResponse; // let the browser convert the response into dom nodes
images = fakeDiv.getElementsByTagName("img"); // get all the included images
for (i = 0, length = images.length; i < length; i++) { // then preload each picture
var img = new Image();
img.onerror = callback; // add event handlers to determine the state of processing
img.onload = callback;
img.src = images[i].src; // init the actual loading
}

Why do my canvases only load on refresh?

I have a web page which contains an iframe, the iframe contains a table which contains up to 1,000 canvas elements. The canvas elements are filled by a function defined in the iframe, which is called by the parent page. The function iterates and fills each canvas with a base64 data URI contained in a hidden span in the parent page. I have read a few other Stackoverflow posts regarding similar issues but I have already tried the most commonly used suggestion (using onload) and that does not seem to work. I am fairly fresh to html/javascript so I doubt I am doing things the best way. I was hoping someone could guide me on how to assure that all the canvas elements are successfully filled with the base64 data on the first load so I don't need to refresh multiple times to get me thumbnails? Here is some code, it is VERY trimmed up code from my html page. If I were to paste the entire page, it might be a bit too much too handle sensibly. Here is what I believe to be the essential actors.
My onload call to my function which fills the canvases, this is on the main page which contains the iframe -
<body onload="document.getElementById('framePage').contentWindow.fillCans();">
Here is the fillCans() function which is contained within the framePage iframe (I probably should move this to the main html page, but so far this is how the script has... evolved haha). pgCnt element contains the "counter" numbers if you will. It will defines how many elements to iterate through. When var c is assigned, the "can"+i elements are the canvases. When var imgSrc is assigned, the "span"+i elements are the hidden span elements on the parent page which contain the base 64 data URIs -
function fillCans() {
var cnt = document.getElementById("pgCnt").innerHTML;
var cnt = cnt.split("-");
var cnt1 = cnt[0];
var cnt2 = cnt[1];
for (var i=cnt1; i <= cnt2; i++) {
var targ = "span"+i
var c = document.getElementById("can"+i);
var ctx = c.getContext("2d");
ctx.fillStyle="#FFFFFF";
ctx.fillRect(0,0,128,128);
var image = new Image();
var imgSrc = parent.document.getElementById("span"+i).innerHTML;
imgSrc = imgSrc.split("*");
image.src = imgSrc[0];
ctx.drawImage(image,0,0);
}
Like I said, this will load fine, firebug reports no errors, however all the canvases are white with no thumbnails. When I hit refresh a few times they all finally load. I've read that the image data is asynchronously loaded but I am not certain to as what that means, I assume it means it doesn't play by the rules that onload plays by? Anyways as usual, thanks for all the help stackoverflow community. It will be great to know how to get these thumbnails to load successfully on the first page load.
After #Sebastian's great suggestions I was able to get my JavaScript functioning as it should. I am pasting in the new function. As it works now, in case anyone ever comes across a similar issue. I had to implement an onload call to a function which drew the image, and then utilize in IIFE for loop to insure that each thumbnail was drawn with correctly iterated data.
function fillCans() {
var cnt = document.getElementById("pgCnt").innerHTML;
var cnt = cnt.split("-");
var cnt1 = cnt[0];
var cnt2 = cnt[1];
for (var i=cnt1; i <= cnt2; i++) {
(function(iterable){
var c = document.getElementById("can"+iterable);
var ctx = c.getContext("2d");
ctx.fillStyle="#FFFFFF";
ctx.fillRect(0,0,128,128);
var image = new Image();
var imgSrc = parent.document.getElementById("span"+iterable).innerHTML;
imgSrc = imgSrc.split("*");
image.onload = function() {
ctx.drawImage(image, 0, 0);
};
image.src = imgSrc[0];
})(i);
}
}
You are right, the problem is that the image has to be loaded first, before you can draw it.
In your case image is not loaded at the time you call
ctx.drawImage(image,0,0);
However later on the browser has cached the image URL and in between the calls to the assignment of the source property and the drawImage call the browser has already retrieved the image from the cache.
To make it work in any case, you need to wait for the image to load:
image.onload = function() {
ctx.drawImage(image, 0, 0);
};
image.src = imgSrc[0];
Be sure to first register the callback and then set the src.
See HTML Canvas DrawImage Tutorial

Binding onload event on image object in jQuery

I have following code on my site:
backgroundImages[bg_img_path_b]=new Image();
backgroundImages[bg_img_path_b].src = bg_img_path_b;
backgroundImages[bg_img_path_b].loaded="loading";
//jQuery(backgroundImages[lastImage]).unbind('load.backgroundImages');
jQuery(backgroundImages[bg_img_path_b]).bind('load.backgroundImages',function(){
if(typeof callback=='function'){
callback.call(this, bg_img_path_b);
if(showLoading) hideLoadingDC();
}
}).bind('load.cache', function(){
backgroundImages[bg_img_path_b].loaded="true";
});;
There is large gallery for images used as background-images of page wrapper. I need to preload images because of speed (the images are quite large). So I have this code (actually is only a part of bigger function, which wraps caching and so on, but this few lines are fired when image is not in cache).
backgroundImages is large array of Image objects, key is the path is the path of image. Every Image object has my property "loaded" which says if image has already been loaded, or is currently in state of loading.
As you can see from my code I am calling callback function when the image is loaded (there are changes of the background etc.)
But I have a problem with I.E<9, the callback is not successfully fired up (but not every time)..When I load my page for first it loads properly, but anytime I call this function again, it goes correctly through without errors, but the load event doesn't fired..
I really don't know where could be an error, in all browsers except older IEs it works fine..
Actually I need to debug if the event is bound correctly, but I can't see it in both IE and Chrome under load item in debugger:(
Please help I am completely screwed, really don't know what to do.
So few days ago I finally found out a solution to my problem..It seems its matter if I at first bind the onload event and then set the url of Image, or at first set the url and then bind the event..
Example
var photo = new Image();
photo.url = "http://www.something.com/something.jpg";
$(photo).bind('load',doSomething());
var photo = new Image();
$(photo).bind('load',doSomething());
photo.url = "http://www.something.com/something.jpg";
First variant runs usually ok, but sometimes it fails (in older IEs)..But the second variant is sure working propely..
which jQuery API version are you using? Try using the latest version of jQuery.
Otherwise use this simple JavaScript code to load images on calling your function on body onload:
var myimages = new Array();
var path = "images/gallery/";
function preloadimages() {
for (i = 0; i < preloadimages.arguments.length; i++) {
myimages[i]=new Image();
myimages[i].src=path+preloadimages.arguments[i];
}
}
// Enter name of images to be preloaded inside parenthesis with douable quotes.
// Extend list as desired with comma.
preloadimages("gImg1.jpg","gImg2.jpg","gImg3.jpg","gImg4.jpg" /*, etc...*/);

Categories

Resources