My 9 year old son is learning Javascript. I'm not able to easily help him. He's working on a small project, and can't seem to get past an error:
Uncaught ReferenceError: mainLoop is not defined.
This is a great learning opportunity for him. We appreciate any clues as to what's going on in his code that's causing the error. Thanks!
Here's what he's got:
var CANVAS_WIDTH = 800;
var CANVAS_HEIGHT = 400;
var LEFT_ARROW_KEYCODE = 37;
var RIGHT_ARROW_KEYCODE = 39;
//SETUP
var canvas = document.createElement('canvas');
var c = canvas.getContext('2d');
canvas.width = CANVAS_WIDTH;
canvas.height = CANVAS_HEIGHT;
document.body.appendChild(canvas);
window.requestAnimationFrame(mainLoop);
var shapeInfo = {
squares: {
square1: {
x: 10,
y: 10,
w: 30,
h: 30,
color: 'orange'
}
}
};
window.addEventListener('keydown', onKeyDown);
window.addEventListener('keyup', onKeyUp);
var leftArrowKeyIsPressed = false;
var rightArrowKeyIsPressed = false;
var touchingRightEdge = false;
// SENSORS
function sense() {
if (shapeInfo.squares.square1.x <= CANVAS_WIDTH - 30) {
touchingRightEdge = true;
}
// PLAYER CONTROLS
function onKeyDown(event) {
if (event.keyCode === RIGHT_ARROW_KEYCODE) {
rightArrowKeyIsPressed = true;
}
}
function onKeyUp(event) {
if (event.keyCode === RIGHT_ARROW_KEYCODE) {
rightArrowKeyIsPressed = false;
}
}
//MAIN LOOP
function mainLoop() {
window.requestAnimationFrame(mainLoop);
draw();
}
//DRAW
function draw() {
c.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// Draw the frame
c.strokeStyle = 'black';
c.strokeRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// Draw square1
c.fillStyle = shapeInfo.squares.square1.color;
c.fillRect(shapeInfo.squares.square1.x, shapeInfo.squares.square1.y, shapeInfo.squares.square1.w, shapeInfo.squares.square1.h);
if (rightArrowKeyIsPressed) {
if (!touchingRightEdge) {
shapeInfo.squares.square1.x++;
}
}
if (leftArrowKeyIsPressed) {
shapeInfo.squares.square1.x--;
}
// end
}
}
Great to hear that your son is learning something as cool as JavaScript. Now as #Pointy pointed out (no pun intended) you are calling window.requestAnimationFrame(mainLoop); outside the sense function which causes the error. The mainLoop function does not exist outside sense.
The solution to this would to be define your functions globally, in this case meaning:
not inside another function.
So prevent doing:
function foo() {
// Do something
function bar() {
// Do something else
}
}
foo() // Do someting
bar() // Uncaught ReferenceError: bar is not defined.
Now bar only exists within foo. Instead do this:
function foo() {
// Do something
}
function bar() {
// Do something else
}
foo() // Do something
bar() // Do something else
Both functions can now be called from the same scope (remember this word).
Also in your mainLoop function you got to switch some things around. Try to call the draw function first before you start the mainLoop again. JavaScript works from top to bottom. So in the example below it will first draw and then start the loop again.
function mainLoop() {
draw();
window.requestAnimationFrame(mainLoop);
}
You're doing great, kid! Keep it up and come back whenever you want. We'll help you out!
Related
I'm writing a small game with canvas in javascript. For that, I use a update function. You can see it here:
var ctx = canvas.getContext("2d");
//some other code here
function animate() {
window.requestAnimationFrame(animate);
ctx.clearRect(0, 0, width, height);
for (let i = 0; i < circleArray.length; i++) {
circleArray[i].update();
}
}
animate();
And now i want to cancel the AnimationFrame, because i want to send a Text like "YOU WON" but with the update function, that sitll loops, this text will be overwritten.
function startGame() {
//some other code here
//OnWin
this.win = function() {
window.cancelAnimationFrame(animate);
//some small style properties here
ctx.fillText("YOU WON!", width/2, height/2);
}
//some other code here
}
startGame();
I think, that the Animationframe loops, because cancelAnimationFrame() will only cancel the frame. But the function still running. I had an idea that i should do it with a boolean but i think that's not a really clear.
Here is my test Bool, but it hasn't worked.:
var ctx = canvas.getContext("2d"),
win = false;
//some other code here
function animate() {
if (!win) window.requestAnimationFrame(animate);
ctx.clearRect(0, 0, width, height);
for (let i = 0; i < circleArray.length; i++) {
circleArray[i].update();
}
}
animate();
//some code here
function startGame() {
win = false
//some other code here
//OnWin
this.win = function() {
win = true
window.cancelAnimationFrame(animate);
//some small style properties here
ctx.fillText("YOU WON!", width/2, height/2);
}
//some other code here
}
startGame();
Thanks for every useful help ^^
I just started a new project in p5, I've already used it directly imported in the browser, but this time, since it's a more complex project, I'm going to use it in webpack.
I imported the library and bootstraped it in this way:
import * as p5 from 'p5';
function setup() {
createCanvas(640, 480);
}
function draw() {
if (mouseIsPressed) {
fill(0);
} else {
fill(255);
}
ellipse(mouseX, mouseY, 80, 80);
}
But it doesn't work.
The reason is simple: webpack wraps the module in a local scope, and p5 isn't aware of it.
For this reason, I assigned the functions to the global scope:
import * as p5 from 'p5';
window.setup = function () {
createCanvas(640, 480);
}
window.draw = function () {
if (mouseIsPressed) {
fill(0);
} else {
fill(255);
}
ellipse(mouseX, mouseY, 80, 80);
}
And it works fine, but still looks wrong. I don't think that pollulating the global scope is the correct way of working with JS in 2019. Expecially if I'm using webpack and I'm about to implement TypeScript.
So, how can I tell p5 to look for the functions in the module scope and not in the global one?
You'd use instance mode, which doesn't rely on globals. Here's the example from that page:
var sketch = function( p ) {
var x = 100;
var y = 100;
p.setup = function() {
p.createCanvas(700, 410);
};
p.draw = function() {
p.background(0);
p.fill(255);
p.rect(x,y,50,50);
};
};
var myp5 = new p5(sketch);
Live Example:
var sketch = function( p ) {
var x = 100;
var y = 100;
p.setup = function() {
p.createCanvas(700, 410);
};
p.draw = function() {
p.background(0);
p.fill(255);
p.rect(x,y,50,50);
};
};
var myp5 = new p5(sketch);
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.min.js"></script>
var bubbles;
function setup() {
createCanvas(600, 400);
bubbles = new Bubble();
}
function draw() {
background(50);
bubbles.displ();
bubbles.mov();
}
class Bubble {
constructor() {
this.x = 200;
this.y = 200;
};
displ() {
noFill();
stroke(255);
strokeWeight(4);
ellipse(this.x, this.y, 25, 25);
};
mov() {
this.x = this.x + random(-1, 1);
this.y = this.y + random(-1, 1);
}
}
ERROR MESSAGE
14: Uncaught SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside strict mode
Did you just try to use p5.js's str() function?
If so, you may want to move it into your sketch's setup() function. For more details, see here.
What is wrong here?
The error message says it all... You are not allowed to declare variables before setup.
And there is a solution for your problem, too. 1 I have rewritten it so it fits your code.
var s = function( sketch ) {
var bubbles;
function setup() {
sketch.createCanvas(600,400);
bubbles = new Bubble();
}
function draw() {
sketch.background(50);
sketch.bubbles.displ();
sketch.bubbles.mov();
}
};
var myp5 = new p5(s);
1 From the github repository referred to in your error-message: https://github.com/processing/p5.js/wiki/Global-and-instance-mode
cancelAnimationFrame() does not seem to work when called inside an object's method. I have tried binding the this value to the callback function (as demonstrated on MDN with setTimeout) but I received a TypeError when using cancelAnimationFrame(). I then tried setting the this value to a local variable called _this and called cancelAnimationFrame() again. That time, I did not receive an error but the animation itself is still playing. How do I cancel the animation?
I have recreated the issue I am having below. If you open a console window, you will see that the animation is still running.
function WhyWontItCancel() {
this.canvas = document.createElement("canvas");
this.canvas.width = 200;
this.canvas.height = 10;
document.body.appendChild(this.canvas);
this.draw = this.canvas.getContext("2d");
this.draw.fillStyle = "#f00";
this.position = 0;
};
WhyWontItCancel.prototype.play = function() {
if (this.position <= 190) {
this.draw.clearRect(0, 0, 400, 10);
this.draw.fillRect(this.position, 0, 10, 10);
this.position += 2;
} else {
//window.cancelAnimationFrame(this.animation.bind(this));
var _this = this;
window.cancelAnimationFrame(_this.animation);
console.log("still running");
}
this.animation = window.requestAnimationFrame(this.play.bind(this));
};
var animation = new WhyWontItCancel();
animation.play();
Seems that you miss two things here. First, this.animation = window.requestAnimationFrame(this.play.bind(this)); line is invoked always when play() is called. Contrary to what you might think, cancelAnimationFrame only removes the previously requested RAF call. Strictly speaking, it's not even necessary here. Second, you don't have to bind on each RAF call; you might do it just once:
function AnimatedCanvas() {
this.canvas = document.createElement("canvas");
this.canvas.width = 200;
this.canvas.height = 10;
document.body.appendChild(this.canvas);
this.draw = this.canvas.getContext("2d");
this.draw.fillStyle = "#f00";
this.position = 0;
this.play = this.play.bind(this); // takes `play` from prototype object
};
AnimatedCanvas.prototype.play = function() {
if (this.position <= 190) {
this.draw.clearRect(0, 0, 400, 10);
this.draw.fillRect(this.position, 0, 10, 10);
this.position += 2;
this.animationId = window.requestAnimationFrame(this.play);
}
};
You might want to add cancel into your prototype to be able to stop your animation, for example:
AnimatedCanvas.prototype.cancel = function() {
if (this.animationId) {
window.cancelAnimationFrame(this.animationId);
}
};
... but the point is, it's not useful in the use case described in the question.
Ok so I don't understand why Firefox is saying that the $("#canvas")[0].getContext('2d'); is undefined. I put it out of the function so that all of the function can access it but here the ctx is still undefined.
(function($) {
// Undefined
var ctx = $('#canvas')[0].getContext("2d");
var x = 150;
var y = 150;
var dx = 2;
var dy = 4;
$(function() {
setInterval(draw, 10);
})
function draw() {
ctx.clearRect(0,0,300,300);
ctx.beginPath();
ctx.arc(x,y,10,0,Math.PI*2,true);
ctx.closePath();
ctx.fill();
x+=dx;
y+=dy;
}
})(jQuery);
However when I transferred the location of ctx to the unnamed function, the ctx is not undefined:
(function($) {
var ctx;
var x = 150;
var y = 150;
var dx = 2;
var dy = 4;
$(function() {
//Not Undefined
ctx = $("#canvas")[0].getContext('2d');
setInterval(draw, 10);
})
function draw() {
ctx.clearRect(0,0,300,300);
ctx.beginPath();
ctx.arc(x,y,10,0,Math.PI*2,true);
ctx.closePath();
ctx.fill();
x+=dx;
y+=dy;
}
})(jQuery);
Whats wrong with the first code? I mean var ctx is declared on the top. So that would make it a global variable. Hmm the error that I got was $("#canvas")[0] is undefined. Means that it can't access the #canvas.. Why??
I think you've mistaken the problem. It is not that you have misunderstood your variable context but probably that you are running the javascript before your canvas tag has been declared.
The following code demonstrates the problem. It will display the message "canvasOne is undefined!" because we are trying to access canvasOne before it is declared (Note the placement of the <canvas> tags).
I've created a hosted demo here: http://jsbin.com/afuju (Editable via http://jsbin.com/afuju/edit)
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
</head>
<body>
<script>
/*
The following line of code is run as soon as the browser sees it.
Since the "canvasOne" tag is not declared yet, the variable will be undefined.
*/
var canvasOne = $('#canvasOne')[0];
$(function() {
// The following code is run after the whole document has finished loading.
if (canvasOne === undefined) {
alert('canvasOne is undefined!');
}
else {
canvasOne.getContext("2d").fillRect(5, 5, 15, 15);
}
});
</script>
<canvas id="canvasOne"></canvas>
<canvas id="canvasTwo"></canvas>
<script>
/*
The following line of code is run as soon as the browser sees it.
But since the "canvasTwo" tag *is* declared above, the variable will *not* be undefined.
*/
var canvasTwo = $('#canvasTwo')[0];
$(function() {
// The following code is run after the whole document has finished loading.
if (canvasTwo === undefined) {
alert('canvasTwo is undefined!');
}
else {
canvasTwo.getContext("2d").fillRect(5, 5, 15, 15);
}
});
</script>
<script>
$(function() {
/*
The following code is run after the whole document has finished loading.
Hence, the variable will *not* be undefined even though the "canvasThree" tag appears after the code.
*/
var canvasThree = $('#canvasThree')[0];
if (canvasThree === undefined) {
alert('canvasThree is undefined!');
}
else {
canvasThree.getContext("2d").fillRect(5, 5, 15, 15);
}
});
</script>
<canvas id="canvasThree"></canvas>
</body>
</html>
This is because in your first call you're actually creating a Function object which will be processed only after the DOM has loaded, in another context.
That anonymous function will be called when all the elements in the page have already loaded, so the caller will be a jQuery core function and its context is completely different from the one you're coding here.
I'd suggest wrapping everything inside the $() call, so this should work:
(function($) {
$(function() {
var ctx = $("#canvas")[0].getContext('2d');
var x = 150;
var y = 150;
var dx = 2;
var dy = 4;
setInterval(draw, 10);
function draw() {
ctx.clearRect(0,0,300,300);
ctx.beginPath();
ctx.arc(x,y,10,0,Math.PI*2,true);
ctx.closePath();
ctx.fill();
x+=dx;
y+=dy;
}
});
})(jQuery);