Stuck with canvas/JS animation - javascript

I'm working on a game to play in canvas, and was wanting to add some ambiance to a background layer using javascript. To start, here is the code...
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext("2d");
var x = canvas.width/2;
var y = canvas.height-150;
var dx = Math.random() * (-5 * 5) + 15;
var dy = -15;
function drawDot() {
ctx.beginPath();
ctx.arc(x, y, 10, 0, Math.PI*2);
ctx.fillStyle = "black";
ctx.fill();
ctx.closePath();
};
function move() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawDot();
x += dx;
y += dy;
};
setInterval(move, 50);
If you run that, you can see that what I have is a black ball that moves up and off the page inside a random conal space. What I need help with is figuring out the best way to:
A. Populate it with more balls (maybe like 2-3) that are on their own random trajectory, and
B. Make it so those 2-3 balls are constantly animating inside the random cone area off the page from the same starting area (kind of like a constant spurting fountain effect).
A problem I can already see is that by using the console log, the 1 working ball I have now just keeps going off into infinity outside the canvas, so when I try to add a new one it won't run the function. I'm very new to javascript and especially canvas so apologies if this is obvious!
Thank you for any help with this!

There is a good tutorial by Seb Lee-Delisle on this exact problem here:
https://vimeo.com/36278748
Basically you have to encapsulate each Dot so it knows about its own position and acceleration.
EDIT 1
Here is an example using you own code:
document.body.innerHTML = '<canvas height="600" width="600" id="myCanvas"></canvas>';
clearInterval(interval);
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
var dotArray = [];
function Dot() {
var dot = {
y : canvas.height / 2,
x : canvas.width / 2,
speedX : Math.random() * ( - 5 * 5) + 15,
speedY : Math.random() * ( - 5 * 5) + 15,
age : 0,
draw : function () {
this.x += this.speedX;
this.y += this.speedY;
ctx.beginPath();
ctx.arc(this.x, this.y, 10, 0, Math.PI * 2);
ctx.fillStyle = 'black';
ctx.fill();
ctx.closePath();
}
};
return dot;
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < dotArray.length; i++) {
dotArray[i].draw();
dotArray[i].age++;
if (dotArray[i].age > 20) {
dotArray.splice(i, 1);
}
}
dotArray.push(new Dot());
}
draw();
var interval = setInterval(draw, 1000 / 60);

Related

Is there a better way of calling these balls with different color, x and speed each time?

I started learning JavaScript a few weeks ago and I decided to try and make my first game from scratch, in the code below I am trying to make the game so the balls fall each time with different position, color and speed and after that there will be another ball that ill be able to move with my mouse and will try to dodge the balls, so my question is is there a better way than the one I did to spawn more balls , because if I want to spawn like 5-6 more the code will look so bad and I am sure there is a better way of doing that, I am still learning so if you can hit me with a simple solution and explain it.
var canvas = document.getElementById("Canv");
var ctx = canvas.getContext("2d");
var x = random(1, 801);
var x2 = random(1, 801);
var y = 10;
var y2 = 10;
var ballRadius = random(2, 51);
var ballRadius2 = random(2, 51);
var color = "#" + ((1 << 24) * Math.random() | 0).toString(16);
var color2 = "#" + ((1 << 24) * Math.random() | 0).toString(16);
var dy = random(1, 6);
var dy2 = random(1, 6);
function random(min, max) {
return Math.random() * (max - min) + min;
}
function drawBall() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
ctx.closePath();
}
function drawBall2() {
ctx.beginPath();
ctx.arc(x2, y2, ballRadius2, 0, Math.PI * 2);
ctx.fillStyle = color2;
ctx.fill();
ctx.closePath();
}
function draw() {
drawBall();
drawBall2();
y += dy;
y2 += dy2;
}
setInterval(draw, 10);
I want to know methods to simplify my code so I know for future projects.
Consider the following code:
The main changes I made are the addition of the function createBall, allowing the creation of multiple new balls per tick, and the removal of balls that are out of view. I tried to add comments to the new parts, if you have any questions, let me know!
var canvas = document.getElementById("Canv");
var ctx = canvas.getContext("2d");
const desiredNumberOfBalls = 10;
let i = 0;
let balls = []; // Track each individual ball
const viewLimit = canvas.height; // The vertical distance a ball has when leaving sight
// For now we create a static paddle
const paddle = {
x: 200,
y: viewLimit - 10,
width: 50,
height: 10,
}
// This is a very rough calculation based on https://math.stackexchange.com/a/2100319 by checking first the corners and then the top edge
paddle.hitsBall = function(ball) {
let hit = false;
if (ball.y + ball.radius >= paddle.y) {
// the ball is at the same level as the paddle
if (Math.sqrt((ball.x - paddle.x) ** 2 + (ball.y - paddle.y) ** 2) < ball.radius) {
// the upper left part of the paddle touches the ball
hit = true;
} else if (Math.sqrt((ball.x - paddle.x - paddle.width) ** 2 + (ball.y - paddle.y) ** 2) < ball.radius) {
// the upper right part of the paddle touches the ball
hit = true;
} else if (ball.x >= paddle.x && ball.x <= paddle.x + paddle.width) {
// the top edge of the paddle touches the ball
hit = true;
}
}
if (hit) console.log("Hit!");
return hit;
}
function createBall() {
let ball = {
id: i++,
x: random(1, canvas.width),
y: 10,
radius: random(2, 51),
color: "#" + ((1 << 24) * Math.random() | 0).toString(16),
speed: random(1, 6)
};
return ball;
}
function random(min, max) {
return Math.random() * (max - min) + min;
}
function drawBall(ball) {
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.fillStyle = ball.color;
ctx.fill();
ctx.closePath();
}
function drawPaddle() {
ctx.beginPath();
ctx.rect(paddle.x, paddle.y, paddle.width, paddle.height);
ctx.fillStyle = 'darkred';
ctx.fill();
ctx.closePath();
}
function clearView() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
function draw() {
clearView();
// Move all existing balls by their designated speed
balls.forEach((ball) => {
ball.y += ball.speed;
});
// Implicitly delete all balls that have exited the viewport
// by only retaining the balls that are still inside the viewport
balls = balls.filter((ball) => ball.y <= viewLimit);
// Implicitly delete all balls that touch the paddle
// by only retaining the balls that don't
balls = balls.filter((ball) => !paddle.hitsBall(ball));
// Create newBallsPerTick new balls
while (balls.length < desiredNumberOfBalls) {
balls.push(createBall());
}
// Draw the paddle
drawPaddle();
// Draw the position of every ball - both the pre-existing ones
// and the ones we just generated
balls.forEach((ball) => {
drawBall(ball);
});
}
setInterval(draw, 1000 / 60);
canvas {
width: 600px;
height: 400px;
}
<canvas id="Canv" height="400" width="600"></canvas>

How can I make a ball jump wherever it is?

I'm trying to make a ball jump even in midair, but my code always just teleports to the same spot and then jumps, can anydody tell me how to fix this problem of mine? It needs to be able to jump wherever it is at that exact moment, and i've already tried something with set interval.
I'm trying to make a ball jump even in midair, but my code always just teleports to the same spot and then jumps, can anydody tell me how to fix this problem of mine? It needs to be able to jump wherever it is at that exact moment, and i've already tried something with set interval.
I'm trying to make a ball jump even in midair, but my code always just teleports to the same spot and then jumps, can anydody tell me how to fix this problem of mine? It needs to be able to jump wherever it is at that exact moment, and i've already tried something with set interval.
var canvas, ctx, container;
canvas = document.createElement('canvas');
ctx = canvas.getContext("2d");
var ball;
var touchGround = false;
var pull= 0.43;
var vy;
var gravity = pull;
var i = Math.floor(Math.random()*11)
color = ["red", "blue","green","yellow","purple","white","pink","silver","teal","turqu oise","magenta","cyan"];
console.log(color[i])
function ballMovement() {
vy += gravity;
ball.y += vy;
if (ball.y + ball.radius > canvas.height) {
ball.y = canvas.height - ball.radius;
vy = 0;
var img = document.getElementById('gameOver');
ctx.drawImage(gameOver, canvas.width/2-436, 100)
ball.radius = 0;
}
}
function init() {
setupCanvas();
var img = document.getElementById('gameOver');
img.style.visibility = 'hidden';
//how high the ball goes
vy = -19;
var y1 = 450
ball = {
x: canvas.width/2,
//where the ball starts moving upwards
y: 480, //here1
radius: 20,
status: 0,
color: color[i]};
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2, false);
ctx.fillStyle = ball.color;
ctx.fill();
ctx.closePath()
//draw a moving ball
ballMovement();
}
setInterval(draw, 1000 / 35);
function setupCanvas() {
container = document.createElement('div');
container.className = "container";
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.body.appendChild(container);
container.appendChild(canvas);
ctx.strokeStyle = "#ffffff";
ctx.lineWidth = 2;
}
window.onclick = function(jump){
pull + 0.1;
touchGround = false;
init()
draw()
ballMovement()
setupCanvas()
vy+((canvas.height-canvas.height)-ball.y);
}
//GOAL
//Ball jumps at somewhere in screen, let it jump wherever it is.
If I got you correctly you want your ball to go higher and higher. But problem is that you got fixed position where it's starts so where's what you need to change:
var canvas, ctx, container;
canvas = document.createElement('canvas');
canvas.width = 800;
canvas.height = 800;
ctx = canvas.getContext("2d");
var ball = {
y: 480
};
var touchGround = false;
var pull= 0.43;
var vy;
var gravity = pull;
//Creating a variable to know whether our game is running
var gameRunning = 0;
var i = Math.floor(Math.random()*11);
//Adding variable for interval so we can start it with init function
var timer;
color = ["red", "blue","green","yellow","purple","pink","silver","teal","turquoise","magenta","cyan", "black"];
function ballMovement() {
vy += gravity;
ball.y += vy;
if (ball.y + ball.radius > canvas.height) {
ball.y = canvas.height - ball.radius;
vy = 0;
var img = document.getElementById('gameOver');
ctx.drawImage(gameOver, canvas.width/2-436, 100)
ball.radius = 0;
//Stoping the draw function
clearInterval(timer);
//Saying the game isn't running
gameRunning = 0;
}
}
function init() {
//Check if canvas already created
if(!document.querySelector('.container')){
setupCanvas()
}
vy = -19;
var y1 = 450
ball = {
x: canvas.width/2,
y: ball.y,
radius: 20,
status: 0,
color: color[i]
};
//Clearing previous interval if it were any and creating a new one
clearInterval(timer);
timer = setInterval(draw, 1000 / 60);
//Saying the game is running
gameRunning = 1;
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2, false);
ctx.fillStyle = ball.color;
ctx.fill();
ctx.closePath()
ballMovement();
}
function setupCanvas() {
container = document.createElement('div');
container.className = "container";
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.body.appendChild(container);
container.appendChild(canvas);
ctx.strokeStyle = "#ffffff";
ctx.lineWidth = 2;
}
window.onclick = function(){
pull + 0.1;
touchGround = false;
//Check if the game is running or not
//If it's not running - call init
if(!gameRunning){
init();
}
else{
//If game is already running - change speed
vy = -19;
}
//I've also removed some function that were starting with init itself
vy+((canvas.height-canvas.height)-ball.y);
}

HTML5 Canvas clearRect not working with beginPath and closePath

Im using HTML5 to draw on canvas. I have an array of points in which i loop through to change the x and y coordinates each cycle of draw. What i have now is the dots moving but not clearing the old one, so just lines moving across instead of dots. I looked up other answers which said that i need to use the beginPath with clearRect but that doesn't work. Is there something wrong with the placement of the clearRect can someone help me with this canvas stuff.
draw: function () {
var ctx = this.context;
if(this.isReady){
var PI2 = Math.PI * 2;
for (var i = 0; i < this.points.length; i++) {
var point = this.points[i];
var ic = this.calculateGravity(point.x, point.y);
ctx.clearRect(0, 0, this.cw, this.ch);
ctx.beginPath();
ctx.arc(point.x - ic.nX, point.y - ic.nY, point.radius, 0, PI2);
ctx.fillStyle= point.color;
ctx.strokeStyle= point.color;
ctx.closePath();
ctx.fill();
ctx.stroke();
this.points[i].x = point.x - ic.nX;
this.points[i].y = point.y - ic.nY;
}
}
},
You should clear your canvas just before rendering your points:
if(this.isReady){
ctx.clearRect(0, 0, this.cw, this.ch);
var PI2 = Math.PI * 2;
for (var i = 0; i < this.points.length; i++) {
/* ... */
}

Rotated shape is moving on y-axis when resizing shape width

I'm trying to resize a rotated shape on canvas. My problem is that when I call the rendering function, the shape starts "drifting" depending on the shape angle. How can I prevent this?
I've made a simplified fiddle demonstrating the problem, when the canvas is clicked, the shape is grown and for some reason it drifts upwards.
Here's the fiddle: https://jsfiddle.net/x5gxo1p7/
<style>
canvas {
position: absolute;
box-sizing: border-box;
border: 1px solid red;
}
</style>
<body>
<div>
<canvas id="canvas"></canvas>
</div>
</body>
<script type="text/javascript">
var canvas = document.getElementById('canvas');
canvas.width = 300;
canvas.height= 150;
var ctx = canvas.getContext('2d');
var counter = 0;
var shape = {
top: 120,
left: 120,
width: 120,
height: 60,
rotation: Math.PI / 180 * 15
};
function draw() {
var h2 = shape.height / 2;
var w2 = shape.width / 2;
var x = w2;
var y = h2;
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(75,37.5)
ctx.translate(x, y);
ctx.rotate(Math.PI / 180 * 15);
ctx.translate(-x, -y);
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, shape.width, shape.height);
ctx.restore();
}
canvas.addEventListener('click', function() {
shape.width = shape.width + 15;
window.requestAnimationFrame(draw.bind(this));
});
window.requestAnimationFrame(draw.bind(this));
</script>
In the "real" code the shape is resized when the resize-handle is clicked and moved but I think this example demonstrates the problem sufficiently.
EDIT: updated fiddle to clarify the issue:
https://jsfiddle.net/x5gxo1p7/9/
Always use local coordinates to define shapes.
When rendering content that is intended to be transformed the content should be in its own (local) coordinate system. Think of a image. the top left pixel is always at 0,0 on the image no matter where you render it. The pixels are at their local coordinates, when rendered they are moved to the (world) canvas coordinates via the current transformation.
So if you make your shape with coordinates set to its local, making the rotation point at its local origin (0,0) the display coordinates are stored separately as world coordinates
var shape = {
top: -30, // local coordinates with rotation origin
left: -60, // at 0,0
width: 120,
height: 60,
world : {
x : canvas.width / 2,
y : canvas.height / 2,
rot : Math.PI / 12, // 15deg clockwise
}
};
Now you don't have to mess about with translating forward and back... blah blah total pain.
Just
ctx.save();
ctx.translate(shape.world.x,shape.world.y);
ctx.rotate(shape.world.rot);
ctx.fillRect(shape.left, shape.top, shape.width, shape.height)
ctx.restore();
or event quicker and eliminating the need to use save and restore
ctx.setTransform(1,0,0,1,shape.world.x,shape.world.y);
ctx.rotate(shape.world.rot);
ctx.fillRect(shape.left, shape.top, shape.width, shape.height);
The local shape origin (0,0) is where the transformation places the translation.
This greatly simplifies a lot of the work that has to be done
var canvas = document.getElementById('canvas');
canvas.width = 300;
canvas.height= 150;
var ctx = canvas.getContext('2d');
ctx.fillStyle = "black";
ctx.strokeStyle = "red";
ctx.lineWidth = 2;
var shape = {
top: -30, // local coordinates with rotation origin
left: -60, // at 0,0
width: 120,
height: 60,
world : {
x : canvas.width / 2,
y : canvas.height / 2,
rot : Math.PI / 12, // 15deg clockwise
}
};
function draw() {
ctx.setTransform(1,0,0,1,0,0); // to clear use default transform
ctx.clearRect(0, 0, canvas.width, canvas.height);
// you were scaling the shape, that can be done via a transform
// once you have moved the shape to the world coordinates.
ctx.setTransform(1,0,0,1,shape.world.x,shape.world.y);
ctx.rotate(shape.world.rot);
// after the transformations have moved the local to the world
// you can ignore the canvas coordinates and work within the objects
// local. In this case showing the unscaled box
ctx.strokeRect(shape.left, shape.top, shape.width, shape.height);
// and a line above the box
ctx.strokeRect(shape.left, shape.top - 5, shape.width, 1);
ctx.scale(0.5,0.5); // the scaling you were doing
ctx.fillRect(shape.left, shape.top, shape.width, shape.height);
}
canvas.addEventListener('click', function() {
shape.width += 15;
shape.left -= 15 / 2;
shape.world.rot += Math.PI / 45; // rotate to illustrate location
// of local origin
var distToMove = 15/2;
shape.world.x += Math.cos(shape.world.rot) * distToMove;
shape.world.y += Math.sin(shape.world.rot) * distToMove;
draw();
});
// no need to use requestAnimationFrame (RAF) if you are not animation
// but its not wrong. Nor do you need to bind this (in this case
// this = window) to the callback RAF does not bind a context
// to the callback
/*window.requestAnimationFrame(draw.bind(this));*/
requestAnimationFrame(draw); // functionaly identical
// or just
/*draw()*/ //will work
body { font-family : Arial,"Helvetica Neue",Helvetica,sans-serif; font-size : 12px; color : #242729;} /* SO font currently being used */
canvas { border: 1px solid red; }
<canvas id="canvas"></canvas>
<p>Click to grow "and rotate" (I add that to illustrate the local origin)</p>
<p>I have added a red box and a line above the box, showing how using the local coordinates to define a shape makes it a lot easier to then manipulate that shape when rendering "see code comments".</p>
Try this. You had ctx.translate() used where it was not entirely necessary. That caused the problems.
<script type="text/javascript">
var canvas = document.getElementById('canvas');
canvas.width = 300;
canvas.height= 150;
var ctx = canvas.getContext('2d');
var counter = 0;
var shape = {
top: 120,
left: 120,
width: 120,
height: 60,
rotation: Math.PI / 180 * 15
};
function draw() {
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(75,37.5)
ctx.rotate(Math.PI / 180 * 15);
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, shape.width, shape.height);
ctx.restore();
}
canvas.addEventListener('click', function() {
shape.width = shape.width + 15;
window.requestAnimationFrame(draw.bind(this));
});
window.requestAnimationFrame(draw.bind(this));
</script>
This is happening because the x and y are set as the half value of the shape size, which completely changes its position.
You should set a point for the center of the shape, anyway. I set this point as ctx.canvas.[width or height] / 2, the half of the canvas.
var h2 = shape.height / 2;
var w2 = shape.width / 2;
var x = (ctx.canvas.width / 2) - w2;
var y = (ctx.canvas.height / 2) - h2;
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(x + (shape.width / 2), y + (shape.height / 2));
ctx.rotate(((shape.rotation * Math.PI) / 180) * 15);
ctx.fillStyle = '#000';
ctx.fillRect(-shape.width / 2, -shape.height / 2, shape.width, shape.height);
ctx.restore();
Fiddle.
Found a solution, problem was that I wasn't calculating the new center point coordinates.
The new fiddle with solution: https://jsfiddle.net/HTxGb/151/
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width =500;
canvas.height = 500;
var x = canvas.width/2;
var y = canvas.height/2;
var rectw = 20;
var recth = 20;
var rectx = -rectw/2;
var recty = -recth/2;
var rotation = 0;
var addedRotation = Math.PI/12;
var addedWidth = 20;
var addedHeight = 10;
var draw = function() {
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(x, y);
ctx.rotate(rotation);
ctx.fillRect(rectx, recty, rectw, recth);
ctx.restore();
}
document.getElementById('growXRight').addEventListener('click', function() {
rectx -= addedWidth/2;
x += addedWidth/2 * Math.cos(rotation);
y -= addedWidth/2 * Math.sin(-rotation);
rectw += addedWidth;
draw();
})
document.getElementById('growXLeft').addEventListener('click', function() {
rectx -= addedWidth/2;
x -= addedWidth/2 * Math.cos(rotation);
y += addedWidth/2 * Math.sin(-rotation);
rectw += addedWidth;
draw();
})
document.getElementById('growYTop').addEventListener('click', function() {
recty -= addedHeight/2;
x += addedHeight/2 * Math.sin(rotation);
y -= addedHeight/2 * Math.cos(-rotation);
recth += addedHeight;
draw();
})
document.getElementById('growYBottom').addEventListener('click', function() {
recty -= addedHeight/2;
x -= addedHeight/2 * Math.sin(rotation);
y += addedHeight/2 * Math.cos(-rotation);
recth += addedHeight;
draw();
})
document.getElementById('rotatePlus').addEventListener('click', function() {
rotation += addedRotation;
rotation = rotation % (Math.PI*2);
if(rotation % Math.PI*2 < 0) {
rotation += Math.PI*2;
}
draw();
})
document.getElementById('rotateMinus').addEventListener('click', function() {
rotation -= addedRotation;
rotation = rotation % (Math.PI*2);
if(rotation % Math.PI*2 < 0) {
rotation += Math.PI*2;
}
draw();
})
draw();

How to rotate a canvas object following mouse move event with easing?

I am not sure if I have used the right word here. I guess easing means it does not follow the mouse immediately but with some delay?
At the moment the iris is rotating to my mouse direction. What if I want it to have same effect as this?. Is it very hard to do so or just require simple code changes? Is there a standard way/solution for this kind of problem?
Here is my current code. It can also be found at Rotating Iris .
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
class Circle {
constructor(options) {
this.cx = options.x;
this.cy = options.y;
this.radius = options.radius;
this.color = options.color;
this.angle = options.angle;
this.binding();
}
binding() {
const self = this;
window.addEventListener('mousemove', (e) => {
self.calculateAngle(e);
});
}
calculateAngle(e) {
if (!e) return;
let rect = canvas.getBoundingClientRect(),
vx = e.clientX - this.cx,
vy = e.clientY - this.cy;
this.angle = Math.atan2(vy, vx);
}
renderEye() {
ctx.setTransform(1, 0, 0, 1, this.cx, this.cy);
ctx.rotate(this.angle);
let eyeRadius = this.radius / 3;
ctx.beginPath();
ctx.arc(this.radius / 2, 0, eyeRadius, 0, Math.PI * 2);
ctx.fill();
}
render() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.beginPath();
ctx.arc(this.cx, this.cy, this.radius, 0, Math.PI * 2);
ctx.closePath();
ctx.strokeStyle = '#09f';
ctx.lineWidth = 1;
ctx.stroke();
this.renderMessage();
this.renderEye();
}
renderMessage() {
ctx.font = "18px serif";
ctx.strokeStyle = 'black';
ctx.fillText('Angle: ' + this.angle, 30, canvas.height - 40);
}
}
var rotatingCircle = new Circle({
x: 320,
y: 160,
radius: 40,
color: 'black',
angle: Math.random() * Math.PI * 2
});
function animate() {
rotatingCircle.render();
requestAnimationFrame(animate);
}
animate();
<canvas id='canvas' style='width: 700; height: 500;'></canvas>
Updated with possible solution:
I actually followed the link I posted in the question and use a similar way to ease the rotation, which I think is similar to what #Blindman67 categories as non-deterministic easing.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
class Circle {
constructor(options) {
this.cx = options.x;
this.cy = options.y;
this.radius = options.radius;
this.color = options.color;
this.toAngle = 0;
this.angle = options.angle;
this.velocity = 0;
this.maxAccel = 0.04;
this.binding();
}
binding() {
const self = this;
window.addEventListener('mousemove', (e) => {
self.calculateAngle(e);
});
}
calculateAngle(e) {
if (!e) return;
let rect = canvas.getBoundingClientRect(),
// mx = parseInt(e.clientX - rect.left),
// my = parseInt(e.clientY - rect.top),
vx = e.clientX - this.cx,
vy = e.clientY - this.cy;
this.toAngle = Math.atan2(vy, vx);
}
clip(x, min, max) {
return x < min ? min : x > max ? max : x;
}
renderEye() {
ctx.setTransform(1, 0, 0, 1, this.cx, this.cy);
let radDiff = 0;
if (this.toAngle != undefined) {
radDiff = this.toAngle - this.angle;
}
if (radDiff > Math.PI) {
this.angle += 2 * Math.PI;
} else if (radDiff < -Math.PI) {
this.angle -= 2 * Math.PI;
}
let easing = 0.06;
let targetVel = radDiff * easing;
this.velocity = this.clip(targetVel, this.velocity - this.maxAccel, this.velocity + this.maxAccel);
this.angle += this.velocity;
ctx.rotate(this.angle);
let eyeRadius = this.radius / 3;
ctx.beginPath();
ctx.arc(this.radius / 2, 0, eyeRadius, 0, Math.PI * 2);
ctx.fill();
}
render() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.beginPath();
ctx.arc(this.cx, this.cy, this.radius, 0, Math.PI * 2);
ctx.closePath();
ctx.strokeStyle = '#09f';
ctx.lineWidth = 1;
ctx.stroke();
this.renderMessage();
this.renderEye();
}
renderMessage() {
ctx.font = "18px serif";
ctx.strokeStyle = 'black';
ctx.fillText('Angle: ' + this.angle.toFixed(3), 30, canvas.height - 40);
ctx.fillText('toAngle: ' + this.toAngle.toFixed(3), 30, canvas.height - 20);
}
}
var rotatingCircle = new Circle({
x: 250,
y: 130,
radius: 40,
color: 'black',
angle: Math.random() * Math.PI * 2
});
function animate() {
rotatingCircle.render();
requestAnimationFrame(animate);
}
animate();
<canvas id='canvas' style='width: 700; height: 500;'></canvas>
There are many ways to do easing. Two methods I will describe in short are deterministic easing and (surprisingly) non-deterministic. The difference being is that the destination of the ease is either known (determined) or unknown (awaiting more user input)
Deterministic easing.
For this you have a starting value and an end value. What you want to do is find a position between the two based on some time value. That means that the start and end values need to also be associated with a time.
For example
var startVal = 10;
var startTime = 100;
var endVal = 100;
var endTime = 200;
You will want to find the value at time 150 halfway between the two. To do this you convert the time to a fraction where the time 100 (start) returns 0 and the time 200 (end) return 1, we call this normalised time. You can then multiply the difference between the start and end values by this fraction to find the offset.
So for a time value 150 to get the value (theValue) we do the following.
var time = 150;
var timeDif = endTime - startTime
var fraction = (startTime - time) / timeDif; // the normalised time
var valueDif = endVal - startVal;
var valueOffset = valueDif * fraction;
var theValue = startVal + valueOffset;
or more concise.
// nt is normalised time
var nt = (startTime - time) / (endTime - startTime)
var theValue = startVal + (endVal - startVal) * nt;
Now to apply a easing we need to modify the normalised time. A easing function simply takes a value from 0 to 1 inclusive and modifies it. So if you input 0.25 the easing function returns 0.1, or 0.5 return 0.5 and 0.75 returns 0.9. As you can see the modification changes the rate of change over the time.
An example of an easing function.
var easeInOut = function (n, pow) {
n = Math.min(1, Math.max(0, n)); // clamp n
var nn = Math.pow( n, pow);
return (nn / ( nn + Math.pow(1 - n, pow)))
}
This function takes two inputs, the fraction n (0 to 1 inclusive) and the power. The power determines the amount of easing. If pow = 1 then the is no easing and the function returns n. If pow = 2 then the function is the same as the CSS ease in out function, starts slowly speeds up then slows down at the end. if pow < 1 and pow > 0 then the ease start quickly slows down midway and then speeds up to the end.
To use the easing function in the above easing value example
// nt is normalised time
var nt = (startTime - time) / (endTime - startTime);
nt = easeInOut(nt,2); // start slow speed up, end slow
var theValue = startVal + (endVal - startVal) * nt;
That is how deterministic easing is done
An excellent easing function page Easing examples and code and another page for a quick visual easing referance
Non-deterministic easing
You may not know what the end result of the easing function is as at any time it may change due to new user input, if you use the above methods and change the end value mid way through the ease the result will be inconsistent and ugly. If you have ever done calculus you may recognise that the ease function above is a polynomial and is thus the anti derivative of a simpler function. This function simply determines the amount of change per time step. So for the non deterministic solution all we know is the change for the next time step. For an ease in function (start quick and slow down as we approch the destination) we keep a value to represent the current speed (the rate of change) and modify that speed as needed.
const ACCELERATION_COEFFICIENT = 0.3;
const DRAG_COEFFICIENT = 0.99;
var currentVal = 100;
var destinationVal = 200;
var currentSpeed = 0;
Then for each time step you do the following
var accel = destinationVal - currentVal; // get the acceleration
accel *= ACCELERATION_COEFFICIENT; // modify it so we are not there instantly
currentSpeed += accel; // add that to the speed
currentSpeed *= DRAG_COEFFICIET; // add some drag to further ease the function as it approaches destination
currentVal += currentSpeed; // add the speed to the current value
Now the currentVal will approch the destination value, if the destination changes than the rate of change (speed) also changes in a consistent way. The currentVal may never get to the destination if the destination is always changing, if however the destination stops changing the current val will approch and eventually stop at destination (by stop I mean the speed will get so small as to be pointless)
This methods behaviour is very dependent on the two coefficients so playing with those values will vary the easing. Some values will give you an over shoot with a bit of a wobble, others will be very slow like moving through molasses.
You can also make it much more complex by adding a second rate of change, thus you can have accelerating acceleration, this will simulate things like air resistance that changes the acceleration over time. You can also add maximums to the rate of change to set speed limits.
That should help you do your easing.
More info
For more info see these answers How would I animate and How to scale between two points
Non-deterministic easing applied to your example
I have added the easing to your function but it has introduced a new problem that will happen when using cyclic values such as angle. As I will not go into it in this answer you can find a solution to that problem in Finding the smallest angle.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ACCELERATION_COEFFICIENT = 0.15;
const DRAG_COEFFICIENT = 0.5;
class Circle {
constructor(options) {
this.cx = options.x;
this.cy = options.y;
this.radius = options.radius;
this.color = options.color;
this.angle = options.angle;
this.angleSpeed = 0;
this.currentAngle = this.angle;
this.binding();
}
binding() {
const self = this;
window.addEventListener('mousemove', (e) => {
self.calculateAngle(e);
});
}
calculateAngle(e) {
if (!e) return;
let rect = canvas.getBoundingClientRect(),
vx = e.clientX - this.cx,
vy = e.clientY - this.cy;
this.angle = Math.atan2(vy, vx);
}
renderEye() {
// this should be in a separate function
this.angleSpeed += (this.angle - this.currentAngle) * ACCELERATION_COEFFICIENT;
this.angleSpeed *= DRAG_COEFFICIENT;
this.currentAngle += this.angleSpeed;
ctx.setTransform(1, 0, 0, 1, this.cx, this.cy);
ctx.rotate(this.currentAngle);
let eyeRadius = this.radius / 3;
ctx.beginPath();
ctx.arc(this.radius / 2, 0, eyeRadius, 0, Math.PI * 2);
ctx.fill();
}
render() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.beginPath();
ctx.arc(this.cx, this.cy, this.radius, 0, Math.PI * 2);
ctx.closePath();
ctx.strokeStyle = '#09f';
ctx.lineWidth = 1;
ctx.stroke();
this.renderMessage();
this.renderEye();
}
renderMessage() {
ctx.font = "18px serif";
ctx.strokeStyle = 'black';
ctx.fillText('Angle: ' + this.angle, 30, canvas.height - 40);
}
}
var rotatingCircle = new Circle({
x: 320,
y: 160,
radius: 40,
color: 'black',
angle: Math.random() * Math.PI * 2
});
function animate() {
rotatingCircle.render();
requestAnimationFrame(animate);
}
animate();
<canvas id='canvas' style='width: 700; height: 500;'></canvas>
As far as I know, with h5 canvas, you may need to write the ease functions yourself. However, css3 animations have several built-in ease functions, you can write .foo {transition: bottom 1s ease} and when .foo elements' bottom style property changes, they'll move in the velocities defined by the ease function. See: https://developer.mozilla.org/en-US/docs/Web/CSS/transition https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions .
Also, check out these amazing animated BB-8 s(built with css animations): http://codepen.io/mdixondesigns/pen/PPEJwz http://codepen.io/Chyngyz/pen/YWwYGq http://codepen.io/bullerb/pen/gMpxNZ

Categories