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');
}
Related
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();
});
I have an array of enemies sent from the server and I am recreating them because they were serialized. After, I'm trying to get them to render on the canvas, but that isn't working for some reason.
for (var i = 0; i < enemies.length; i++) { // recreate each enemy and render it
var image = new Image();
var currentFish = new Fish();
for (var key in enemies[i]) { // copying properties to object that has the necessary methods
if (enemies[i].hasOwnProperty(key)) {
currentFish[key] = enemies[i][key];
}
}
image.src = currentFish.icon;
image.onload = function () {
ctx.drawImage(image, currentFish.position.x, currentFish.position.y);
};
ctx.fillText('Drone', 250, 200);
}
I think the issue is that image.onload is not called until after or in between frames so it isn't seen. I'm not sure how to work around this.
Edit:
I forgot to mention that I'm using requestAnimationFrame to handle rendering the canvas, so I don't know when the frame is going to be rendered.
The problem is that your outside for loop is overwriting the image variable with each loop (before the image can be loaded and drawn).
Try this alternative image loader which preloads all images into imgs[] and then calls start():
// image loader
var imageURLs = []; // put the paths to your images here
var imagesOK = 0;
var imgs = [];
imageURLs.push("");
loadAllImages(start);
function loadAllImages(callback) {
for (var i = 0; i < imageURLs.length; i++) {
var img = new Image();
imgs.push(img);
img.onload = function() {
imagesOK++;
if (imagesOK >= imageURLs.length) {
callback();
}
};
img.onerror = function() {alert("image load failed");}
img.crossOrigin = "anonymous";
img.src = imageURLs[i];
}
}
function start() {
// imgs[] holds fully loaded images
// imgs[] is in the same order as imageURLs[]
}
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.
I'm building a navigation bar where the images should be swapped out on mouseover; normally I use CSS for this but this time I'm trying to figure out javascript. This is what I have right now:
HTML:
<li class="bio"><img src="images/nav/bio.jpg" name="bio" /></li>
Javascript:
if (document.images) {
var bio_up = new Image();
bio_up.src = "images/nav/bio.jpg";
var bio_over = new Image();
bio_over.src = "images/nav/bio-ov.jpg";
}
function over_bio() {
if (document.images) {
document["bio"].src = bio_over.src
}
}
function up_bio() {
if (document.images) {
document["bio"].src = bio_up.src
}
}
However, all of the images have names of the form "xyz.jpg" and "xyz-ov.jpg", so I would prefer to just have a generic function that works for every image in the navbar, rather than a separate function for each image.
A quick-fire solution which should be robust enough provided all your images are of the same type:
$("li.bio a").hover(function() {
var $img = $(this).find("img");
$img[0].src = $img[0].src.replace(".jpg", "") + "-ov.jpg";
}, function() {
var $img = $(this).find("img");
$img[0].src = $img[0].src.replace("-ov.jpg", "") + ".jpg";
});
This should work will all image formats as long as the extension is between 2 and 4 characters long IE. png, jpeg, jpg, gif etc.
var images = document.getElementById('navbar').getElementsByTagName('img'), i;
for(i = 0; i < images.length; i++){
images[i].onmouseover = function(){
this.src = this.src.replace(/^(.*)(\.\w{2,4})$/, '$1'+'-ov'+'$2');
}
images[i].onmouseout = function(){
this.src = this.src.replace(/^(.*)-ov(\.\w{2,4})$/, '$1'+'$2');
}
}
Here's an idea in plain javascript (no jQuery):
function onMouseOverSwap(e) {
e.src = e.src.replace(/\.jpg$/", "-ov.jpg"); // add -ov onto end
}
function onMouseOutSwap(e) {
e.src = e.src.replace(/(-ov)+\.jpg$/, ".jpg"); // remove -ov
}