I have a class like:
function run(){
this.interval;
this.start = function(){
this.interval = setInterval('this.draw()',1000);
};
this.draw = function(){
//some code
};} var run = new run(); run.start();
however I can't seem to reference/call this.draw() within the setInterval, it says this.draw() is not a function, and if I remove the quotes it says useless setInterval call, what am I doing wrong?
The bind() method!
See the following example in ES6:
<!DOCTYPE html>
<html>
<body>
<canvas id="canvas" width="200" height="200" style="border: 1px solid black"></canvas>
<script>
class Circles {
constructor(canvas, r = 5, color = 'red') {
this.ctx = canvas.getContext('2d')
this.width = canvas.width
this.height = canvas.height
this.r = r
this.color = color
setInterval(
this.draw.bind(this),
1000
)
}
draw() {
this.ctx.fillStyle = this.color
this.ctx.beginPath()
this.ctx.arc(
Math.random() * this.width,
Math.random() * this.height,
this.r,
0,
2 * Math.PI
)
this.ctx.fill()
}
}
</script>
<script>
var canvas = document.querySelector('#canvas')
var circles = new Circles(canvas)
</script>
</body>
</html>
The value of this is set depending on how a function is called. When you call a function as a constructor using new then this will refer to the object being created. Similarly when you call a function with dot notation like run.start() then this will refer to run. But by the time the code run by the setInterval is called this doesn't mean what you think. What you can do is save a reference to this and then use that reference, like the following:
function Run(){
var self = this;
self.start = function(){
self.interval = setInterval(function() { self.draw(); },1000);
};
self.draw = function(){
//some code
};
}
var run = new Run();
run.start();
Note also that you've created a function called run and a variable called run - you need to give them different names. In my code (bearing in mind that JS is case sensitive) I've changed the function name to start with a capital "R" - which is the convention for functions intended to be run as constructors.
EDIT: OK, looking at the other answers I can see that just maybe I overcomplicated it and as long as draw() doesn't need to access this it would've been fine to just say:
this.interval = setInterval(this.draw, 1000);
But my point about not giving your constructor and later variable the same name still stands, and I'll leave all the self stuff in because it is a technique that you will need if draw() does need to access this. You would also need to do it that way if you were to add parameters to the draw() function.
function run(){
this.interval;
this.start = function(){
this.interval = setInterval(this.draw,1000);
};
this.draw = function(){
//some code
}
;}
var run = new run();
run.start();
Related
I'm building a game similar to the Chrome dinosaur in Vanilla JS. To animate the obstacles I have created a class Obstacle, which stores their position and size, and defines a method that changes the position.
var Obstacle = function (type, w, h, sprite) {
this.h = h; // Obstacle height
this.w = w; // Obstacle width
this.x = 600; // Starting horizontal position
this.y = GROUND - this.h; // Starting vertical position
this.type = type;
this.sprite = sprite;
this.speed = -4;
this.move = function () {
this.x += this.speed;
}
}
These are stored inside an array, defined as a property of a different class:
var ObstacleBuffer = function () {
this.bufferFront = [];
this.createObstacle = function () {
this.bufferFront.push(this.createBox());
}
// Obstacle creators
this.createBox = function () {
if (Math.random() < 0.5) return new Obstacle ("box1", OBSTACLES.box1.w, OBSTACLES.box1.h, OBSTACLES.box1.sprite);
return new Obstacle ("box2", OBSTACLES.box2.w, OBSTACLES.box2.h, OBSTACLES.box2.sprite);
}
//Obstacle animation
this.animateObstacle = function () {
this.bufferFront[0].move();
}
}
When running this an error pops up:
TypeError: Cannot read property 'move' of undefined.
I have logger the content of this.bufferFront[0] and it correctly show the Obstacle stored inside it.
I have also tried assigning this.bufferFront[0] locally to a variable and then tried to call the method from there. The data stored is correct but the error pops up whenever trying to access Obstacles methods or properties.
Any ideas ? Thank you very much.
EDIT - I have reviewed the code as per your suggestions and the problem seems to be at the point where I'm calling the function. Before generating the obstacles I am preloading a series of images and only generating obstacles once these have load:
this.loadWhenReady = function () {
if (self.resources.isLoadComplete()) {
self.sound.load(self.resources.list.sfx);
drawGround();
this.obstacles.createObstacle(); // <--
self.startGame();
return;
} else {
setTimeout(self.loadWhenReady, 300);
}
}
And this is called in:
setTimeout(self.loadWhenReady, 300);
Of course const self = this has been defined before.
Everything seems to move forward when the method is called outside the SetTimeout.
Why is this happening though ? And is there a way of solving this while calling the method in there ?
SOLVED - As #Bergi and #Jaime-Blandon mention it was a context problem. Calling the method from outside the setTimeout loop or using self.obstacle.createObstacle() instead of this.obstacle.createObstacle() did the trick and solved the issue.
Try this change on the ObstacleBuffer Class:
var ObstacleBuffer = function () {
this.bufferFront = [];
this.createObstacle = function () {
//this.bufferFront.push(this.createBox());
this.createBox();
}
// Obstacle creators
this.createBox = function () {
if (Math.random() < 0.5) this.bufferFront.push(new Obstacle ("box1", OBSTACLES.box1.w, OBSTACLES.box1.h, OBSTACLES.box1.sprite));
this.bufferFront.push(new Obstacle ("box2", OBSTACLES.box2.w, OBSTACLES.box2.h, OBSTACLES.box2.sprite));
}
//Obstacle animation
this.animateObstacle = function () {
this.bufferFront[0].move();
}
}
Best Regards!
I wrote this code but it don't work. Javascript draws the line at once, instead of showing me how the line is drawn
What am I doing wrong? Thanks
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var i = 200;
while (i<220) {
ctx.beginPath();
ctx.lineWidth = 10;
ctx.lineCap = "butt";
ctx.moveTo(20, 20);
ctx.lineTo(i, 20);
ctx.stroke();
console.log("Hello");
sleep(200);
console.log("World!");
i++
}
function sleep(milliseconds) {
const date = Date.now();
let currentDate = null;
do {
currentDate = Date.now();
} while (currentDate - date < milliseconds);
}
<p>The three different line caps:</p>
<canvas id="myCanvas">Example</canvas>
Just use setInterval to see the animation like this :
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
let i = 0;
const startInterval = function(){
i += 5
ctx.beginPath();
ctx.lineWidth = 10;
ctx.lineCap = "butt";
ctx.moveTo(20, 20);
ctx.lineTo(i, 20);
ctx.stroke();
if(i === 220){
clearInterval(interval);
console.log("finished")
}
}
let interval = setInterval(startInterval, 200)
<p>The three different line caps:</p>
<canvas id="myCanvas">
Example</canvas>
I can't explain exactly why that behaviour is occurring, but I do have a solution. Typically if you want to perform the same task in repeated intervals, you use setInterval. There's also setTimeout which you could use, but the first is more appropriate.
var number = 0;
// you pass the function to call, and how often as arguments
let count = setInterval(countUpTo5, 500);
function countUpTo5() {
if (number > 5) {
// by passing the name of the interval, you can stop it
clearInterval(count);
} else {
console.log(number);
number++;
}
}
Good luck, and if you'd like more help just leave a comment.
That's because canvas does not update itself while code is running. You can easily see the effect by changing canvas border color for example. The only way to solve this is to imulate async operations with setTimeout or setInterval, as offered above. By this you make the stack empty for a while and canvas repaints as the result within each iteration.
I am new to JavaScript and I am having a hard time understanding how to get the canvas to cooperate with stuff I want to do.
I am trying to have my HTML button outside the canvas create a rectangle on the canvas. Then I want the rectangle to fall. I have the canvas animated, I have the button set to create a rectangle at user inputted coordinates, but...the canvas won't animate it.
It won't fall like the statically created rectangles. I also am struggling with how to get my button to create a new rectangle each time instead of redrawing the same one? Hoping someone can explain more than just give me the code.
Thanks in advance.
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
window.onload = addListeners;
function addListeners() {
document.getElementById('b1').addEventListener('click',btn1,false);
function btn1() {
var inputx = document.getElementById("work").value;
var inputy = document.getElementById("y").value;
var inputs = document.getElementById("s").value;
new Rectangle(inputx,inputy,inputs);
// rec.x = inputx;
//rec.y = inputy;
//rec.s = inputs;
}
}
var r2 = new Rectangle(500, 50, 50);
var rec = new Rectangle();
//animate
function animate() {
requestAnimationFrame(animate);
ctx.clearRect(0,0,1450,250);
r2.draw();
r2.update();
rec.draw();
rec.update();
}
code for rectangle:
function Rectangle(x,y,s){
this.x = x;
this.y = y;
this.s = s;
this.draw = function(){
//console.log('fuck');
ctx.fillStyle = "rgba(0,200,0,1)";
ctx.fillRect(this.x,this.y,this.s,this.s);
}
this.update = function(){
console.log(this.y);
if((this.y + this.s) <= 250){
this.y= this.y+2;
}
}
}
The button is not animating anything as the animate method was not being called. Also in your code when you called animate it ended in an infinite loop as requestAnimationFrame will continue to call animate repeatedly, so I've added a condition around requestAnimationFrame(animate) to check the squares height and stop running it when it reaches the bottom, similar to what you had in your update function.
To create a new square every time you click the button, I moved the creation of the Rectangles inside the btn1 function. If you want to keep the old squares while creating new ones, you will need to keep track of them outside the canvas and redraw them with everything else each animation.
I guessed what your html looks like, but you can run the code below it will drop two squares, but note that the stop condition is only on the hard coded one.
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
document.getElementById("b1").addEventListener("click", btn1, false);
var r2;
var rec;
function btn1() {
var inputx = document.getElementById("work").value;
var inputy = document.getElementById("y").value;
var inputs = document.getElementById("s").value;
r2 = new Rectangle(500, 50, 50);
rec = new Rectangle(parseInt(inputx, 10), parseInt(inputy, 10), parseInt(inputs, 10));
animate();
}
//animate
function animate() {
if (r2.y + r2.s <= 400) {
requestAnimationFrame(animate);
}
ctx.clearRect(0,0,1450,400);
r2.draw();
r2.update();
rec.draw();
rec.update();
}
function Rectangle(x,y,s){
this.x = x;
this.y = y;
this.s = s;
this.draw = function(){
ctx.fillStyle = "rgba(0,200,0,1)";
ctx.fillRect(this.x,this.y,this.s,this.s);
}
this.update = function(){
this.y= this.y+2;
}
}
<div>
<input id='work'>
<input id='y'>
<input id='s'>
<button id="b1"> Create Sqaure</button>
</div>
<canvas id="myCanvas" width=600 height=400></canvas>
I have a "sketch.js" where i want to instance multiple canvases und display different objects of one class in them.
sketch.js:
var myObjects = [];
var sketch1 = function (p) {
p.setup = function () {
p.createCanvas(600, 400);
}
p.draw = function () {
p.background(51);
p.rect(p.width/2, p.height/2, 200, 200);
}
};
new p5(sketch1, "canvas_container");
var sketch2 = function (p) {
p.setup = function () {
p.createCanvas(600, 400);
myObjects.push(new myObject(p, 1, 2));
myObjects.push(new myObject(p, 3, 4));
}
p.draw = function () {
p.background();
for (var i=0; i<myObjects.length; i++) {
p.line(0, 0, myObjects[i].x, myObjects[i].y);
myObjects[i].doSomething(Math.random()*10);
}
}
};
new p5(sketch2, "canvas_container");
When do i use "this." and when "p." in this case?
Furthermore I would like to use some "other" methods from the p5 library in my sketch.js file, outside of the instaces, like:
select('..') ...
but I get the error:
select is not defined
I found myself a dirt workaround:
new p5().select('...') ...
What is the clean way to do this?
myObjects.js
function myObject(canvas, x, y) {
this.canvas = canvas;
this.x = x;
this.y = y;
this.doSomething = function(rad) {
this.canvas.ellipse(this.x, this.y, rad, rad);
}
}
Has anybody an example for handeling multiple instances of canvases?
Note that right now, you aren't ever creating a new instance of myObject. Your array is named myObjects, but you're only ever adding p (which is an instance of p5) to it. So you can't magically call your doSomething() function on the objects you've added to your myObjects array, because they aren't myObject objects. They're p5 objects.
I think what you would want to do is take the p variable passed into each of your sketches and pass that into your "class" functions. Something like this:
p.setup = function () {
p.createCanvas(600, 400);
var myObjectOne = new myObject(p, 1, 2);
var myObjectTwo = new myObject(p, 3, 4);
}
Then it should work how you're expecting it to.
Hi I'm trying to count my points in a game. I just started with javascript and working with CreateJS. My problem is that I don't know how I can use a Ticker and Click Event at the same time. It doesn't work...
function init(){
var stage = new createjs.Stage("myCanvas");
stage.mouseEventsEnabled = true;
createjs.Ticker.interval = 1500;
createjs.Ticker.addEventListener("tick", handleTick);
var statPoint = new createjs.Text("Punkte:", "bold 20px Arial", "#000000");
statPoint.x = 750;
var currentPoints = new createjs.Text("0", "20px Arial", "#000000");
currentPoints.x= 850;
var victim = new createjs.Bitmap("Opfer.png");
victim.scaleX = 0.4;
victim.scaleY = 0.4;
stage.addChild(statPoint);
stage.addChild(currentPoints);
stage.addChild(victim);
victim.addEventListener("click", handleClick);
function handleTick(event){
victim.x = 850*Math.random();
victim.y = 550*Math.random();
stage.update();
}
function handleClick(event){
currentPoints.text = parseInt(currentPoints.text + 1);
}
}
You are probably dealing with scope issues. You have defined your victim and currentPoints variables using var inside the init method, so it is only available there. This means your handleTick and handleClick methods can not access those variables. You likely have undefined errors in your console.
Change the variables to be declared outside of the init method, and they will be accessible in the handler methods.
var currentPoints, victim;
function init() {
// Other code
currentPoints = values;
victim = value;
}