I am strugling to create a script to load multiple images for a game drawn on Canvas. The window seems to load without completing the load of all images. I've tried many ways but none of them seems to work. The function drawGameMenu() is called before the images are actually loaded and so the images are not drawn. If someone could help, I would be grateful. Here is my script, kind regards:
var imageNames = ["menuImage", "resetScoreButton", "instructionsButton", "playButton", "dialogPanel", "gamePlayImage", "exitButton", "timerPanel", "messengerPanel", "scoreBar", "yesButton", "noButton", "goButton"];
var imageFileNames = ["game_Menu", "reset_score_button", "instructions_button", "play_button", "dialog_panel", "game_play", "exit_button", "timer", "messenger_panel", "score_bar", "yes_button", "no_button", "go_button"];
var imageCollection = {};
window.addEventListener("load", function() {
var u = imageNames.length - 1;
for(i = 0; i <= u; i++) {
var name = imageNames[i];
imageCollection[name] = new Image();
imageCollection[name].src = imageFileNames[i] + ".png";
console.log(imageCollection[name]);
imageCollection[name].addEventListener('load', function() {
do {
var x = imageCollection[name].complete;
}
while(x != true);
});
}
drawGameMenu();
});
I made some changes on the script and now it works on the PC browser, but not working on smartphone. The script is the following:
window.addEventListener("load", async function loadImageCollection() {
var u = imageNames.length - 1;
for(i = 0; i <= u; i++) {
var name = imageNames[i];
imageCollection[name] = new Image();
imageCollection[name].src = imageFileNames[i] + ".png";
do {
await new Promise((resolve, reject) => setTimeout(resolve, 50));
x = imageCollection[name].complete;
console.log(x);
}
while(x == false);
}
drawGameMenu();
});
Keep it simple
Just use a simple callback and a counter to count of images as they load. Adding promises adds an additional level of complexity that is just a source of potential bugs. (the promise for each image and its callback and the need to call it on image load, and the need to handle promise.all with another callback)
const imageCollection = loadImages(
["menuImage", "resetScoreButton", "instructionsButton", "playButton", "dialogPanel", "gamePlayImage", "exitButton", "timerPanel", "messengerPanel", "scoreBar", "yesButton", "noButton", "goButton"],
["game_Menu", "reset_score_button", "instructions_button", "play_button", "dialog_panel", "game_play", "exit_button", "timer", "messenger_panel", "score_bar", "yes_button", "no_button", "go_button"],
drawGameMenu // this is called when all images have loaded.
);
function loadImages(names, files, onAllLoaded) {
var i = 0, numLoading = names.length;
const onload = () => --numLoading === 0 && onAllLoaded();
const images = {};
while (i < names.length) {
const img = images[names[i]] = new Image;
img.src = files[i++] + ".png";
img.onload = onload;
}
return images;
}
With the use of promises this becomes a very easy task. I don't know if ES6 allows it, but give it a try anyways.
var jarOfPromise = [];
for(i = 0; i <= u; i++) {
jarOfPromise.push(
new Promise( (resolve, reject) => {
var name = imageNames[i];
imageCollection[name] = new Image();
imageCollection[name].src = imageFileNames[i] + ".png";
console.log(imageCollection[name]);
imageCollection[name].addEventListener('load', function() {
resolve(true);
});
})
)
}
Promise.all(jarOfPromise).then( result => {
drawGameMenu();
});
Related
I have the following code below, which successfully preloads images into the browser cache when the PreloadAllImages function is called. Does anyone know how to send a simple alert, e.g. "All images have been preloaded into the browser cache." when the last image (or all images) is loaded. I have 300 images, so setTimeOut won't work for latency/download lag reasons. I've tried callbacks, placing alert commands in different places, etc... but have not been successful.
This code is based on JavaScript Only Method #1 found here. https://perishablepress.com/3-ways-preload-images-css-javascript-ajax/
function PreLoadAllImages() {
var images = new Array();
function preload() {
for (i = 0; i < preload.arguments.length; i++) {
images[i] = new Image();
images[i].src = preload.arguments[i];
}
}
preload('Images/Image1.jpg', 'Images/Image2.jpg', ... , 'Images/Image300.jpg');
}
With some help, this is how the problem was solved in case anyone else needs it. Loaded images into an invisible div on the html page, then ran script to ensure everything was loaded. A button calls PreLoadAllImages() and then CheckImages().
var imagefiles = [
"Images/Image1.jpg",
"Images/Image2.jpg",
.....
"Images/Image300.jpg"];
function PreLoadAllImages () {
imagefiles.forEach(function(image){
var img = document.createElement('img');
img.src = image;
document.body.appendChild(img);});
$('img').hide();
}
var all_loaded;
function CheckImages() {
$('img').each(function(){ // selecting all image element on the page
var img = new Image($(this)); // creating image element
img.src = $(this).attr('src'); // pass src to image object
if(img.complete==false) {
all_loaded=false;
return false;
}
all_loaded=true;
return true;});
}
function CheckLoadingImages() {
var result;
var Checking=setInterval(function(){
result=CheckImages();
if(all_loaded==true) {
clearInterval(Checking);
MsgAfterLoading();
}
}, 2000);
}
function MsgAfterLoading() {
alert('All Images Loaded');
}
You could check if i is equals to the length of the array, so it will look like:
for (i = 0; i < preload.arguments.length; i++) {
images[i] = new Image()
images[i].src = preload.arguments[i]
//the code
if (i == preload.arguments.length){
alert("Last image loaded");
}
//the code
}
Let me know if you have any questions.
Could you try looping through all the image elements and check the naturalWidth property, if they are all greater than 0 and its the last image then alert.
This link: Preloading Images in JavaScript (Need an Alert When Completed) should help
A promise might resolve your problem.
https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Promise
var myPromise = new Promise( function(resolve, reject) {
// preload code
resolve(true);
};
myPromise.then(function() {
// your alert
};
You can use the onload event.
function PreLoadAllImages() {
var images = new Array();
var numberofimages = 0, loadedimages = 0;
function preload() {
numberofimages = preload.arguments.length;
for (i = 0; i < preload.arguments.length; i++) {
images[i] = new Image();
images[i].src = preload.arguments[i];
images[i].onload = () => {
loadedimages++;
if(loadedimages == numberofimages){
alert("All images are loaded");
}
}
}
}
preload('Images/Image1.jpg', 'Images/Image2.jpg', ... , 'Images/Image300.jpg');
}
I have faced such a problem. I need to make a preloader with a percentage for a page but I don't know how. Actually, I don't need animation or simple preloader. What do I can and what do I have?
window.onload = function() {
var images = document.images,
imagesTotalCount = images.length,
imagesLoadedCount = 0,
preloader = document.getElementById('js_preloader'),
percDisplay = document.getElementById('js_preload__percentage');
for(var i = 0; i < imagesTotalCount; i++) {
image_clone = new Image();
image_clone.onload = image_loaded;
image_clone.onerror = image_loaded;
image_clone.src = images[i].src;
}
function image_loaded() {
imagesTotalCount++;
percDisplay.innerHTML = (((100 / imagesTotalCount) * imagesLoadedCount) << 0) + '%';
if(imagesLoadedCount >= imagesTotalCount) {
setTimeout(function() {
if(!preloader.classList.contains('done')) {
preloader.classList.add('done');
}
}, 1500);
}
}
};
This aproach allows to see all images to be downloaded and and calculate percentage. But how do I can also take in count the download of css and js files?
You could use the same approach, but with document.scripts and document.styleSheets collections.
I'm trying to create a Tri Peaks Solitaire game in Javascript, using CreateJS to help interact with the HTML canvas. I'm not entirely new to programming, as I've taken college courses on Java and C, but I am new to both Javascript and HTML.
I created a large amount of the card game using a Card class and Deck class, with the image adding, event listening, and game logic done in one Javascript file, but this resulted in a lot of messy, somewhat buggy code that I felt needed cleaning up.
I'm trying to implement a separate class for the card table, which will be the canvas, or stage, and it will draw the 4 rows of playable cards, the stock pile, and the waste pile. I'm currently just trying to get the stock pile to show up on the canvas, but it's not happening.
My question is, why is my stock pile not showing up? Also, as you can see, the card images are being loaded when the cards are created. Should I be doing that differently?
Card class:
var Card = function(number) {
this.isFaceUp = false;
//number for image file path
this.number = number;
this.value = Math.ceil( (this.number)/4 );
//back of the card is default image
this.initialize("images/" + 0 + ".png");
console.log("card created")
this.height = this.getBounds().height;
this.width = this.getBounds().width;
//load the card's front image
this.frontImage = new Image();
this.frontImage.src = ( this.getImagePath() );
};
Card.prototype = new createjs.Bitmap("images/" + this.number + ".png");
Card.prototype.getImagePath = function() {
return "images/" + this.number + ".png";
};
Card.prototype.getValue = function () {
return this.value;
}
Card.prototype.flip = function() {
// this.image = new Image();
// this.image.src = ( this.getImagePath() );
this.image = this.frontImage;
this.isFaceUp = true;
};
Card.prototype.getHeight = function() {
return this.height;
};
Card.prototype.getWidth = function() {
return this.width;
};
CardDeck class:
function CardDeck() {
//create empty array
this.deck = [];
//fill empty array
for(i=1; i<=52; i++) {
//fill deck with cards, with i being the number of the card
this.deck[i-1] = new Card(i);
}
//shuffle deck
this.shuffle();
}
CardDeck.prototype.getCard = function() {
if(this.deck.length < 1) alert("No cards in the deck");
return this.deck.pop();
};
CardDeck.prototype.getSize = function() {
return this.deck.length;
};
CardDeck.prototype.shuffle = function() {
for (i=0; i<this.deck.length; i++) {
var randomIndex = Math.floor( Math.random()*this.deck.length );
var temp = this.deck[i];
this.deck[i] = this.deck[randomIndex];
this.deck[randomIndex] = temp;
}
};
CardDeck.prototype.getRemainingCards = function() {
return this.deck.splice(0);
}
CardDeck.prototype.listCards = function() {
for(i=0; i<this.deck.length; i++) {
console.log(this.deck[i].number);
}
};
CardTable class:
var CardTable = function(canvas) {
this.initialize(canvas);
this.firstRow = [];
this.secondRow = [];
this.thirdRow = [];
this.fourthRow = [];
this.stockPile = [];
this.wastePile = [];
//startX is card width
this.startX = ( new Card(0) ).width*3;
//startY is half the card height
this.startY = ( new Card(0) ).height/2;
};
CardTable.prototype = new createjs.Stage();
CardTable.prototype.createStockPile = function(cards) {
for(i=0; i<23; i++) {
cards[i].x = 10;
cards[i].y = 50;
this.stockPile[i] = cards[i];
}
};
CardTable.prototype.addToWastePile = function(card) {
card.x = startX + card.width;
card.y = startY;
this.wastePile.push(card);
this.addChild(card);
this.update();
};
CardTable.prototype.drawStockPile = function() {
for(i=0; i<this.stockPile.length; i++) {
console.log("this.stockPile[i]");
this.addChild(this.stockPile[i]);
}
this.update();
};
HTML file:
<html>
<head>
<title>Tri-Peaks Solitaire</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://code.createjs.com/easeljs-0.8.1.min.js"></script>
<script src="card.js"></script>
<script src="carddeck.js"></script>
<script src="cardtable.js"></script>
<script>
function init(){
var canvas = document.getElementById("canvas");
var table = new CardTable("canvas");
deck = new CardDeck();
table.createStockPile(deck.getRemainingCards());
table.drawStockPile();
table.update();
}
</script>
</head>
<body onload="init()">
<h1>Tri-Peaks Solitaire</h1>
<canvas style='border: 5px solid green; background: url("images/background.jpg");'
id="canvas" width="1000" height="450">
</canvas>
</body>
</html>
PRELOADJS UPDATE:
var canvas = document.getElementById("canvas");
var stage = new createjs.Stage(canvas);
var gameDeck = new CardDeck();
var numbers = [];
var preloader;
var progressText = new createjs.Text("", "20px Arial","#FF0000");
progressText.x = 50;
progressText.y = 20;
stage.addChild(progressText);
stage.update();
setupManifest();
startPreload();
function setupManifest() {
for(i=0; i<=52; i++) {
console.log("manifest");
manifest.push( {src:"images/" + i + ".png",
id: i} );
numbers.push(i);
}
}
function startPreload() {
preload = new createjs.LoadQueue(true);
preload.on("fileload", handleFileLoad);
preload.on("progress", handleFileProgress);
preload.on("complete", loadComplete);
preload.on("error", loadError);
preload.loadManifest(manifest);
}
function handleFileLoad(event) {
console.log("A file has loaded of type: " + event.item.type);
//console.log(numbers.pop());
var cardNumber = numbers.pop();
var newCard = new Card(cardNumber);
//faces.push( new Image() );
//faces[ faces.length - 1 ].src = "images/" + cardNumber + ".png";
gameDeck.insertCard(newCard);
}
function loadError(evt) {
console.log("error",evt.text);
}
function handleFileProgress(event) {
progressText.text = (preload.progress*100|0) + " % Loaded";
stage.update();
}
function loadComplete(event) {
console.log("Finished Loading Assets");
}
You are only updating the stage immediately after creating the cards. The cards are loading bitmap images, which is a concurrent operation that takes time. As such, the only time you render the stage is before the images load.
I would suggest using PreloadJS to preload your card images. This will also give you a lot more control over when / how they load, and let you show a progress bar or distractor to the user as they load.
I would suggest waiting for all the images to load and then calling stage.update(). Alternatively use the createjs.Ticker.addEventListener("tick", handleTick); event handler to update the stage for you.
Reference: http://www.createjs.com/docs/easeljs/classes/Ticker.html
I'm dabbling with canvas. And I'm a little lost on something.
I have this function:
function preloadimages(arr) {
var newimages = []
var arr = (typeof arr != "object") ? [arr] : arr
for (var i = 0; i < arr.length; i++) {
newimages[i] = new Image()
newimages[i].src = arr[i]
}
}
And I call it like so:
preloadimages(['images/background.png', 'images/hero.png', 'images/monster.png']);
The only problem is, I don't know how to then draw them again later.
If I was preloading one image inside my js I would say:
var bgOk = false;
var bg = new Image();
bg.onload = function () {
bgOk = true;
};
bg.src = "images/background.png";
and then further down when I wanted it drawn I would say:
if (bgOk) {
context.drawImage(bg, 0, 0);
}
And that would be that. The problem is I have made a preloader class, I don't really know how now to call in just the image I want to draw now, or even how to implement the bgOk idea so that if it loaded ok, I can draw it, and if not, leave it alone.
Could someone advise me on this? I'm basically just trying to go more class based rather than the dirty great mess I normally have with a huge javascript file that is ugly and not as maintainable.
This seems to be a complicated problem, but in reality isn't as bad as it looks. If you want to use pre-existing code, or just want to look at something for ideas you can have a look at: http://thinkpixellab.com/pxloader/ This library was used in the HTML5 version of Cut The Rope.
A simple custom implementation could be something like the following:
function loadImages(arr, callback) {
this.images = {};
var loadedImageCount = 0;
// Make sure arr is actually an array and any other error checking
for (var i = 0; i < arr.length; i++){
var img = new Image();
img.onload = imageLoaded;
img.src = arr[i];
this.images[arr[i] = img;
}
function imageLoaded(e) {
loadedImageCount++;
if (loadedImageCount >= arr.length) {
callback();
}
}
}
And then you can call it like this:
var loader = loadImages(['path/to/img1', 'path/to/img2', 'path/to/img3'], function() {
ctx.drawImage(loader.images['path/to/img1']); // This would draw image 1 after all the images have been loaded
// Draw all of the loaded images
for (var i = 0; i < loader.images.length; i++) {
ctx.drawImage(loader.images[i]);
}
});
If you want more details on asset loading you can have a look at the asset loading section of Udacity's HTML5 Game Development course https://www.udacity.com/course/cs255
A function I use:
function ImageLoader(sources, callback)
{
var images = {};
var loadedImages = 0;
var numImages = 0;
// get num of sources
for (var src in sources) {
numImages++;
}
for (var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if (++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = sources[src];
}
}
You call it like so:
var sources = {
bg: path/to/img.png,
title: path/to/img/png
};
var _images = {};
isReady = ImageLoader(sources, function(images) {
_images = images;
});
And then to access your images
_images.bg;
Example: drawImage(_images.bg, 0, 0);
I'm trying to write some code to find all the linked images on a webpage. So far I'm able to generate an array of all the links (imageLinks) but in the code below the final console.log(linkedImages) always shows an empty array.
The thing I can't wrap my head around is where I've commented "This works / But this doesn't work:"
What am I doing wrong? Any help is greatly appreciated for this somewhat noob. Thanks!
//Create an array of all the links containing img tags
var imageLinks = $("img").parent("a").map(function () {
var h = $(this).attr("href");
return h;
}).get();
//This correctly shows all the links:
//console.log(imageLinks);
//Declare an array to hold all the linked images
var linkedImages = [];
//Loop through all the links to see if they're images or not
for (var l = 0; l < imageLinks.length; l++) {
var currLink = imageLinks[l];
function myCallback(url, answer) {
if (answer) {
//This works:
console.log(url + ' is an image!');
//But this doesn't work
linkedImages.push(url);
} else {
//alert(url+' is NOT an image!');
}
}
function IsValidImageUrl(url, callback) {
var img = new Image();
img.onerror = function () {
callback(url, false);
}
img.onload = function () {
callback(url, true);
}
img.src = url
}
IsValidImageUrl(currLink, myCallback);
};
//HELP! This always evaluates as just "[]"
console.log(linkedImages);
What #SLaks said. Since the loading of images is async, your callback doesn't fire until after the images have been loaded. To fix the problem, you can use $.Deferred from jQuery (I am assuming you are using jQuery since there is a $(...) in your code):
function callback(dfd, url, answer) {
if (answer) {
//This works:
console.log(url+' is an image!');
//But this doesn't work
dfd.resolve(url);
} else {
//alert(url+' is NOT an image!');
dfd.reject();
}
}
//Create an array of all the links containing img tags
var imageLinks = $("img").parent("a").map(function() {
var h = $(this).attr("href");
return h;
}).get();
//This correctly shows all the links:
//console.log(imageLinks);
//Declare an array to hold all the linked images
var linkedImages = [];
//Loop through all the links to see if they're images or not
var dfds = [];
for (var l=0; l<imageLinks.length; l++) {
var currLink = imageLinks[l];
var dfd = $.Deferred();
dfd.done(function(url) { linkedImages.push(url); });
dfds.push(dfd.promise());
(function(dfd, url) {
var img = new Image();
img.onerror = function() { callback(dfd, url, false); }
img.onload = function() { callback(dfd, url, true); }
img.src = url
})(dfd, currLink);
};
$.when.apply(null, dfds).done(function() {
console.log(linkedImages);
});
Have not tested this, but the general idea for how to use deferred to reach your goal is in there.