Canvas resetting or not populated?

Symptom: Only one card (of 4) appears on my canvas – and, I think, at the coordinates of the last card drawn. However, when I insert an “Alert” statement in the loop, all four cards appear and in the proper positions.

My research into the sitepoint forum posts (sources after code) suggests that I may need an EventListener to assure the image is finished loading. Is that my problem? If so, how would you reconfigure my loop to do so?

<!DOCTYPE html>
<html>
<head>
<title>Simple Solitare</title>
<meta name="" content="">
</head>

<body>
<canvas id="board" width="800px" height="600px"></canvas>
<script src="deck.js"></script>
<script type="text/javascript">
 var DealObj = Deal();
 var canvas = document.getElementById('board');
 var context = canvas.getContext('2d');
 var xWidth = 69;
 var xPos = xWidth;
 for (j=1;j<5;j++) {
 //	alert(xPos);
  var nextCard = GetNextCard();
  var imageObj = new Image();
  imageObj.onload = function() {
   context.drawImage(imageObj, xPos, 50);
  }
  xPos += xWidth;
  imageObj.src =nextCard + ".gif";
} // end for
</script>
</body>
</html>

my deck.js file: (which I would welcome your comments as well)

<!--
	deck=new Array(53);
	cardsLeft = 52;
	function Deal(){
		for(i=1;i<14;i++){
			deck[i] = i+"c";
			deck[i+13] = i+"d";
			deck[i+26] = i+"h";
			deck[i+39] = i+"s";
		} // end for
		cardsLeft = 52;
	} // end function Deal

	function GetNextCard() {
		if(cardsLeft < 1) return false;
		// get a number between 1 and the number of remaining cards
		i = Math.floor(Math.random() * cardsLeft) + 1;
		var nextCard = deck[i];			// pull that card
		deck[i] = deck[cardsLeft];      // replace that card with the last in the deck
		--cardsLeft;
		return nextCard;
	} // end function GetNextCard
-->

references:

(1) My October 15 2013 post entitled “Body script invoking before onload script?”
(2) http://www.sitepoint.com/image-manipulation-with-html5-canvas-a-sliding-puzzle-2/ states

var img = new Image();
img.src = ‘http://www.brucealderman.info/Images/dimetrodon.jpg’;
img.addEventListener(‘load’, drawTiles, false);

The event listener is there to guarantee the image is finished loading before the browser attempts to draw it. The canvas won’t display the image if it’s not ready to be drawn.

(3) Which, in turn, Pullo referenced in his reply to thread http://www.sitepoint.com/forums/showthread.php?1090542-Creating-a-Puzzle-where-pieces-are-dragged-into-place&highlight=canvas on 1:48 June 1 2013

Hi Grnadpa,

The problem lies with this loop:

for (j=1;j<5;j++) {
    var nextCard = GetNextCard();
    var imageObj = new Image();
    imageObj.onload = function() {
        context.drawImage(imageObj, xPos, 50);
    }
    xPos += xWidth;
    imageObj.src = nextCard + ".gif";
}

Your imageObj.onload function references the variables imageObj and xPos, but the function executes after the loop has finished, and so xPos is the same value for all images, resulting in only the last image being visible.

The way to solve this is to call a function within the loop, passing the variables in as parameters:

var canvas = document.getElementById('board'),
    context = canvas.getContext('2d');

function displayCard(card, xPos, yPos) {
    var imageObj = new Image();
    imageObj.src = card + ".gif";
    imageObj.onload = function() {
       context.drawImage(imageObj, xPos, yPos);
    }
}

var xWidth = 100,
    xPos = xWidth;

for (j = 1; j < 5; j++) {
    displayCard(GetNextCard(), xPos, 50);
    xPos += xWidth;
}

Doing this creates a closure, which effectively freezes the current values of the parameters passed to displayCard() until the onload event handler is run.

Thank you fretburner.
If you will indulge me, despite your lucid explanation, I’m still not completely clear on why this solved my problem. Would you help my basic literacy?

My confusion is that the unnamed function [ imageObj.onload = function() ] lexically exists within the same for-loop as the imageObj call to the NextCard().function. So how is it “after the loop is finished”?

Speaking of that unnamed function, why does context.drawImage… require enclosure within a function? i.e. why not imageObj.onload = context.drawImage(…

Finally, the only time I’ve seen an onLoad was in the <body onload=…> . Is this the same “onload”, but used in context of an image load?

Again, thank you I never would have figured this out on my own – as I find this all quite counter-intuitive. (Not that it should be intuitive).

Regards,

grNadpa

Even though you assign the onload handler within the loop, it doesn’t get executed immediately, so the loop continues and the anonymous function is executed later, when the image has finished loading.

Here is the sequence of events with your original code:


1st iteration:
    xPos == 69
    create new image (1)
    assign onload handler (1)

2nd iteration:
    xPos == 138
    create new image (2)
    assign onload handler (2)

3rd iteration:
    xPos == 207
    create new image (3)
    assign onload handler (3)

4th iteration:
    xPos == 276
    create new image (4)
    assign onload handler (4)
    
    
handler (1) executes:
    xPos == 276
    draw image (1) on canvas
    
handler (2) executes:
    xPos == 276
    draw image (2) on canvas
    
handler (3) executes:
    xPos == 276
    draw image (3) on canvas
    
handler (4) executes:
    xPos == 276
    draw image (4) on canvas

When you assign event handlers (eg. onload, onclick etc.) the function is called and passed a single argument, which is an Event object (it contains details of the element which triggered the event, among other things). If you want to call a function like context.drawImage, that requires different arguments, then you have to wrap it in an anonymous function which you use as the handler (and which will receive the Event object as a parameter).

At least several kinds of DOM element fire an onload event. I’m not sure if all elements do - perhaps someone else could confirm this if they know?

I’m not the worlds best explainer when it comes to these things, so if I’ve missed out anything or you have any more questions just let me know.

Thank you fretburner.

I suppose I should let this rumble around in my mainframer mindset before taking you up on your follow-up offer. But (and I’m certain to be embarrassed by your answer), would you do a “1st” and “2nd” iteration under the corrected scenario? That is, I don’t understand how adding the separate function alters when the event handler executes.

grNadpa

The loop still executes four times, but it no longer affects the image loading code.

Within displayCard, the variables card, xPos, and yPos are part of the scope of that function (i.e. they’re not available outside of the function, and they’re separate from any external variables with the same name). When we then assign an anonymous function to imageObj.onload, that function gets (and keeps) access to the variables within the displayCard scope, even after displayCard has finished executing.

If we take the 2nd iteration of the loop as an example, the value of xPos is 138 at the point in time when we pass it into displayCard. Whenever the the image loads and the onload callback (our anonymous function) executes, the value of xPos that we’re using will be 138.

The execution now looks like this:


1st iteration:
    xPos == 69
    call displayCard (1):
        assign onload handler (1) with private copy of passed arguments

2nd iteration:
    xPos == 138
    call displayCard (2):
        assign onload handler (2) with private copy of passed arguments

// etc..    
    
handler (1) executes:
    xPos == 69
    draw image (1) on canvas
    
handler (2) executes:
    xPos == 138
    draw image (2) on canvas
    
// etc..

This page on closures explains what’s going on in a bit more detail.

Got it, I think. Thanks for your patience.

The reference on closures is REALLY SCARY. I can only imagine the horrendous stories suffered in debugging and support given closure side effects and data persistence. Wow! I’d have no idea how to develop sufficiently robust test suites for anything but the most trivial commercial applications.

I may have the idea now. If the onLoad function could be renamed:
“GoAheadAndDoAnythingElseYouNeedToWhileYouWaitButDoNotReturnControlToTheUserlUntil_I_AmFinished(){}”
then I understand the concept.

I understood the parallel processing, what I didn’t understand was the “ButDoNotReturn…I_AmFinished” dependency.

So is that right, or am I just creative in my confusion?

grNadpa