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>
Related
I have a long list of images and I want to check each image (if broken, it should return false; else, return true)
I wrote this code but the function of checking the urls always return me wrong results
...
validImages=[];
this.list.forEach(item=>{
if(checkImage(item.url)){
validImages.push(item);
}
});
...
checkImage(url){
let image = new Image();
image.src = url;
console.log(image.onerror);
if(image.onerror){return false;}else{return true;}
}
hint: I could find these two statements. It works but i couldn't run them in my function to return true/false value
image.onerror = function (evt){alert("can't download")};
image.onload = function (evt){alert("can be download")};
So any helps for this issue ?
EDIT
Here is my new code
...
validImages=[];
this.list.forEach(item=>{
this.imageExists(item.img, function(exists){
if(exists){ // exists get correct true/false values
validImages.push(item); //push function not works
}
});
});
checkImage(url, callback){
let image = new Image();
image.src = url;
image.onerror = function (evt){callback(false);};
image.onload = function (evt){callback(true);};
}
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'm facing a problem with canvas. No errors is returned. I'm trying to make a loader, that load every ressources like pictures, sounds, videos, etc, before the start of application. The loader must draw the number of ressourses loaded dynamically.
But at this moment, my loader has the result to freeze the browser until it draws the total of ressources loaded.
Tell me if i'm not clear :)
This is the code :
function SimpleLoader(){
var ressources ;
var canvas;
var ctx;
this.getRessources = function(){
return ressources;
};
this.setAllRessources = function(newRessources){
ressources = newRessources;
};
this.getCanvas = function(){
return canvas;
};
this.setCanvas = function(newCanvas){
canvas = newCanvas;
};
this.getCtx = function(){
return ctx;
};
this.setCtx = function(newCtx){
ctx = newCtx;
};
};
SimpleLoader.prototype.init = function (ressources, canvas, ctx){
this.setAllRessources(ressources);
this.setCanvas(canvas);
this.setCtx(ctx);
};
SimpleLoader.prototype.draw = function (){
var that = this;
this.getCtx().clearRect(0, 0, this.getCanvas().width, this.getCanvas().height);
this.getCtx().fillStyle = "black";
this.getCtx().fillRect(0,0,this.getCanvas().width, this.getCanvas().height)
for(var i = 0; i < this.getRessources().length; i++){
var data = this.getRessources()[i];
if(data instanceof Picture){
var drawLoader = function(nbLoad){
that.getCtx().clearRect(0, 0, that.getCanvas().width, that.getCanvas().height);
that.getCtx().fillStyle = "black";
that.getCtx().fillRect(0,0, that.getCanvas().width, that.getCanvas().height);
that.getCtx().fillStyle = "white";
that.getCtx().fillText("Chargement en cours ... " + Number(nbLoad) +"/"+ Number(100), that.getCanvas().width/2, 100 );
}
data.img = new Image();
data.img.src = data.src;
data.img.onload = drawLoader(Number(i)+1); //Update loader to reflect picture loading progress
} else if(data instanceof Animation){
/* Load animation */
} else if(data instanceof Video){
/* Load video */
} else if(data instanceof Sound){
/* Load sound */
}else {
}
}
};
So with this code, all resources are loaded, but i want to display the progress of loading. Some idea of what i missed?
You are "busy-looping" in the loader so the browser doesn't get a chance to redraw/update canvas.
You can implement a setTimeout(getNext, 0) or put the draw function outside polling current status in a requestAnimationFrame loop instead. I would recommend the former in this case.
In pseudo code, this is one way to get it working:
//Global:
currentItem = 0
total = numberOfItems
//The loop:
function getNextItem() {
getItem(currentItem++);
drawProgressToCanvas();
if (currentItem < total)
setTimeout(getNextItem(), 0);
else
isReady();
}
getNextItem(); //start the loader
Adopt as needed.
The setTimeout with a value of 0 will cue up a call next time there is time available (ie. after a redraw, empty event stack etc.). isReady() here is just one way to get to the next step when everything is loaded. (If you notice any problems using 0, try to use for example 16 instead.)
Using requestAnimationFrame is a more low-level and efficient way of doing it. Not all browsers support it at the moment, but there are poly-fills that will get you around that - For this kind of usage it is not so important, but just so you are aware of this option as well (in case you didn't already).
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
}
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.