Variabele ongedefinieerd volgens Firefox

Status
Niet open voor verdere reacties.

Joshua822

Gebruiker
Lid geworden
17 apr 2008
Berichten
306
Hallo allemaal!

Ik heb een erg raar probleem, ik heb het volgende document:
Code:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
    <head>
        <title>Bass Boss</title>
        <script type="text/javascript">
            /* These global variables contain a reference to the canvas and its 2D-context.
               We need these in all functions and we only have one canvas area, so grabbing these variables
               in every function is a waste of resources. */
            var canvas;
            var canvas_context;
            
            /* This function makes all other functions ready to use. 
               ALWAYS CALL THIS FUNCTION FIRST, OR OTHER FUNCTIONS WILL 
               WORK WRONGLY OR NOT AT ALL. */
            function Initialize ( )
            {
                /* Get a reference to the raw canvas object */
                canvas = document.getElementById ( "drawing_area" );
                /* Get the context for this canvas object */
                canvas_context = canvas.getContext ( "2d" );
                /* Set the stroke color and width */
                canvas_context.fillStyle = "brown";
                canvas_context.lineWidth = 10;
                /* Set the fill color */
                canvas_context.fillStyle = "red";             
            }
            /* This function erases everything on the canvas. */
            function EraseCanvas ( )
            {
                canvas_context.clearRect ( 0, 0, canvas.width, canvas.height );
            }
            /* This function draws a falling square. Accepts one argument, namely the 
               distance the block should fall  per second */
            function DrawFallingSquare ( fall_per_second )
            {
                /* This variable holds the constantely increasing y-value for the position
                   to draw the square, so when we draw the square in certain intervals it will 
                   appear to be falling. */
                var y_offset;
                /* In this function the main drawing happens. We cannot put this
                   unencapsulated in the code because drawing needs to be done in 
                   certain lapses of times and Javascript only has setInterval() 
                   for such a task, which calls a function after a certain interval. */
                var MainDrawingFunction = function ( ) {
                    EraseCanvas ( );
                    canvas_context.strokeRect ( 100, y_offset, 100, 100 );
                    canvas_context.fillRect ( 100, y_offset, 100, 100 );
                }
                
                for ( y_offset = 0; y_offset <= 300; y_offset += fall_per_second )
                {
                    setInterval ( MainDrawingFunction, 1000 );
                }    
            }
        </script>
    </head>
    <body>
        <canvas id="drawing_area" width="300" height="300">
            <p>Deze browser ondersteund deze webapplicatie niet.</p>
        </canvas>
        <script type="text/javascript">
            Initialize ( );
            DrawFallingSquare ( 5 );
        </script>        
    </body>
</html>
Het probleem is dat wanneer ik de DrawFallingSquare-functie aanroep Firefox klaagt dat de variabele y niet gedefinieerd is, terwijl ik nergens een variabele y gebruik!

Weet iemand waardoor dit komt?

Alvast bedankt,
Joshua
 
y is de naam van een variabele die gebruikt wordt in canvas_context.strokeRect en canvas_context.fillRect. Het probleem is dus dat de variabele y_offset lokaal (in MainDrawingFunction) niet gedefiniëerd is: y_offset is undefined op het moment dat MainDrawingFunction gedefiniëerd wordt.

Mogelijke oplossingen (zie hieronder):
- Parametriseer de variabele
- Maak in iedere for-iteratie een nieuwe functie aan. Het verschil is dan dat y_offset wél een waarde heeft wanneer die functie gedefiniëerd wordt.

[JS]function DrawFallingSquare ( fall_per_second )
{
var y_offset;

var MainDrawingFunction = function ( y_offset ) {
EraseCanvas ( );
canvas_context.strokeRect ( 100, y_offset, 100, 100 );
canvas_context.fillRect ( 100, y_offset, 100, 100 );
}

for ( y_offset = 0; y_offset <= 300; y_offset += fall_per_second )
{
setInterval ( "MainDrawingFunction(" + y_offset + ")", 1000 );
}
}[/JS]
[JS]function DrawFallingSquare ( fall_per_second )
{
var y_offset;

for ( y_offset = 0; y_offset <= 300; y_offset += fall_per_second )
{
setInterval ( function ( ) {
EraseCanvas ( );
canvas_context.strokeRect ( 100, y_offset, 100, 100 );
canvas_context.fillRect ( 100, y_offset, 100, 100 );
}, 1000 );
}
}[/JS]
 
De 1e oplossing gebruikt echter zo'n string geval in een eval-achtige functie, dus dat is niet echt cool. Zoiets is netter:

[js]setInterval(function(y)
{
return function()
{
MainDrawingFunction(y);
}
}(y_offset), 1000);[/js]

Maargoed, dit is weer een hoop gedoe; de 2e oplossing van Robin ziet er een stuk strakker uit.




Fijne dagen allebij.
 
Bedankt, ik stuur y_offset nu als parameter mee aan MainDrawingFunction ( ). Maar er is nu nog een probleem in DrawFallingSquare ( ), namelijk dat MainDrawingFunction ( ) kennelijk enkel in de eerste iteratie van de for-loop wordt aangeroepen:
Code:
            function DrawFallingSquare ( fall_per_second )
            {
                /* This variable holds the constantely increasing y-value for the position
                   to draw the square, so when we draw the square in certain intervals it will 
                   appear to be falling. */
                var y_offset;
                /* In this function the main drawing happens. We cannot put this
                   unencapsulated in the code because drawing needs to be done in 
                   certain lapses of times and Javascript only has setInterval() 
                   for such a task, which calls a function after a certain interval. */
                var MainDrawingFunction = function ( y_offset ) {
                    EraseCanvas ( );
                    canvas_context.strokeRect ( 100, y_offset, 100, 100 );
                    canvas_context.fillRect ( 100, y_offset, 100, 100 );
                }
                
                for ( y_offset = 0; y_offset <= 300; y_offset += fall_per_second )
                {
                    alert ( y_offset );
                    setInterval ( MainDrawingFunction ( y_offset ), 1000 );
                }    
            }

Heb je ook een idee hoe dit komt? Alvast bedankt :)
 
setInterval ziet er zo uit:

[js]function setInterval(func, time);[/js]

waar func een functie-referentie is, en time de tijd in welke interval deze functie steeds wordt aangeroepen. Nu is het probleem met jou code dat je niet een functie-referentie meegeeft, maar de functie aanroept (immers, er staan haakjes achter, en het heeft een parameter).

Wat er nu gebeurt is dat als Javascript de code doorloopt (parsed), deze de functie aanroept en 'neerzet' wat er uit komt. In dit geval dus wat de functie MainDrawingFunction returnt; wat dus niets is. Hier kan de setInterval natuurlijk niets mee; deze wilt een Function hebben!


Om het wat duidelijker te maken, dit is het verschil:

[js]function iets()
{
alert('iets!');
}

// het verschil:

iets; // dit is een Function (zoals een getal een 'Number' is en een stukje text een 'String'). Doet verder niets, tenzij je 'm aanroept!
iets(); // dit is een functie-aanroep. Zal iets als 'undefined' teruggeven omdat er geen return-waarde is[/js]

Het 1e is dus wat dus in een setInterval() moet komen. In ieder geval, als je nog wat meer info hierover wilt hebben kan je eens hier verderlezen. 't Is wel een beetje wat complexere Javascript.



Nu, de oplossing is simpel: gebruik de code doe ik eerder heb gepost, of de (tweede) oplossing van Robin. In het stukje code dat ik poste return je een Function, welke de MainDrawingFunction aanroept. Zo kan het dus wel.
 
Laatst bewerkt:
Oh OK, ik begrijp het nu, en de functie wordt nu iedere iteratie uitgevoerd, maar het probleem is dat er nog steeds niet getekend wordt:
[JS] function DrawFallingSquare ( fall_per_second )
{
/* This variable holds the constantely increasing y-value for the position
to draw the square, so when we draw the square in certain intervals it will
appear to be falling. */
var y_offset;

for ( y_offset = 0; y_offset <= 300; y_offset += fall_per_second )
{
setInterval ( function ( ) {
EraseCanvas ( );
canvas_context.strokeRect ( 100, y_offset, 100, 100 );
canvas_context.fillRect ( 100, y_offset, 100, 100 );
}, 1000 );
}
}[/JS]
Weet je ook waarom dit niet lukt?

Alvast bedankt,
Joshua
 
Laatst bewerkt:
Als je de width en height van het canvas eens op 800 zet, zie je 't wel!

HTML:
<canvas id="drawing_area" width="800" height="800">
   ...
</canvas>

Je zegt namelijk dit:

[js]canvas_context.fillRect ( 100, y_offset, 100, 100 );[/js]

wat betekent: maak een vierkantje op positie 100, y_offset met een breedte van 100 en een hoogte van 100. Als je dan het <canvas> maar 300 hoog maakt, en y_offset is 300, dan zie je natuurlijk niets, want 't coordinaat 100, 300 ligt buiten het scherm!
 
Laatst bewerkt:
Dank je wel! Daar zat inderdaad de oplossing in voor het tekenen van het vierkant. Maar wat nog altijd vervelend is is dat Firefox het vierkant niet op de nieuwe y_offset tekent in elke iteratie, maar slechts in enkele. Is dit een instelling ofzo?

Sorry dat dit probleem zoveel tijd kost, ik zou namelijk gewoon willen leren hoe je met het canvas-element mooie, geanimeerde graphics zou willen kunnen maken maar het is toch veel moeilijker als ik dacht.
 
Ik heb 't expres niet gezegd vorige post, want ik weet nogsteeds niet precies wat precies het idee is van de code. Wat wil je precies bereiken? Moet het vierkant van boven naar beneden vallen? In dat geval is de code een stuk lastiger dan nodig is. Het punt is dat je een loop gebruikt om setInterval's te 'spawnen'. Probleem is dat setInterval waarschijnlijk setTimeout moet zijn (immers, het moet maar 1x gebeuren, en na een bepaalde tijd), en sowieso zijn timers-in-loops niet echt praktisch, zoals je waarschijnlijk wel hebt ondervonden.

Onderaan het bericht staat een werkend stukje met wat je (denk ik) bedoeld.




Tijd is geen probleem ;) en tsja, alle nieuwe dingen kosten tijd om te leren. Canvas (of eigenlijk, dit is een animatie/timer probleem) is nou eenmaal niet het makkelijkste onderwerp.





Dit is werkende code; prop het in een html bestandje, open het met Firefox, en zie het werken! Dan kan je eens lekker de code gaan doorspitten. Heb veel comments neergegooid zodat het wat duidelijker is. Je moet nog even klooien met de setInterval tijd en de += y_current waardes, dan wordt het misschien wat 'vloeiender'.

HTML:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
    <head>
        <title>Bass Boss</title>
        <script type="text/javascript">
            /* These global variables contain a reference to the canvas and its 2D-context.
               We need these in all functions and we only have one canvas area, so grabbing these variables
               in every function is a waste of resources. */
            var canvas;
            var canvas_context;
            
            /* This function makes all other functions ready to use. 
               ALWAYS CALL THIS FUNCTION FIRST, OR OTHER FUNCTIONS WILL 
               WORK WRONGLY OR NOT AT ALL. */
            function Initialize ( )
            {
                /* Get a reference to the raw canvas object */
                canvas = document.getElementById ( "drawing_area" );
                /* Get the context for this canvas object */
                canvas_context = canvas.getContext ( "2d" );
                /* Set the stroke color and width */
                canvas_context.fillStyle = "brown";
                canvas_context.lineWidth = 10;
                /* Set the fill color */
                canvas_context.fillStyle = "red";             
            }
            /* This function erases everything on the canvas. */
            function EraseCanvas ( )
            {
                canvas_context.clearRect ( 0, 0, canvas.width, canvas.height );
            }




// laten we 't eens aanpakken zodat het niet fout KAN gaan: stap voor stap.
// begin eens met een functie die zo'n mooi vierkantje tekent,
// en geef 't twee parameters: de x en y coordinaat.

function drawSquare(x, y)
{
   canvas_context.strokeRect(x, y, 100, 100);
   canvas_context.fillRect(x, y, 100, 100);
}


// alright! Nu, tijd om eens een var te maken waar we de y-coordinaat
// in opslaan. Deze is globaal, dat maakt het een STUK makkelijker!
// dit is in grotere projectjes niet echt netjes, maar daar zijn genoeg
// oplossingen voor.

var current_y = 0;   // begin bovenaan!


// de setInterval slaan we hier op, zodat we 'm kunnen wissen als
// eht vierkant aan de onderkant van het scherm zit!

var timerDing;


// tijd voor de magie: elke x seconde zetten we de y-coordinaat
// omhoog (meer pixels vanaf de bovenkant) en tekenen we het vierkant

// dit is de functie die we steeds aanroepen:

function a_step()
{
   // wis scherm
   EraseCanvas();

   // teken vierkantje
   drawSquare(100, current_y);

   // verhoog y-coordinaat, als dat nog kan.
   // anders, wis de timer

   if(current_y >= 700)
   {
      // 800 hoogte - 100 van vierkant = 700
      clearInterval(timerDing);   // wis timer
   }else{
      current_y += 5;   // 5 px met elke stap erbij
   }
}



// een functie die de setInterval aanroept. Niet echt nodig, maar sinds je al een Inititalize() functie
// had, dacht ik, waarom niet.


function doeMagie()
{
   // sla de setInterval op in 'timerDing' zodat we het later kunnen wissen
   // elke 50ms een stap!

   timerDing = setInterval(a_step, 50);
}







        </script>
    </head>
    <body>
        <canvas id="drawing_area" width="800" height="800">
            <p>Deze browser ondersteund deze webapplicatie niet.</p>
        </canvas>
        <script type="text/javascript">
            Initialize();
            doeMagie();
        </script>        
    </body>
</html>

code is lekker flexibel dus je kan nu ook bijvoorbeeld de x-coordinaat veranderen per 'iteratie'. Flexibele code is de 1e stap naar goede en makkelijke code!


[edit] ohja, en regel 22 is overbodig, want op regel 25 zet je 'm naar rood! [/edit]
 
Laatst bewerkt:
Dank je wel! Dat was precies wat ik zocht! :D

Echt waar, enorm bedankt. Ik zal wel nog heel veel fouten maken, maar hier heb ik in ieder geval erg veel van geleerd!
 
Status
Niet open voor verdere reacties.
Terug
Bovenaan Onderaan