I am using JcanvaScript library to work with Html5 canvas and now I want to load some images in my Canvas, but only last image loading is successful, I can't see any other images but the last one and I don't know what is wrong with my code.
here is the code
<!DOCTYPE HTML>
<html>
<head>
<style>
canvas {
border: 1px solid #9C9898;
}
</style>
<script src="jcanvas/jCanvaScript.1.5.15.js"></script>
<script>
var imagesUrl = [];
var imagesObj = [];
var canvasWidth = 800;
var canvasHeight = 600;
var imagesIdPrefix = 'images_';
var canvas = "canvas";
var fps = 60;
imagesUrl.push('images/image1.jpg');
imagesUrl.push('images/image2.jpg');
imagesUrl.push('images/image3.jpg');
imagesUrl.push('images/image4.jpg');
function init(){
for (i = 0; i < imagesUrl.length; i++){
var xImage = new Image();
xImage.src = imagesUrl[i];
xImage.onload = function(){
jc.start(canvas, fps);
jc.image(xImage, i*10, 0, 200, 200)
.id(imagesIdPrefix.toString() + i.toString());
jc.start(canvas, fps);
}
imagesObj.push(xImage);
}
}
</script>
</head>
<body onload="init()">
<canvas id="canvas" width="800px" height="600px"></canvas>
</body>
</html>
The bug has been identified in comment by MrOBrian : when the onload callbacks are called, i has the value it has at the end of the loop. That's the reason why you think that only last image loading is successful.
A standard solution is to embbed the content of the loop in a closure keeping the value of i :
for (i = 0; i < imagesUrl.length; i++){
(function(i){
var xImage = new Image();
xImage.src = imagesUrl[i];
xImage.onload = function(){
jc.start(canvas, fps);
jc.image(xImage, i*10, 0, 200, 200)
.id(imagesIdPrefix.toString() + i.toString());
jc.start(canvas, fps);
}
imagesObj.push(xImage);
})(i);
}
EDIT :
when you want to ensure all the needed images are loaded before running some code, you may use this function :
function onLoadAll(images, callback) {
var n=0;
for (var i=images.length; i-->0;) {
if (images[i].width==0) {
n++;
images[i].onload=function(){
if (--n==0) callback();
};
}
}
if (n==0) callback();
}
The images must have been provided their src first. You call the function like this :
onLoadAll(imagesObj, function(){
// do something with imagesObj
});
You need to make sure the images are loaded before you go and loop through them.
By defining your onload function inside of the for loop, the image may not have loaded by that point, and so the script will skip over it. And when it's done looping, the last image may be the only one that's loaded in time.
I would recommend adding a loader for each of the images outside the for loop, and when all the images are loaded go ahead and loop through the array.
A loader and checker looks something like this:
var loadedImages = [];
var loadedNumber = 3 //the number of images you need loaded
var imageToBeLoaded = new Image();
imageToBeLoaded.onload = function() {
loadedImages.push(true);
checkImages();
}
function checkImages() {
if (loadedImages.length != loadedNumber) {
setTimeout(checkImages(),1000);
} else {
init(); //your draw function
}
}
Related
I have an HTML page with 2 canvases side-by-side. The left one has a clickable upper region. The right one is where all the drawing takes place. I want the user to click the region on the left, which will load a new image on the right, and replace the one that is already there, but I have to click twice for the new image to show up. Why is this? See code.
THE HTML
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<style>
#centerAll {
margin-left:auto;
margin-right:auto;
width:1300px;
}
</style>
</head>
<body>
<div id="centerAll">
<canvas id="TheLeftCanvas" width="300" height="500"
style="border:2px solid #999999;"></canvas>
<canvas id="TheRightCanvas" width="900" height="500"
style="border:2px solid #999999;"></canvas>
<script type="text/javascript" src="whyClickTwiceForImage.js">
</script>
</div>
</body>
</html>
AND THE JAVASCRIPT
window.addEventListener("load", eventWindowLoaded, false);
function eventWindowLoaded () {
canvasApp();
}
function canvasApp() {
var rightCanvas = document.getElementById("TheRightCanvas");
//THE EMPTY CANVAS ON THE RIGHT, WHICH DRAWS THINGS
var rightCtx = rightCanvas.getContext("2d");
var leftCanvas = document.getElementById("TheLeftCanvas");
//THE EMPTY CANVAS ON THE LEFT, WHICH HANDLES USER CLICKS
var leftCtx = leftCanvas.getContext("2d");
var currentMouseXcoor = 0;
var currentMouseYcoor = 0;
var imagesToLoad = 0; //USED TO PRE-LOAD SOME OTHER VISUALS, BEFORE THE USER STARTS
var imagesLoaded = 0;
var picturesTheUserCanChoose = ["pic01.png", "pic02.png",
"pic03.png", "pic04.png", "pic05.png"];
var nameOfPicTheUserChose = "pic05.png"; //LET'S JUST SAY "pic05.png" IS A PLACEHOLDER PICTURE... TO START OFF
var legitMouseClick = 0; //DID THE USER CLICK IN THE UPPER REGION OF THE LEFT CANVAS?
var loaded = function(){ // "LOADED" AND "LOAD IMAGE" ARE USED TO PRELOAD ALL THOSE OTHER VISUALS BEFORE THE USER STARTS
imagesLoaded += 1;
if(imagesLoaded === imagesToLoad){
imagesToLoad = 0;
imagesLoaded = 0;
drawScreen(); // OK... ALL LOADED.... SO DRAW THE SCREEN
}
}
var loadImage = function(url){ // "LOADED" AND "LOAD IMAGE" ARE USED TO PRELOAD ALL THOSE OTHER PICS BEFORE THE USER STARTS
var image = new Image();
image.addEventListener("load",loaded);
imagesToLoad += 1; // ADD THE NUMBER TO THE IMAGES NEEDED TO BE LOADED
image.src = url;
return image; // ...AND RETURN THE IMAGE
}
//--------------------------------------------------------------
leftCanvas.addEventListener('mouseup', function(evt) { var
mousePos = doMouseUp(leftCanvas, evt);
currentMouseXcoor = mousePos.x; currentMouseYcoor =
mousePos.y; processMousePosition();}, false);
//--------------------------------------------------------------
function drawScreen(){
clearScreen();
rightCtx.drawImage(picture1,0,0,100,100);
}
//THIS FUNCTION PRESUMABLY LOADS IMAGES ON-THE-FLY... BUT WHY DOES THE LEFT CANVAS REGION HAVE TO BE CLICKED TWICE FOR THE NEW IMAGE TO SHOW?
function processMousePosition() {
if(currentMouseYcoor >= 0 && currentMouseYcoor <= 50){
//IF THE CLICK IS IN THE UPPER REGION OF THE LEFT CANVAS
nameOfPicTheUserChose = picturesTheUserCanChoose[0];
//WE'LL SAY THE USER CHOSE THE VERY FIRST PIC IN THE ARRAY
picture1.src = ("picturesFolder/"+ nameOfPicTheUserChose);
picture1.onload = legitMouseClick = 1;//I JUST MADE UP FOR SOMETHING TO HAPPEN WHEN THE NEW PICTURE LOADS???
alert(nameOfPicTheUserChose); // THE ALERT CORRECTLY SHOWS THE NAME OF THE FIRST PIC IN THE ARRAY
}
drawScreen();
}
function doMouseUp(leftCanvas, evt){
var rect = leftCanvas.getBoundingClientRect();
return { x: evt.clientX - rect.left, y: evt.clientY - rect.top};
}
function clearScreen(){
rightCtx.clearRect(0, 0, 900, 500);
leftCtx.clearRect(0, 0, 300, 500);
}
//var someOtherVisual1 =
//loadImage("backgroundArtFolder/someOtherVisual1.jpg");
//var someOtherVisual2 =
//loadImage("backgroundArtFolder/someOtherVisual2.png");
//var someOtherVisual3 =
//loadImage("backgroundArtFolder/someOtherVisual3.png");
//var someOtherVisual4 =
//loadImage("backgroundArtFolder/someOtherVisual4.png");
//var someOtherVisual5 =
//loadImage("backgroundArtFolder/someOtherVisual5.png");
var picture1 = loadImage("picturesFolder/pic05.png");
//LET'S JUST PRELOAD "pic05.png" AS A PLACEHOLDER... TO START OFF
}
You can change the image load on click in js file picture1.src = ("picturesFolder/"+ nameOfPicTheUserChose); to picture1 = loadImage("picturesFolder/"+nameOfPicTheUserChose); and picture1.onload = legitMouseClick = 1; to 'legitMouseClick = 1'
var url_array = ["ulr1", "ulr2", "ulr3", "ulr4", "ulr5"];
var img = document.createElement('img');
for (i = 0; i < url_array.length; i++){
var div = document.createElement('div');
div.setAttribute("id", "div"+i);
document.getElementById('main').appendChild(div);
document.getElementById('div'+i).style.width = ImageWidth+5+"px";
document.getElementById('div'+i).style.height = ImageHeight+5+"px";
console.log("\ncreate all the DIVs. \nFor loop count: "+i);
img.src = loadImage(url_array[i], ImageWidth, ImageHeight);
try{throw img}
catch(c_img) {
document.getElementById('div'+i).appendChild(c_img);
console.log("after load, and append, in Catch: "+img.src);
console.log("div NR = "+document.getElementById('div'+i).id);
console.log(document.getElementById('div'+i).childNodes.length);
} //catch
} // FOR
function loadImage(URL, h, w)
{
console.log("loadImage callaed with URL = "+URL);
return url = URL+Date.now().toString(10);
}
For-Loop is supposed to retrieve urls address of an images (from camera) and append them to DIV. DIV and IMGs are created on the fly. Problem is that only last DIV become image holder.
I am using catch-try to force execute code immediately so each separate Div+i will have distinctive image. Also construction like this one (immediately invoked function expression):
(function(){
something here;
})();
for creating "private" scope gives no hope. Either "Let" - which supposed to define variable locally is not helping. *My knowledge here is limited and I'm relying on data found on web site (can give link later if it is not against rules).
Output of console.log() is not helping much.
Everything goes as it should, except for the childNodes.length which is 1 for each for-loop iteration - that means each DIV have its IMG child I guess... If so - why I can't see them?
I feel I am close to what I want to achieve - using Intervals() refresh each DIV with new camera snapshot, but I need to solve this current issue.
Example of code ready to use:
js.js:
var url_array = [
"https://images.pexels.com/photos/104827/cat-pet-animal-domestic-104827.jpeg",
"https://images.pexels.com/photos/45201/kitty-cat-kitten-pet-45201.jpeg",
"https://images.pexels.com/photos/617278/pexels-photo-617278.jpeg"
];
var ImageWidth = 640;
var ImageHeight = 480;
var img = document.createElement('img');
for (i = 0; i < url_array.length; i++){
var div = document.createElement('div');//.setAttribute("id", "div0");
div.setAttribute("id", "div"+i);
document.getElementById('main').appendChild(div);
document.getElementById('div'+i).style.width = ImageWidth+5+"px";
document.getElementById('div'+i).style.height = ImageHeight+5+"px";
var color = ((Math.floor(Math.random() * (16777215)) + 1).toString(16)); // from 1 to (256^3) -> converted to HEX
document.getElementById('div'+i).style.background = "#"+color;
document.getElementById('div'+i).style.width = ImageWidth+5+"px";
document.getElementById('div'+i).style.height = ImageHeight+5+"px";
console.log("\ncreate all the DIVs. \nFor loop count: "+i);
img.src = loadImage(url_array[i], ImageWidth, ImageHeight);
try{throw img}
catch(c_img) {
document.getElementById('div'+i).appendChild(c_img);
console.log("after load, and append, in Catch: "+img.src);
console.log("div NR = "+document.getElementById('div'+i).id);
console.log(document.getElementById('div'+i).childNodes.length);
} // catch
} // FOR
function loadImage(URL, h, w)
{
console.log("loadImage callaed with URL = "+URL);
return url = URL+"?auto=compress&h="+h+"&w="+w;
}
HTML:
<html>
<head>
<meta charset="utf-8">
<STYLE>
div#main {
padding: 5px;
background: black;
}
</STYLE>
</head>
<body>
<script type="text/javascript">
function downloadJSAtOnload() {
var element = document.createElement("script");
element.src = "js.js";
document.body.appendChild(element);
}
if (window.addEventListener)
window.addEventListener("load", downloadJSAtOnload, false);
else if (window.attachEvent)
window.attachEvent("onload", downloadJSAtOnload);
else window.onload = downloadJSAtOnload;
</script>
<div id="main"></div>
</body>
</html>
The problem is that your
var img = document.createElement('img');
is outside the loop - you're only ever creating one img. When appendChild is called on an element that already exists in the DOM (such as on the second, third, fourth, etc iteration), the element gets removed from its previous location and inserted into the new location.
Create the image inside the loop instead, and try not to implicitly create global variables - when declaring new variables, always use let or const.
Also, when you do
var div = document.createElement('div');
you have a reference to the div you just created - there's no need to assign an id to it in order to select it with
document.getElementById('div'+i)
in below lines in the same scope. Instead, just keep referencing the div:
const ImageWidth = 200;
const ImageHeight = 200;
const main = document.getElementById('main');
var url_array = ["ulr1", "ulr2", "ulr3", "ulr4", "ulr5"];
for (let i = 0; i < url_array.length; i++){
const img = document.createElement('img');
const div = document.createElement('div');
main.appendChild(div);
div.style.width = ImageWidth+5+"px";
div.style.height = ImageHeight+5+"px";
img.src = loadImage(url_array[i], ImageWidth, ImageHeight);
div.appendChild(img);
}
console.log(main.innerHTML);
function loadImage(URL, h, w) {
return URL+Date.now().toString(10);
}
div#main {
padding: 5px;
background: black;
}
<div id="main">
</div>
Hello stackoverflow community,
I am working on a graphics engine in JavaScript, and currently am trying to repeat an image to use it as a background. Strangely, the initalize() function is not being called, until directly called in the browser console. I before this, I had buttons to move the image which I am now trying to use as the background. Even then, the image would only appear after one of the buttons was pressed. (that old code is commented out). Here is the JavaScript:
var ctx;
var cam_x = 0,cam_y = 0;
//End of Global Variables
function initialize(){
var canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);//RESETS
var img=new Image();
img.src = "brick.png";
var i;
var j;
for(i = 0; i < 10; i++){
for(j = 0; j < 10; j++){
drawTile(img, i, j, 0);
}
}
console.log("should be drawing");
// drawTile(img, 0,0,0);
// drawTile(img, 1,0,0);
// drawTile(img, 2,0,0);
// drawTile(img, 1,1,0);
// drawTile(img, 2,2,0);
// console.log("should be drawing");
}
function setTransformations(){
ctx.translate(-cam_x,-cam_y);
}
function drawTile(img,x,y,z){
ctx.setTransform(1,0,-1,1,0,z);
setTransformations();
ctx.drawImage(img,x*32,y*32);
ctx.setTransform(1,0,0,1,0,0);
}
function camtranslate(x,y){
cam_x += x;
cam_y += y;
initialize();
}
The commented out drawTile() calls were the to warp the image so it looked oblique (but that's not too relevant). Below is the HTML file:
<!DOCTYPE html>
<html>
<head>
<style>
body {
background-color: #1ABC9C
}
</style>
<script src="main.js"></script>
</head>
<body>
<!-- <button onclick="camtranslate(16,0);"> Right </button>
<button onclick="camtranslate(-16,0);"> Left</button> <br>
<button onclick="camtranslate(0,-16);"> Up </button>
<button onclick="camtranslate(0,16);"> Down </button>
-->
<center>
<h1>Akimbo</h1>
<canvas id="canvas" height="600" width="600" style="border:1px solid #7F8C8D"></canvas>
</center>
<script>camtranslate(16, 0);</script>
</body>
</html>
The commented out buttons made the image appear on pressing them, but now that I've commented them out, even though I've called the function, it doesn't work. The HTML file should call the initialize() function, but nothing appears in the canvas until I call the function directly in the console. How can I make it so that the background appears immediately upon the page's being loaded?
Thanks
Things take time to load and setting an images src attribute does not mean that the image is available for use.
You need to use the image.onload event to flag that the image has loaded and call the function that uses the image.
function drawTiles(){ // create the function to draw the tiles
var i;
var j;
for(i = 0; i < 10; i++){
for(j = 0; j < 10; j++){
drawTile(img, i, j, 0);
}
}
}
var img=new Image(); // create image
img.src = "brick.png"; // set the URL and it will start to load it
img.onload = drawTiles; // the image will call your function when it has loaded
Hi I found this code on Stackoverflow for a canvas photo slide show, but I'm just wondering how to make the transitions between images slower?
var loaded = 0, numOfImages = 4;
//first part of chain, invoke async load
var image0 = document.createElement('img'); //this will work in new Chrome
var image1 = document.createElement('img'); //instead of new Image
var image2 = document.createElement('img');
var image3 = document.createElement('img');
//common event handler when images has loaded with counter
//to know that all images has loaded
image0.onload = image1.onload =
image2.onload = image3.onload = function(e) {
loaded+;
if (loaded === numOfImages)
draw(); // <-- second part of chain, invoke loop
}
//show if any error occurs
image0.onerror = image1.onerror =
image2.onerror = image3.onerror = function(e) {
console.log(e);
}
//invoke async loading... you can put these four into your
//window.onload if you want to
image0.src = "img/pic1.jpg";
image1.src = "img/pic2.jpg";
image2.src = "img/pic3.jpg";
image3.src = "img/pic4.jpg";
// this is the main function
function draw() {
var images = new Array(image0, image1, image2, image3),
counter = 0,
maxNum = images.length - 1,
myCanvas = document.getElementById('myCanvas'),
ctx = myCanvas.getContext('2d'),
me = this; //this we need for setTimeout()
//third part of chain, have a function to invoke by setTimeout
this._draw = function() {
//if the next image will cover the canvas
//there is no real need to clear the canvas first.
//I'll leave it here as you ask for this specifically
ctx.clearRect(0, 0, myCanvas.width, myCanvas.height)
ctx.drawImage(images[counter++], 0, 0);
if (counter > maxNum) counter = 0;
setTimeout(me._draw, 1000); //here we use me instead of this
}
this._draw(); //START the loop
}
but I'm just wondering how to make the transitions between images slower?
You have a typo in your onload methods:
loaded++; // instead of loaded+;
The setTimeout controls the delay until me._draw is called again.
For example 3 seconds of delay would be 3000 milliseconds, like this:
setTimeout(me._draw, 3000);
If you instead want an actual transition, you could use something like this:
changing images on a canvas with transitions
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/m8E6J/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" />
<script src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var loaded = 0, numOfImages = 4;
//first part of chain, invoke async load
var image0 = document.createElement('img'); //this will work in new Chrome
var image1 = document.createElement('img'); //instead of new Image
var image2 = document.createElement('img');
var image3 = document.createElement('img');
//common event handler when images has loaded with counter
//to know that all images has loaded
image0.onload = image1.onload =
image2.onload = image3.onload = function(e) {
loaded++;
if (loaded === numOfImages)
draw(); // <-- second part of chain, invoke loop
}
//show if any error occurs
image0.onerror = image1.onerror =
image2.onerror = image3.onerror = function(e) {
console.log(e);
}
//invoke async loading... you can put these four into your
//window.onload if you want to
image0.src = "https://dl.dropboxusercontent.com/u/139992952/stackoverflow/house204-1.jpg";
image1.src = "https://dl.dropboxusercontent.com/u/139992952/stackoverflow/house204-2.jpg";
image2.src = "https://dl.dropboxusercontent.com/u/139992952/stackoverflow/house204-3.jpg";
image3.src = "https://dl.dropboxusercontent.com/u/139992952/stackoverflow/house204-4.jpg";
// this is the main function
function draw() {
var images = new Array(image0, image1, image2, image3),
counter = 0,
maxNum = images.length - 1,
myCanvas = document.getElementById('myCanvas'),
ctx = myCanvas.getContext('2d'),
me = this; //this we need for setTimeout()
//third part of chain, have a function to invoke by setTimeout
this._draw = function() {
//if the next image will cover the canvas
//there is no real need to clear the canvas first.
//I'll leave it here as you ask for this specifically
ctx.clearRect(0, 0, myCanvas.width, myCanvas.height)
ctx.drawImage(images[counter++], 0, 0);
if (counter > maxNum) counter = 0;
setTimeout(me._draw, 3000); //here we use me instead of this
}
this._draw(); //START the loop
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="myCanvas" width=600 height=400></canvas>
</body>
</html>
I am playing with the canvas, and it seems to be working great in FF6, but in Chrome 13, the sprite that I am drawing does not appear reliably. I have done some research and found that the problem stems from the function firing before the asset has loaded completely.
Fiddle here:
http://jsfiddle.net/LqHY9/
Relevant Javascript:
function sprite(ipath, sh, sw, ih, iw){
/* BASIC INFO FOR SPRITE */
this.frameWidth = sw;
this.frameHeight= sh;
frame_rows = ih/sh;
frame_columns = iw/sw;
num_frames = frame_columns*frame_rows ;
this.frame = new Array();
frameNumber = 0;
for(row = 0; row<frame_rows; row++){
for(i=0;i<frame_columns;i++){
this.frame[frameNumber] = {};
this.frame[frameNumber].offsetX = this.frameWidth*i;
this.frame[frameNumber].offsetY = this.frameHeight*row;
frameNumber++
}
}
this.sheight = sh;
this.swidth = sw;
this.raw = new Image();
this.raw.src = ipath;
}
animation=new sprite("http://www.melonjs.org/tutorial/tutorial_final/data/sprite/gripe_run_right.png",64,64,64,512);
context.drawImage(animation.raw, animation.frame[0].offsetX, animation.frame[0].offsetY, animation.frameWidth, animation.frameHeight, 0, 0, animation.frameWidth,animation.frameHeight)
(Don't worry, my context variable is defined, I just cut that bit out, you can see the whole thing in the JSFiddle.)
The Image object has an onload event which you should hook into.
Assuming you have more than one image, you could implement a sort of a "loader". This would basically just take an array of image URLs, load each of them, and listen to their onload events. Once each image has loaded, it would in turn call some other function, which would signal that every resource has finished loading.
the Image() object has an onload(and onerror) event. If you need to execute something after it loads you can attach a function.
e.g.
var img = new Image();
img.onload = function() {
//do something
};
Just make sure you attach the onload before setting the src.
You need to use the onload handler for the image. You must set the handler before you set the .src for the object because in some browsers, the load event may fire immediately upon setting .src if the image is in the browser cache. Here's a piece of pseudo code:
var img = new Image();
img.onload = function () {
// image is now loaded and ready for handling
// you can safely start your sprite animation
}
img.src = "xxx";
You can see related sample code from another answer I wrote here: jQuery: How to check when all images in an array are loaded?.
function Sprite(urls, speed, box)
{
var that = this, running = false, interval = 0, loaded = false;
that.urls = urls;
that.speed = speed;
that.images = new Array();
that.box = box || { x: 0.0, y: 0.0, w: 64, h: 64 };
for(var i = 0; i < that.urls.length; ++i)
{
that.images[i] = new Image();
that.images[i].src = that.urls[i];
that.images[i].id = i;
var len = that.urls.length;
that.images[i].onload = function(){ if(parseInt(this.id) === len) { loaded = true; } };
}
that.current = 0;
var Draw = function(ctx)
{
if(loaded)
{
var curr = that.images[that.current];
ctx.drawImage(curr, 0.0, 0.0, curr.width, curr.height, that.box.x, that.box.y, that.box.w, that.box.h);
}
};
that.Run = function(ctx)
{
if(!running)
{
running = true;
interval = setInterval(function(){
Draw(ctx);
if(that.current < that.urls.length)
{
that.current++;
}
else
{
that.current = 0;
}
}, that.speed);
}
};
that.Clear = function()
{
if(running)
{
running = false;
clearInterval(interval);
}
};
}
// Exemple
var test = new Sprite(["image1.png", "image2.png", "image3.png"], 250);
test.Run(myContext);