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[]
}
Related
I'm trying to use image.onload function but async issue is occuring. I'm passing the data through array but it is not executing according to array order in the image.onload function. How can I fix this issue?
Object.entries(data).forEach(element => {
const brushtype = element[0]['brush']
const img = new Image();
img.onload = () => {
brushctx.drawImage(img, 0, 0);
}
img.src = brushtype;
}
The problem is the way you're requesting those images.
By doing
Object.entries(data).forEach(element => {
}
it's happening at the same time virtually. Whatever image finished loading will be drawn right-after.
If you want your images to be drawn in a specific order - e.g. the order it's listed in the array - you need to request a new one after the previous finished loading.
Here's an example:
let canvas = document.getElementById('canvas');
let context = canvas.getContext('2d');
let myImages = ['https://picsum.photos/id/1/50/50', 'https://picsum.photos/id/22/50/50', 'https://picsum.photos/id/83/50/50', 'https://picsum.photos/id/64/50/50'];
function loadImages(index) {
let image = new Image();
image.onload = function(e) {
context.drawImage(e.target, index % 2 * 50, parseInt(index / 2) * 50);
if (index + 1 < myImages.length) {
loadImages(index + 1);
}
}
image.src = myImages[index];
}
loadImages(0);
<canvas id='canvas' width='200' height='200'></canvas>
To draw images in order of request you either need to wait until all images have loaded and then draw them only when the earlier image have been draw. This answer gives an example of both approaches.
Note That if there are any load errors none of the images may be drawn. In the second method an error will mean none to all but one are drawn.
Load first then draw
The snippet bellow loads first then draws. It stores the images in an array images and keeps a count of images loading. Counting up for each new image, and when an image load it counts down.
When the counter is zero when know all images have loaded and can then draw them all
var imgCount = 0;
const images = [];
Object.entries(data).forEach(element => {
const img = new Image;
img.src = element[0]['brush'];
images.push(img);
imgCount ++;
img.onload = () => {
imgCount --;
if (!imgCount) {
for(const img of images) { brushctx.drawImage(img, 0, 0) }
}
}
}
Draw only when first or previous loaded.
The alternative is to again create an array to hold all the images. But this time we track what has been drawn. Each time an image is loaded, we check if any of the images in the array before it have loaded, then we draw all images until we find a empty image slot.
var drawnCount = 0;
const images = [];
Object.entries(data).forEach(element => {
const img = new Image;
const imgIdx = images.length;
img.src = element[0]['brush'];
img.onload = () => {
images[imgIdx] = img;
while (images[drawnCount]) { brushctx.drawImage(images[drawnCount++], 0, 0) }
}
images.push(null);
}
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 got a code for loading an image into a canvas, and it works fine, but when I try to iterate to do it multiple times, it only loads one image (the last one). Anyone knows why?
<pre id="output_1"></pre>
<canvas id="canvas_1"></canvas>
<pre id="output_2"></pre>
<canvas id="canvas_2"></canvas>
<script type="text/javascript">
var pics = [ 'Desert.jpg', 'Hydrangeas.jpg' ];
for(var i=0; i < pics.length; i++) {
var img = new Image();
var ctx = $( '#canvas_'+(i+1) )[0].getContext('2d');
img.onload = function() {
ctx.drawImage(img, 0, 0);
};
img.src = pics[i];
}
</script>
The var img is being overwritten as you loop. The onloads will not be called until the current execution is complete. By that time img will equal the 3rd iteration.
To fix
var pics = [ 'Desert.jpg', 'Hydrangeas.jpg' ];
function loadImage(i){
var img = new Image();
var ctx = $( '#canvas_'+(i+1) )[0].getContext('2d');
img.onload = function() {
ctx.drawImage(img, 0, 0);
};
img.src = pics[i];
}
for(var i=0; i < pics.length; i++) {
loadImage(i);
}
The call to the function loadImage creates a new reference to all the variable each time it is called. It differs from onload because it is called immediately, while onload is a callback and must wait until the currently executing context has completely exited (in other words returned until there are no more returns), then and only then can the onload happen.
I know we can use this code to enable CORS on a single image
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var img = new Image();
img.crossOrigin = '';
img.src = 'http://crossdomain.com/image.jpg';
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0, img.width, img.height);
Is there any way to do it for multiple image URLs at once?
URL array
To load several images enabling CORS request, you can use an array which is practical for this purpose.
One thing to be aware of is that requesting CORS can be denied by server. The browser may fail loading the image in those cases so you will need to know in advance if CORS need to be requested or not.
Example loader
var urls = [url1, url2, url3, ...]; // etc. replace with actual URLs
var images = []; // store the loaded images
var i = 0, len = urls.length;
var count = len; // for load and error handlers
for(; i < len; i++) {
var img = new Image();
img.onload = loadHandler;
img.onerror = img.onabort = errorHandler;
img.crossOrigin = ""; // enable CORS request
img.src = urls[i]; // set src last
images.push(img); // store in array
}
function loadHandler() {
if (!--count) callback(); // loading done
}
function errorHandler() {
// handle errors here
loadHandler(); // make sure to update counter/callback
}
function callback() {
// ... ready, continue from here
}
Demo
var urls = [
"http://i.imgur.com/0LINzxs.jpg", // random urls from imgur..
"http://i.imgur.com/6ksiMgS.jpg",
"http://i.imgur.com/aGQSLi9.jpg"
];
var images = []; // store the loaded images
var i = 0, len = urls.length;
var count = len; // for load and error handlers
for(; i < len; i++) {
var img = new Image();
img.onload = loadHandler;
img.onerror = img.onabort = errorHandler;
img.crossOrigin = ""; // enable CORS request
img.src = urls[i]; // set src last
images.push(img); // store in array
}
function loadHandler() {
if (!--count) callback(); // loading done
}
function errorHandler() {
// handle errors here
loadHandler(); // make sure to update
}
function callback() {
// ... ready, continue from here
console.log(images);
var ctx = document.querySelector("canvas").getContext("2d");
ctx.drawImage(images[0], 0, 0);
ctx.drawImage(images[1], 0, 0);
ctx.drawImage(images[2], 0, 0);
console.log(ctx.canvas.toDataURL()); // OK if CORS is OK!
}
<canvas></canvas>
Put the srcs in an array and iterate over them
working fiddle
https://jsfiddle.net/ps50po4z/
This allows you to use multiple images from different sources and display them.
use this function as a template to iterate through all the cors src images
<html>
<head>
<style>
body {
margin: 0px;
padding: 0px;
}
</style>
</head>
<body>
<canvas id="myCanvas" width="578" height="200"></canvas>
<script>
function loadImages(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];
}
}
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var sources = {
darthVader: 'http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg',
yoda: 'http://www.html5canvastutorials.com/demos/assets/yoda.jpg'
};
loadImages(sources, function(images) {
context.drawImage(images.darthVader, 100, 30, 200, 137);
context.drawImage(images.yoda, 350, 55, 93, 104);
});
</script>
</body>
</html>
I have an array of image objects which hold all the necessary info like path,x, y, w, h. Now i want to draw all those images on canvas in a loop.. but when i do so, it only draws the first image. Here is the code:
for(var i=0; i<shapes.length; i++)
{
var A = shapes.pop();
if(A.name == 'image' && A.pageNum == pNum)
{
var img = new Image();
img.src = A.path;
img.onload = function() {
context.drawImage(img, A.x, A.y, A.w, A.h);
}
}
}
i checked all the info in the shapes array... inside the if condition, before calling drawImage function, all the info of each image is correct but for some strange reason it doesn't display images except the 'last' in the array (the one which pops out last)
Your image loading code is faulty.
Each image will take time to load and by then you have overwritten var img with another new Image();
Here's an example of an image loader that executes only after all the images have been loaded and are ready to be drawn:
[ Warning: untested code -- some adjustments may be required! ]
// image loader
var imageURLs=[]; // put the paths to your images in this array
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];
// note: instead of this last line, you can probably use img.src=shapes[i].path;
}
}
function start(){
// the imgs[] array holds fully loaded images
// the imgs[] are in the same order as imageURLs[]
for(var i=0;i<shapes.length;i++){
var shape=shapes[i];
context.drawImage(imgs[i],shape.x,shape.y,shape.w,shape.h);
}
}
It was the problem of some sort of closure... like, the loop finishing before images getting loaded or something. The solution that worked for me was putting all the image loading code in a separate function and calling that function from the loop:
if(A.name == 'image' && A.pageNum == pNum)
{
displayImage(A.path, A.x, A.y, A.w, A.h);
}
function displayImage(path, x, y, w, h)
{
var img = new Image();
img.src = path;
img.onload = function() {
context.drawImage(img, x, y, w, h);
}
}
I don't understand why you're using pop() to get your object data. You could instead access each object using shapes[i] notation and, as a bonus, store image handles in each object:
for(var i=0; i<shapes.length; i++)
{
if(shapes[i].name == 'image' && shapes[i].pageNum == pNum)
{
shapes[i].img = new Image();
shapes[i].img.src = shapes[i].path;
shapes[i].img.onload = function() {
context.drawImage(shapes[i].img, shapes[i].x, shapes[i].y, shapes[i].w, shapes[i].h);
}
}
}