I wanted to make a short cut for writing all the onload functions for my images by looping through an object's variables called GameImages. I'm not sure why the images aren't being loaded when I look in the developer console in chrome. Is the for loop interrupting the loading of the images? How can I load the images in a loop instead of writing each onload function?
var i = 1; //set increment variable
var GameImages = { //object to hold each image
game1 : new Image(),
game2 : new Image(),
game3 : new Image(),
game4 : new Image(),
game5 : new Image(),
};
for(gameImage in GameImages) { //loop through each image
gameImage.onload = function () { //set the onload function for the current image
gamePosters.push(gameImage);
console.log(gamePosters.length); //print out the new size of the gamePosters array
};
//give source of image. (my images are named game1.jpg, game2.jpg, etc.)
gameImage.src = "images/assets/posters/games/game" + i + ".jpg";
i += 1; //increment i
}
It is because you're for (gameImage in GameImages) loop is looping through each of your GameImage object's properties (i.e. gameImage is first "game1", then "game2", etc.). Change your code to:
for (game in GameImages) {
var gameImage = GameImages[game]; // This will get your actual Image
gameImage.onload = function () {
gamePosters.push(gameImage);
console.log(gamePosters.length);
};
//give source of image. (my images are named game1.jpg, game2.jpg, etc.)
gameImage.src = "images/assets/posters/games/game" + i + ".jpg";
i += 1; //increment i
}
Related
I am working on animating an OpenLayers map with multiple layers over a period of time. I would like to precache ol.layer.tile tiles to have smooth transitions between the dates. Any suggestions on how to precache/preload these tiles?
You'll want to rely on your browser cache here. And it requires that your server sends proper cache headers, so the browser does not re-fetch the images with every request. With these prerequisites in mind, proceed as follows:
Call ol.source.TileImage#getTileUrlFunction on your source so you can compute the urls of the tiles you want to cache.
Call ol.source.TileImage#getTileGrid on your source so you can get the tile coordinates for the extent and zoom level you want to cache
Call ol.tilegrid.TileGrid#forEachTileCoord with a function that computes the url for each tile and sets it as src on an image object. When doing so, keep track of the number of loading tiles so you know when you're done.
Repeat the above for all dimensions, after making the respective dimension changes to your source.
In the end, your code for preloading a single dimension could look something like this:
var loadingCount = 0;
var myTileUrlFunction = mySource.getTileUrlFunction();
var myTileGrid = mySource.getTileGrid();
myTileGrid.forEachTileCoord(myExtent, myZoom, function(tileCoord) {
var url = myTileUrlFunction.call(mySource, tileCoord, ol.has.DEVICE_PIXEL_RATIO, myProjection);
var img = new Image();
img.onload = function() {
--loadingCount;
if (loadingCount == 0) {
// ready to animate
}
}
++loadingCount;
img.src = url;
}
Solution that gets around cache-preventing headers.
var i = 0;
var renderer = new ol.renderer.canvas.TileLayer(layer);
var tileSource = layer.getSource();
var datePromise = new Promise(function(resolve, reject) {
tileGrid.forEachTileCoord(extent, currentZ, function(tileCoord) {
tile = tileSource.getTile(tileCoord[0], tileCoord[1], tileCoord[2], pixelRatio, projection);
tile.load();
var loader = function(e) {
if(e.type === 'tileloadend') {
--i;
if(i === 0) {
resolve();
}
} else {
reject(new Error('No response at this URL'));
}
/* remove listeners */
this.un('tileloadend', loader);
this.un('tileloaderror', loader);
};
tileSource.on('tileloadend', loader);
tileSource.on('tileloaderror', loader);
++i;
});
});
I am using the following code to render DOM elements as canvas data and then make a PDF with them.
I have had to create a loop for this because if it was all one image it is impossible to correctly place the data over multiple pages.
$scope.pdfMaker = function() {
var content = [];
var pdfElement = document.getElementsByClassName("pdfElement");
for (var i = pdfElement.length - 1; i >= 0; i--) {
html2canvas(pdfElement[i], {
onrendered: function(canvas) {
var data = canvas.toDataURL();
content.push({
image: data,
width: 500,
margin: [0, 5]
});
console.log(canvas);
}
});
}
var docDefinition = {
content: content
};
setTimeout(function() {
pdfMake.createPdf(docDefinition).download("Score_Details.pdf");
}, 10000);
}
Issues:
I have that nasty 10 second timeout to allow time for processing, how can I restructure my code to allow the PDF to be made after all canvas data has been performed.
My canvas elements are becoming mixed up when they are converted, the correct order is essential. How can I maintain the order of the DOM elements?
It seems that html2canvas returns a promise:
$scope.pdfMaker = function() {
var promises = [];
var pdfElements = document.getElementsByClassName("pdfElement");
for (var i = 0; i < pdfElements.length; i++) {
promises.push(
html2canvas(pdfElements[i]).then(function(canvas) {
console.log('finished one!');
return {
image: canvas.toDataURL(),
width: 500,
margin: [0, 5]
};
})
);
}
console.log('done calling');
$q.all(promises).then(function(content) {
console.log('all done');
pdfMake.createPdf({ content: content }).download("Score_Details.pdf");
console.log('download one');
}, function(err) {
console.error(err);
})
}
To get your code to work just requires a few mods.
The problem from your description is that the rendering time for html2canvas varies so that they come in an undetermined order. If you make the code inside the for loop a function and pass the index to it, the function will close over the argument variable, you can use that index in the onrendered callback because (that will also close over the index) to place the rendered html in the correct position of the content array.
To know when you have completed all the rendering and can convert to pdf, keep a count of the number of html2canvas renderer you have started. When the onrendered callback is called reduce the count by one. When the count is zero you know all the documents have been rendered so you can then call createPdf.
Below are the changes
$scope.pdfMaker = function() {
var i,pdfElement,content,count,docDefinition;
function makeCanvas(index){ // closure ensures that index is unique inside this function.
count += 1; // count the new render request
html2canvas(pdfElement[index], {
onrendered: function(canvas) { // this function closes over index
content[index] = { // add the canvas content to the correct index
image: canvas.toDataURL(),
width: 500,
margin: [0, 5]
};
count -= 1; // reduce count
if(count === 0){ // is count 0, if so then all done
// render the pdf and download
pdfMake.createPdf(docDefinition).download("Score_Details.pdf");
}
}
});
}
// get elements
pdfElement = document.getElementsByClassName("pdfElement");
content = []; // create content array
count = 0; // set render request counter
docDefinition = {content: content}; //
for (i = pdfElement.length - 1; i >= 0; i--) { //do the loop thing
makeCanvas(i); // call the makeCanvas function with the index
}
}
The only thing I was not sure of was the order you wanted the pdfElements to appear. The changes will place the rendered content into the content array in the same order as document.getElementsByClassName("pdfElement"); gets them.
If you want the documents in the revers order call content.reverse() when the count is back to zero and you are ready to create the pdf.
Closure is a very powerful feature of Javascript. If you are unsure of how closure works then a review is well worth the time.
MDN closures is as good a place as any to start and there are plenty of resources on the net to learn from.
I'm trying to use deferred/promise in a loop, but I get strange behavior. My code is as follows:
var images = [];
var numImages = Blobs.length;
var image = {};
console.log("numImages: " + numImages);
function doAsyncOriginal(i) {
var defer = $.Deferred();
image.original = Blobs[i].key;
image.resized = '';
image.thumbnail = '';
images.push(image);
console.log("i: " + i + " image: " + image.original);
console.log("images[" + i + "]: " + images[i].original);
defer.resolve(i);
return defer.promise();
}
$(function(){
var currentImage = doAsyncOriginal(0);
for(var i = 1; i < numImages; i++){
currentImage = currentImage.pipe(function(j) {
return doAsyncOriginal(j+1);
});
}
$.when(currentImage).done(function() {
console.log(JSON.stringify(images));
});
});
The Blob used in the code is an array of objects that I get from remote webservice, which contains properties about the images (it comes from filepicker.io's pickandstore method to be precise).
When I run this, I get the following in console:
numImages: 2
i: 0 image: pictures_originals/3QnQVZd0RryCr8H2Q0Iq_picture1.jpg
images[0]: pictures_originals/3QnQVZd0RryCr8H2Q0Iq_picture1.jpg
i: 1 image: pictures_originals/MD3KO6GjT8SNFYoPcG8J_picture2.jpg
images[1]: pictures_originals/MD3KO6GjT8SNFYoPcG8J_picture2.jpg
[
{
"original":"pictures_originals/MD3KO6GjT8SNFYoPcG8J_picture2.jpg",
"resized":"",
"thumbnail":""
},
{
"original":"pictures_originals/MD3KO6GjT8SNFYoPcG8J_picture2.jpg",
"resized":"",
"thumbnail":""
}
]
Although it shows images[0] and images[1] correctly, when printing separately, the object array shows only twice images[1]!!!
Am I doing something wrong???
Thanks in advance for your time.
UPDATE: I corrected the code based on comment of #TrueBlueAussie
You are reusing the same image object in every call to doAsyncOriginal(), so every element of your images array is pointing to the same object.
You need to create the object inside your function:
var image = {}; // <-- delete this
function doAsyncOriginal(i) {
var image = {};
// ...
}
This problem is unrelated to promises/deferreds, and promises/deferreds really aren't serving any purpose in your code. You could just do this:
$(function(){
var images = Blobs.map(function (blob) {
return {
original: blob.key,
resized: '',
thumbnail: ''
};
});
console.log(JSON.stringify(images));
});
In doAsyncOriginal you resolve your deferred before returning it's promise or even before adding the done handler on it.
You should delay the defer.resolve(i) call, so the deferred will be resolved later and enter the done handler...
function doAsyncOriginal(i) {
var defer = $.Deferred();
// ...
// Function.bind equivalent to jQuery.proxy
window.setTimeOut(defer.resolve.bind(defer, i), 0);
return defer.promise();
}
I am working on a HTML5 game and I have a function that should load an image and append it to an array for later displaying to a canvas. The code for the loading function is here...
function loadImage(url) {
box.images.total++;
image = new Image();
image.src = url;
image.onload = function () {
box.images.loaded++;
};
box.images[box.images.length] = image;
}
The code for the holding array is here...
var box = new Object({});
box.images = new Array([]);
And the rendering code is here...
loadImage("images/background.png");
while (box.images.loaded<box.images.total) {console.log("lol");}
box.ctx.drawImage(box.images[0], 0, 0);
My hope was that this would attempt to load the set of images and would increase the counter each time. Then when an image loads it would increase the loaded counter and then once all of the code has run the rest of the code would run. But I get an error saying "function was found that matched the signature provided" and the array appears to contain an empty element.
Also i'm in Chrome on Xubuntu 12.04
Update It turns out that the image was being put in index 1 not 0 and that was why the image wasn't loading. But it still doesn't render the image please help.
Another Update So it turns out that both total and loaded were NaN and so the while loop wasn't able to load at all. I set them to zero and the wile loop didn't terminate and it crashed my browser tab.
You need some sort of Promise or a callback function. For simplicity, I would suggest the callback. Example:
var box = {};
box.images = [];
box.images.total = box.images.loaded = 0;
function loadImage(url, callback) {
box.images.total++;
image = new Image();
image.onload = function () {
box.images.loaded++;
// We call something when the image has loaded
callback();
};
image.src = url;
box.images[box.images.length] = image;
}
function loadAllImages(urls, callback) {
// Loop through each image and load url
for (var i = 0; i < urls.length; ++i) {
var url = urls[i];
loadImage(url, function() {
// When everything loads, call callback
console.log(box.images.total, box.images.loaded);
if (box.images.loaded === box.images.total) callback();
});
}
}
window.onload = function() {
box.ctx = document.querySelector("canvas").getContext("2d");
loadAllImages([
"https://lh4.googleusercontent.com/-rNTjTbBztCY/AAAAAAAAAAI/AAAAAAAAAFM/RNwjnkgQ9eo/photo.jpg?sz=128",
"https://www.gravatar.com/avatar/20e068dd2ad8db5e1403ce6d8ac2ef99?s=128&d=identicon&r=PG&f=1"
], function() {
// Do whatever once everything has loaded
box.ctx.drawImage(box.images[0], 0, 0);
});
};
<canvas>
</canvas>
I got myself into a tangle which probably involves multiple asynchronous callback situations.
I have a javascript function called populatePageArea()
Inside populatePageArea it traverses through this array-like variable called pages amongst other code.
function populatePagesArea() {
// there was some code before the for loop
for (var i=0, l=pages.length; i<l; i++) {
addToPagesArea(pages[i], "");
}
// some code after...
}
Inside addToPagesArea function, I used the FileAPI from HTML 5 to preview the drag'n dropped files amongst other code.
function addToPages(file, front) {
// there was some code before..
reader = new FileReader();
reader.onload = (function (theDiv) {
return function (evt) {
var backgroundimage = "url(" + evt.target.result + ")";
theDiv.css("background-image", backgroundimage);
var sizeSettings = getSizeSettingsFromPage(file, calculateRatio);
};
}(imageDiv));
// step#3 execute file reader
reader.readAsDataURL(file);
// there was some code after..
}
So every time I previewed the file, I also attempted to do some calculation on the dimensions of the file.
function getSizeSettingsFromPage(file, whenReady) {
reader = new FileReader();
reader.onload = function(evt) {
var image = new Image();
image.onload = function(evt) {
var width = this.width;
var height = this.height;
var filename = file.name;
if (whenReady) {
whenReady(width, height, filename);
}
};
image.src = evt.target.result;
};
reader.readAsDataURL(file);
}
function calculateRatio(width, height, filename) {
var ratio = width/height;
var object = new Object();
object['height'] = width;
object['width'] = height;
object['ratio'] = ratio;
object['size'] = 'Original';
for (var size in SIZES) {
var min = SIZES[size].ratio - 0.01;
var max = SIZES[size].ratio + 0.01;
if (ratio <= max && ratio >= min) {
object['size'] = size;
}
}
pageSizes.add(filename, object);
}
pageSizes as seen in calculateRatio is a global variable which is an array-like type of variable.
It is definitely empty BEFORE populatePagesArea gets called.
Here is the situation:
my code is:
populatePagesArea();
getMajorityPageSize(); // this acts on the supposedly non-empty pageSizes global variable
But because I think the calculateRatio has not been called on ALL the previewed images, the pageSizes is always empty when getMajorityPageSize is called.
how can i make sure that after populatePagesArea is called, getMajorityPageSize gets triggered ONLY after all the pages have undergone calculateRatio function?
I believe this is asynchronous callback. But I am not sure how to do it for an array of objects that will need to undergo an async callback function like calculateRatio.
The simple solution (I have marked my changes with // ***:
// ***
var totalPages;
function populatePagesArea() {
// there was some code before the for loop
// ***
totalPages = pages.length;
for (var i=0, l=pages.length; i<l; i++) {
addToPagesArea(pages[i], "");
}
// some code after...
}
function addToPages(file, front) {
// there was some code before..
reader = new FileReader();
reader.onload = (function (theDiv) {
return function (evt) {
var backgroundimage = "url(" + evt.target.result + ")";
theDiv.css("background-image", backgroundimage);
var sizeSettings = getSizeSettingsFromPage(file, calculateRatio);
// *** Check to see if we're done after every load
checkPagesReady();
};
}(imageDiv));
// step#3 execute file reader
reader.readAsDataURL(file);
// there was some code after..
}
// *** Call getMajorityPageSize() here, only after all pages have loaded.
function checkPagesReady() {
if (pageSizes.length >= totalPages)
getMajorityPageSize();
}
The better solution if you're going to be dealing with more asynchronous things later on would be to refactor your code using promises. Promises is an API designed for dealing with asynchronous programming in a systematic and organized way. It'll make your life a lot easier if you're going to be doing more async work. There's a lot of free libraries that support promises, one of the major players is Q.js.