I would like to create a reusable JavaScript component out of the following canvas spinner. Never done this before. How to achieve it and how to use the component?
http://codepen.io/anon/pen/tkpqc
HTML:
<canvas id="spinner"></canvas>
JS:
var canvas = document.getElementById('spinner');
var context = canvas.getContext('2d');
var start = new Date();
var lines = 8,
cW = context.canvas.width,
cH = context.canvas.height;
centerX = canvas.width / 2;
centerY = canvas.height / 2;
radius = 20;
var draw = function() {
var rotation = parseInt(((new Date() - start) / 1000) * lines) % lines;
context.save();
context.clearRect(0, 0, cW, cH);
for (var i = 0; i < lines; i++) {
context.beginPath();
//context.rotate(Math.PI * 2 / lines);
var rot = 2*Math.PI/lines;
var space = 2*Math.PI/(lines * 12);
context.arc(centerX,centerY,radius,rot * (i) + space,rot * (i+1) - space);
if (i == rotation)
context.strokeStyle="#ED3000";
else
context.strokeStyle="#CDCDCD";
context.lineWidth=10;
context.stroke();
}
context.restore();
};
window.setInterval(draw, 1000 / 30);
EDIT - SOLUTION:
Here comes a solution if anybody is interested
http://codepen.io/anon/pen/tkpqc
There are any number of ways to do this. Javascript is an object oriented language so you can easily write like:
var Spinner = function(canvas_context)
{
this.context = canvas_context;
// Whatever other properties you needed to create
this.timer = false;
}
Spinner.prototype.draw = function()
{
// Draw spinner
}
Spinner.prototype.start = function()
{
this.timer = setInterval(this.start, 1000 / 30);
}
Spinner.prototype.stop = function() {
clearInterval(this.timer);
}
Now you can use this object like so:
var canvas = document.getElementById('#canvas');
var context = canvas.getContext('2d');
var spinner = new Spinner(context);
spinner.start();
Basically, you are creating a class whose sole purpose in life is to draw a spinner on a canvas. In this example, you'll note that you're passing in the canvas's context into the object, since the details of the canvas itself is not relevant to this class's interest.
Related
I'm trying to create a two-dimensional board game with canvas for practice.
The goal is to have a game with only html, javascript and php.
I made a first canvas which takes up the entire board.
Currently my second canvas, contains a round pawn which moves as well as a clipping done with the clip () method.
My problem concerns the second canvas. I would like to be able to ensure that my clipping follows the position of the pawn.
But I can't move the clipping so it stays in the same place causing my pawn to appear and then disappear.
Could you tell me how to move this clipping please?
Thank you in advance.
const goUp = 1;
const goDown = 2;
const goLeft = 3;
const goRight = 4;
var canvas = document.getElementById("pawn");
var ctx = canvas.getContext("2d");
var largeurProprietee = 60;
var hauteurProprietee = 90;
x = 45 + 90 + (8 * 60) + 90;
y = 30;
orientation = 1;
pawnHeight = 40;
pawnWidth = 40;
function drawPawn() {
var image = new Image();
image.src = 'img/' + 'zelda' + '/' + 'vert' + '.png'; //adresse de l'image
image.onload = function() {
ctx.drawImage(this, y, x, pawnHeight, pawnWidth);
}
}
function shifting() {
erasePrevious();
cutOut();
drawPawn();
x--;
}
function erasePrevious() {
var previousX;
var previousY;
if (orientation == 1) {
previousX = x;
previousY = y;
ctx.clearRect(previousY, previousX, pawnHeight, pawnWidth);
}
}
function cutOut() {
var rayon = (pawnHeight / 2);
var clipX = (x + rayon);
var clipY = (y + rayon);
ctx.beginPath();
ctx.arc(clipY, clipX, rayon, 0, Math.PI * 2);
ctx.clip();
}
setInterval(shifting, 10);
I'm trying to smoothly resize an html5 canvas element. I've cobbled together bits of code and I think the following should work:
<body>
<header id='myheader' height='200'>
<canvas id='mycanvas' width='200' height='200'></canvas>
<script>
var mycanvas = document.getElementById('mycanvas');
var context = mycanvas.getContext('2d');
var centerX = mycanvas.width / 2;
var centerY = mycanvas.height / 2;
var radius = 70;
context.beginPath();
context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
context.fillStyle = 'blue';
context.fill();
context.lineWidth = 5;
context.strokeStyle = '#003300';
context.stroke();
var animate = function(prop, val, duration) {
var start = new Date().getTime();
var end = start + duration;
var current = mycanvas[prop];
var distance = val - current;
var step = function() {
var timestamp = new Date().getTime();
var progress = Math.min((duration - (end - timestamp)) / duration, 1);
mycanvas[prop] = current + (distance * progress);
if (progress < 1) requestAnimationFrame(step);
};
return step();
};
animate('mycanvas.height', 10, 1000);
</script>
</header>
</body>
...but obviously it doesn't! The result I'm looking for is a canvas that shrinks to just show the middle part of the circle (something more interesting than a circle will be added later). Is there anything obvious that I'm missing, or am I just doing this the wrong way? Ultimately I want to to resize both the canvas and the header together, so getting the canvas resizing to work is stage 1. Any help appreciated...
(Edit: actually, I ultimately want to resize both the canvas and header in response to a scroll event - which I think means avoiding a css solution - but I want to get this bit working first!)
Here are a few changes to your script that I believe do what you want:
var mycanvas = document.getElementById('mycanvas');
var context = mycanvas.getContext('2d');
var radius = 70;
function draw() {
var centerX = mycanvas.width / 2;
var centerY = mycanvas.height / 2;
context.beginPath();
context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
context.fillStyle = 'blue';
context.fill();
context.lineWidth = 5;
context.strokeStyle = '#003300';
context.stroke();
}
var animate = function(target, prop, val, duration, action) {
var start = new Date().getTime();
var end = start + duration;
var current = target[prop];
var distance = val - current;
var step = function() {
var timestamp = new Date().getTime();
var progress = Math.min((duration - (end - timestamp)) / duration, 1);
target[prop] = current + (distance * progress);
action();
if (progress < 1) requestAnimationFrame(step);
};
return step();
};
animate(mycanvas, 'height', 10, 1000, draw);
I am having some trouble with this because Javascript just seems terrible for classes and the implementation is interesting. I am trying to get this block working so I can create multiple triangles:
var canvas = document.querySelector('canvas');
var context = canvas.getContext('2d');
var phase = 0;
var tau = 2 * Math.PI;
function animate() {
requestAnimationFrame(animate);
var sides = 3;
var size = 100;
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
phase += 0.005 * tau;
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
for (var i = 0; i <= sides; i++) {
context[i ? 'lineTo' : 'moveTo'](
centerX + size * Math.cos(phase + i / sides * tau),
centerY + size * Math.sin(phase + i / sides * tau)
);
}
context.stroke();
}
animate();
And here I tried making it into a class:
var canvas = document.querySelector('canvas');
var context = canvas.getContext('2d');
var phase = 0;
var tau = 2 * Math.PI;
function Triangle(cntx, canvs) {
this.ctx = cntx;
this.canv = canvs;
this.draw = drawTriangle;
}
function drawTriangle() {
requestAnimationFrame(drawTriangle);
var sides = 3;
var size = 100;
var centerX = this.canv.width / 2;
var centerY = this.canv.height / 2;
phase += 0.005 * tau;
this.ctx.clearRect(0, 0, this.canv.width, this.canv.height);
this.ctx.beginPath();
for (var i = 0; i <= sides; i++) {
this.ctx[i ? 'lineTo' : 'moveTo'](
centerX + size * Math.cos(phase + i / sides * tau),
centerY + size * Math.sin(phase + i / sides * tau)
);
}
this.ctx.stroke();
}
var triangle1 = new Triangle(context,canvas);
triangle1.draw();
The problem is that it just draws the triangle once so I am not really sure what I am doing wrong here.
The problem here is that you're calling the requestAnimationFrame and passing a callback to the same function, but the this keyword will refer to the window object, and not your class anymore.
So, you must explicit that you want to set the context of the callback function as the same context you are, and you can achieve that by calling .bind(this). Take a look at the example below:
var canvas = document.querySelector('canvas');
var context = canvas.getContext('2d');
var phase = 0;
var tau = 2 * Math.PI;
function Triangle(cntx, canvs) {
this.ctx = cntx;
this.canv = canvs;
this.draw = drawTriangle;
}
function drawTriangle() {
requestAnimationFrame(drawTriangle.bind(this));
var sides = 3;
var size = 100;
var centerX = this.canv.width / 2;
var centerY = this.canv.height / 2;
phase += 0.005 * tau;
this.ctx.clearRect(0, 0, this.canv.width, this.canv.height);
this.ctx.beginPath();
for (var i = 0; i <= sides; i++) {
this.ctx[i ? 'lineTo' : 'moveTo'](
centerX + size * Math.cos(phase + i / sides * tau),
centerY + size * Math.sin(phase + i / sides * tau)
);
}
this.ctx.stroke();
}
var triangle1 = new Triangle(context, canvas);
triangle1.draw();
<canvas></canvas>
You have two ways you can do it as a javascript object (don't think of them as classes).
First way using prototype to define object methods.
function Triangle(cntx, canvs) { // define the triange
this.ctx = cntx;
this.canv = canvs;
this.draw = this.drawTriangle.bind(this);
}
// this creates and compiles the draw function ready to be used for any Triangle object you create.
Triangle.prototype.drawTriangle = function() { // define the draw method as part of
// triangle's prototype
requestAnimationFrame(this.draw); // all properties of Triangle.prototype can be referenced via 'this'
var sides = 3;
var size = 100;
var centerX = this.canv.width / 2;
var centerY = this.canv.height / 2;
phase += 0.005 * tau;
this.ctx.clearRect(0, 0, this.canv.width, this.canv.height);
this.ctx.beginPath();
for (var i = 0; i <= sides; i++) {
this.ctx[i ? 'lineTo' : 'moveTo'](
centerX + size * Math.cos(phase + i / sides * tau),
centerY + size * Math.sin(phase + i / sides * tau));
}
this.ctx.stroke();
}
var triangle1 = new Triangle(context, canvas);
triangle1.draw();
Or you can create a safer Triangle with the following. In this case we minimise the use of the this token by using closure to encapsulate the variables we want ctx and canv are no longer exposed and only accessible from within the triangle object calls. Using closure is a little faster when running, abut slower at creation.
function Triangle(cntx, canvs) {
var ctx = cntx; // create closure vars
var canv = canvs;
this.draw = (function() { // draw run faster because it does not have
// to search the prototype for the values
// of ctx and canv.
requestAnimationFrame(this.draw);
var sides = 3;
var size = 100;
var centerX = this.canv.width / 2;
var centerY = this.canv.height / 2;
phase += 0.005 * tau;
ctx.clearRect(0, 0, canv.width, canv.height); // dont need the this keyword
ctx.beginPath();
for (var i = 0; i <= sides; i++) {
ctx[i ? 'lineTo' : 'moveTo'](
centerX + size * Math.cos(phase + i / sides * tau),
centerY + size * Math.sin(phase + i / sides * tau));
}
ctx.stroke();
}).bind(this); // because requestAnimationFrame sets this wee need to bind the current this to the method.
}
var triangle1 = new Triangle(context, canvas); // It takes a little longer to invoke
// because it will need to create and compile
// the draw function.
triangle1.draw(); // but call this now runs a little faster.
The differences in speed in this case is very minor, the advantage of one over the other will depend on how often you create the new object and how often you call its methods. Use prototype if you create often, use closure if you create once and use often.
I am creating a game (using HTML5 canvas) that involves catching falling apples, i know, how original! I am having trouble finding a way to make it so multiple apples fall?
Here is the link to the JSFiddle: https://jsfiddle.net/pgkL09j7/14/
var apple_x = 100;
var apple_y = 0;
var basket_x = 100;
var basket_y = 100;
var points = 0;
var basket_img = new Image();
basket_img.src = "http://s18.postimg.org/h0oe1vj91/basket.png";
var Countable = function() {}
//Background colour of canvas
var c = document.getElementById("c");
var ctx = c.getContext("2d");
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, 500, 500);
//Here is the event listener
c.addEventListener("mousemove", seenmotion, false);
//////////////////////
function seenmotion(e) {
//This is the code for the mouse
//moving over the canvas.
var bounding_box = c.getBoundingClientRect();
basket_x = (e.clientX - bounding_box.left) * (c.width / bounding_box.width) - basket_img.width / 2;
basket_y = (e.clientY - bounding_box.top) * (c.height / bounding_box.height) - basket_img.height / 2;
}
function start_game() {
setInterval(game_loop, 50);
}
function game_loop() {
// The code above is called every 50ms and is a
// frame-redraw-game-animation loop.
c.width = c.width;
// Below is the code that draws the objects
draw_apple(apple_x, apple_y);
draw_basket(basket_x, basket_y);
// Below is the code that updates the balloons location
apple_y++;
if (apple_x > c.width) {
apple_x = 0;
}
//Here is the collision detection code
if (collision(apple_x, apple_y, basket_x, basket_y)) {
points -= 0.5;
}
//Here is the code for the point system
points += 1;
// and let's stick it in the top right.
var integerpoints = Math.floor(points); // make it into an integer
ctx.font = "bold 24px sans-serif";
ctx.fillText(integerpoints, c.width - 50, 50);
}
context.clearRect(0, 0, 500, 500);
function collision(basket_x, basket_y, apple_x, apple_y) {
if (apple_y + 85 < basket_y) {
return false;
}
if (apple_y > basket_y + 91) {
return false;
}
if (apple_x + 80 < basket_x) {
return false;
}
if (apple_x > basket_x + 80) {
return false;
}
return true;
}
// Code to stop the game when we're finished playing
function stop_game() {
}
//Code for the ball
function draw_apple(x, y) {
var apple_img = new Image();
apple_img.src = "http://s15.postimg.org/3nwjmzsiv/apple.png";
ctx.drawImage(apple_img, x, y);
}
//Code for the basket
function draw_basket(x, y) {
ctx.drawImage(basket_img, x, y);
}
I posted this on your previous question, so I'll repeat it here. You will need to maintain an array of apples, but you will also want to check out requestAnimationFrame in order to improve performance. Things are going to get janky for you, and you probably already noticed it when you move the bucket around. I've modified your fiddle to demonstrate exactly how you might modify your program to support multiple apples falling at different rates of speed. (Set apples_per_level to 2 or more to immediately see multiple apples -- or just play the game, and watch as they accumulate!).
https://jsfiddle.net/h82gv4xn/
Improvements include:
Fixed scoreboard
Added level progression (Level increases every 10 apples)
Allowance for many many more apples on screen (play to level 9).
Apples will fall at different speeds and speed up as the levels increase.
Uses the animation frame system for much smoother animations.
Relaxed collision handling (The center of the bucket must touch the apple)
It all gets really silly as the levels wind upwards, but it should be a nice example to improve upon. The relevant javascript follows (this would go into your onLoad function):
var game = create_game();
game.init();
function create_game() {
debugger;
var level = 1;
var apples_per_level = 1;
var min_speed_per_level = 1;
var max_speed_per_level = 2;
var last_apple_time = 0;
var next_apple_time = 0;
var width = 500;
var height = 500;
var delay = 1000;
var item_width = 50;
var item_height = 50;
var total_apples = 0;
var apple_img = new Image();
var apple_w = 50;
var apple_h = 50;
var basket_img = new Image();
var c, ctx;
var apples = [];
var basket = {
x: 100,
y: 100,
score: 0
};
function init() {
apple_img.src = "http://s15.postimg.org/3nwjmzsiv/apple.png";
basket_img.src = "http://s18.postimg.org/h0oe1vj91/basket.png";
level = 1;
total_apples = 0;
apples = [];
c = document.getElementById("c");
ctx = c.getContext("2d");
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, 500, 500);
c.addEventListener("mousemove", function (e) {
//moving over the canvas.
var bounding_box = c.getBoundingClientRect();
basket.x = (e.clientX - bounding_box.left) * (c.width / bounding_box.width) - basket_img.width / 2;
basket.y = (e.clientY - bounding_box.top) * (c.height / bounding_box.height) - basket_img.height / 2;
}, false);
setupApples();
requestAnimationFrame(tick);
}
function setupApples() {
var max_apples = level * apples_per_level;
while (apples.length < max_apples) {
initApple(apples.length);
}
}
function initApple(index) {
var max_speed = max_speed_per_level * level;
var min_speed = min_speed_per_level * level;
apples[index] = {
x: Math.round(Math.random() * (width - 2 * apple_w)) + apple_w,
y: -apple_h,
v: Math.round(Math.random() * (max_speed - min_speed)) + min_speed,
delay: Date.now() + Math.random() * delay
}
total_apples++;
}
function collision(apple) {
if (apple.y + apple_img.height < basket.y + 50) {
return false;
}
if (apple.y > basket.y + 50) {
return false;
}
if (apple.x + apple_img.width < basket.x + 50) {
return false;
}
if (apple.x > basket.x + 50) {
return false;
}
return true;
}
function maybeIncreaseDifficulty() {
level = Math.max(1, Math.ceil(basket.score / 10));
setupApples();
}
function tick() {
var i;
var apple;
var dateNow = Date.now();
c.width = c.width;
for (i = 0; i < apples.length; i++) {
apple = apples[i];
if (dateNow > apple.delay) {
apple.y += apple.v;
if (collision(apple)) {
initApple(i);
basket.score++;
} else if (apple.y > height) {
initApple(i);
} else {
ctx.drawImage(apple_img, apple.x, apple.y);
}
}
}
ctx.font = "bold 24px sans-serif";
ctx.fillStyle = "#2FFF2F";
ctx.fillText(basket.score, c.width - 50, 50);
ctx.fillText("Level: " + level, 20, 50);
ctx.drawImage(basket_img, basket.x, basket.y);
maybeIncreaseDifficulty();
requestAnimationFrame(tick);
}
return {
init: init
};
}
Your next logical step would be to create an apple Object with appropriate properties. Following that, you can store them in an Array and animate multiple apples.
As Gerard said, you should create an Array of apple Objects. You can take a look at this example with balls instead of apples.
One you are done with that, I would recommend that you draw all the apples that fall at the same speed in different offscreen canvases and that you animate those instead of going apple by apple if you are planning to add many of them. If they are all going to fall at the same speed, use just one offscreen canvas and move all at once.
Also take a look at http://www.html5rocks.com/en/tutorials/canvas/performance/ if you have trouble keeping your FPS at an appropriate rate.
I've hit a wall trying to figure this out. I'm new to OO Javascript and trying to put together my first class/object. I'm trying to create a canvas loader and it's not working. I've narrowed down the error to the requestAnimationWindow portion inside my animate function in my clock class. I get a Object [object global] has no method 'animate' error. Here is my code.
HTML:
<div id="loader"><canvas id="showLoader" width="250" height="250"></canvas><div id="showTimer"><p id="elapsedTime">
<script>
var clockTest = new clock(document.getElementById("showLoader"), 0, 100);
clockTest.animate();
</script>
</p></div></div>
Javascript:
function clock(canvas, curPerc, endPrecent){
var showPerc = document.getElementById("elapsedTime");
this.canvas = document.getElementById("showLoader");
var context = this.canvas.getContext('2d');
var x = this.canvas.width / 2;
var y = this.canvas.height / 2;
var radius = 75;
this.curPerc = 0;
this.endPercent = 110;
var counterClockwise = false;
var circ = Math.PI * 2;
var quart = Math.PI / 2;
context.lineWidth = 10;
context.strokeStyle = '#ed3f36';
this.animate = function(current) {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.context.beginPath();
this.context.arc(x, y, radius, -(quart), ((circ) * current) - quart, false);
this.context.stroke();
this.curPerc++;
if(this.curPerc < this.endPercent) {
requestAnimationFrame(function () {
this.animate(curPerc / 100);
showPerc.innerHTML = this.curPerc + '%';
});
}
};
}
Any tips is appreciated. Thanks!
It is to do with the context of this in the anonymous function you pass to requestAnimationFrame, its not the this you think. Use a closure
i.e.
this.animate = function(current) {
var self = this; //<-- Create a reference to the this you want
self.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
/.. etc, etc..
if(self.curPerc < self.endPercent) {
requestAnimationFrame(function () {
self.animate(self.curPerc / 100); //<-- and use it here
showPerc.innerHTML = self.curPerc + '%'; //<-- and here
});
}
};
On a couple of other points, I would try to structure the object a bit better, you don't seem to be keeping reference to the properties correctly. The parameters you passed in , are not store on the object, and you are not storing the context correctly. Something like:
function clock(canvas, curPerc, endPrecent) {
var self = this;
// Set object properties here, i.e. from the parameters passed in
// Note also, anything that is a property (i.e. this. ) is public, can be accessed from otuside this object,
// whereas variable declared with var , are privte, can only be access within this object
self.canvas = canvas;
self.curPerc = curPerc;
self.endPercent = endPrecent;
self.context = self.canvas.getContext('2d'); //needs to be store like this, if you want to access below as this.context
self.context.lineWidth = 10;
self.context.strokeStyle = '#ed3f36';
//Private variables
var showPerc = document.getElementById("elapsedTime");
var x = self.canvas.width / 2;
var y = self.canvas.height / 2;
var radius = 75;
var counterClockwise = false;
var circ = Math.PI * 2;
var quart = Math.PI / 2;
//Methods
self.animate = function (current) {
self.context.clearRect(0, 0, self.canvas.width, self.canvas.height);
self.context.beginPath();
self.context.arc(x, y, radius, -(quart), ((circ) * current) - quart, false);
self.context.stroke();
self.curPerc++;
if (self.curPerc < self.endPercent) {
requestAnimationFrame(function () {
self.animate(curPerc / 100);
showPerc.innerHTML = self.curPerc + '%';
});
}
};
}
is starting to head in a better direction.
I ran into the same problem using three.js while calling requestAnimationFrame from inside an ES6 class method and the way I ended up solving it was by:
animate() {
requestAnimationFrame(() => this.animate());
this.render();
}