I'm looking to simulate projectile motion (off the ground with a certain velocity, continuing until the ball lands on the ground again) ignoring air resistance. I'm using the canvas feature of HTML5 and JavaScript to do it. I'm a novice with JavaScript, so any help is appreciated.
This is my code so far. I'm running into the same error again and again: unexpected end of input. I'm also trying to include start, stop, and reset buttons, as well as the option for the user to input their own angle, so they can see the final horizontal distance change.
I've tried many different ways to get rid of this error; what am I missing?
<html>
<head>
<title>
Projectile Motion
</title>
</head>
<body>
<button onclick=settimer()>Start</button><br>
<button onclick=reset()>Reset</button><br>
<button onclick=cleartimer()>Stop</button><br>
Angle=<input type="text" id="Angle in Degrees" value=45></input>
<canvas id="mycanvas" height="600" width="600"></canvas>
<script>
function rectang() {
co.beginPath();
co.rect(0,0,600,600);
co.stroke();
}
var gravity
var Angle
var velocity
var velocityx
var velocityy
var distance
var co
var inc
var time
var x
var y
var radius
//list variables
function init() {
var can;
can = document.getElementById("mycanvas");
co = can.getContext("2d");
reset();
}
function reset() {
gravity = 5;
Angle = (45*(Math.PI)/180);
velocity = 10;
velocityx = velocity*Math.cos(Angle);
velocityy = velocity*Math.sin(Angle);
time = 0;
distance = velocityx*time;
inc = 0.5;
x = 0;
y = 600;
radius = 10;
}
function draw() {
co.clearRect(0,0,600,600);
circle(co,x,y,radius,"yellow",false)
time = time + inc;
x = x + velocityx*time;
y = y + velocityy*time;
velocityx = velocityx;
velocityy = velocityy + velocityy*time;
}
function settimer() {
if (timer == null) timer = setInterval(draw,time);
Angle = document.getElementById("Angle").value;
}
function cleartimer(){
clearInterval(timer);
timer = null;
}
init();
function circle(context,x,y,r,color,fill) {
if (fill == true) {
//if fill is true, fill the circle
var temp = context.fillStyle;
context.fillStyle = color;
context.beginPath();
context.arc(x,y,r,0,2*Math.PI);
context.fill();
context.fillStyle = temp;
}
else {
//if fill is false, don't fill the circle
var temp = context.strokeStyle;
context.strokeStyle = color;
context.beginPath();
context.arc(x,y,r,0,2*Math.PI);
context.stroke();
context.strokeStyle = temp;
}
</script>
</body>
</html>
There are some issues, mainly some kind of double-incrementing, where "time" was getting larger each loop, and x/y values were incrementing itself too. Also, for "velocityy" you should rather use negative value at start (bullet getting higher) and then add something based on gravity. And yellow isn't very readable here.
function rectang() {
co.beginPath();
co.rect(0,0,600,600);
co.stroke();
}
var gravity
var Angle
var velocity
var velocityx
var velocityy
var distance
var co
var inc
var time
var x
var y
var radius
//list variables
function init() {
var can;
can = document.getElementById("mycanvas");
co = can.getContext("2d");
reset();
}
function reset() {
gravity = 5;
Angle = (45*(Math.PI)/180);
velocity = 10;
velocityx = velocity*Math.cos(Angle);
velocityy = velocity*Math.sin(Angle)*-1;
time = 0;
distance = velocityx*time;
inc = 0.5;
x = 0;
y = 300;
radius = 10;
}
function draw() {
//console.log(x,y)
co.clearRect(0,0,600,300);
circle(co,x,y,radius,"yellow",false)
time = time + inc;
x = x + velocityx*inc;
y = y + velocityy*inc;
velocityx = velocityx;
velocityy = velocityy + gravity*inc*0.1;
}
var timer = null;
function settimer() {
if (timer == null) timer = setInterval(draw,time);
Angle = document.getElementById("Angle").value;
}
function cleartimer(){
clearInterval(timer);
timer = null;
}
init();
function circle(context,x,y,r,color,fill) {
x = Math.round(x)
y = Math.round(y)
//console.log(x,y,r,color,fill)
if (fill == true) {
//if fill is true, fill the circle
var temp = context.fillStyle;
context.fillStyle = color;
context.beginPath();
context.arc(x,y,r,0,2*Math.PI);
context.fill();
context.fillStyle = temp;
}
else {
//if fill is false, don't fill the circle
var temp = context.strokeStyle;
context.beginPath();
context.arc(x,y,r,0,2*Math.PI);
context.strokeStyle = color;
context.lineWidth = 4;
context.stroke();
context.strokeStyle = temp;
}
}
<button onclick=settimer()>Start</button><br>
<button onclick=reset()>Reset</button><br>
<button onclick=cleartimer()>Stop</button><br>
Angle=<input type="text" id="Angle" value=45></input>
<canvas id="mycanvas" height="300" width="600"></canvas>
Related
I am trying to make a circle follow the mouse in HTML Canvas which I am using in a game. I am trying to make the circle move 5px per iteration, but it goes slower when traveling horizontal and faster when it goes vertical. Here's the math that I used:
x=distance between mouse and circle on the x-axis
y=distance between mouse and circle on the y-axis
z=shortest distance between mouse and circle
a=number of units circle should move along the x-axis
b=number of units circle should move along the y axis
x^2 + y^2=z^2
Want the total distance traveled every iteration to be five pixels
a^2 + b^2 = 25
b/a=y/x
b=ay/x
a=sqrt(25-ay/x^2)
a^2+ay/x-25=0
Use Quadratic formula to find both answers
a=(-y/x+-sqrt(y/x)^2+100)/2
I replicated the problem in the code below
$(function(){
let canvas = $("canvas")[0];
let ctx = canvas.getContext("2d");
//Gets position of mouse and stores the value in variables mouseX and mouseY
let mouseX = mouseY = 0;
$("canvas").mousemove(function(e){
mouseX = e.pageX;
mouseY = e.pageY;
}).trigger("mousemove");
let circleX = 0;
let circleY = 0;
function loop(t){
//Background
ctx.fillStyle="blue";
ctx.fillRect(0, 0, canvas.width, canvas.height);
let xFromMouse = mouseX-circleX;
let yFromMouse = mouseY-circleY;
let yxRatio = yFromMouse/xFromMouse;
let xyRatio = xFromMouse/yFromMouse;
let speed = 25;
let possibleXValues = [(-yxRatio+Math.sqrt(Math.pow(yxRatio,2)+(4*speed)))/2,(-yxRatio-Math.sqrt(Math.pow(yxRatio,2)+(4*speed)))/2];
//I use this code as a temporary fix to stop the circle from completely disappearing
if(xFromMouse === 0 || isNaN(yxRatio) || isNaN(possibleXValues[0]) || isNaN(possibleXValues[1])){
possibleXValues = [0,0];
yxRatio = 0;
}
//Uses b=ay/x to calculate for y values
let possibleYValues = [possibleXValues[0]*yxRatio,possibleXValues[1]*yxRatio];
if(xFromMouse >= 0){
circleX += possibleXValues[0];
circleY += possibleYValues[0];
} else {
circleX += possibleXValues[1];
circleY += possibleYValues[1];
}
ctx.beginPath();
ctx.arc(circleX, circleY, 25, 0, 2 * Math.PI,false);
ctx.fillStyle = "red";
ctx.lineWidth = 0;
ctx.fill();
window.requestAnimationFrame(loop);
}
window.requestAnimationFrame(loop);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas width="450" height="250"></canvas>
I think you may be better using a cartesian to polar conversion. Here's an example from something I made previously. This will allow you to have a consistent step per iteration "speed".
//Canvas, context, mouse.
let c, a, m = { x:0, y:0};
//onload.
window.onload = function(){
let circle = {},
w, h,
speed = 5; //step speed = 5 "pixels" (this will be fractional in any one direction depending on direction of travel).
//setup
c = document.getElementById('canvas');
a = c.getContext('2d');
w = c.width = window.innerWidth;
h = c.height = window.innerHeight;
function move(){
//get distance and angle from mouse to circle.
let v1m = circle.x - m.x,
v2m = circle.y - m.y,
vDm = Math.sqrt(v1m*v1m + v2m*v2m),
vAm = Math.atan2(v2m, v1m);
//if distance is above some threshold, to stop jittering, move the circle by 'speed' towards mouse.
if(vDm > speed) {
circle.x -= Math.cos(vAm) * speed;
circle.y -= Math.sin(vAm) * speed;
}
}
function draw(){
//draw it all.
a.fillStyle = "blue";
a.fillRect(0,0,w,h);
a.fillStyle = "red";
a.beginPath();
a.arc(circle.x, circle.y, circle.r, Math.PI * 2, false);
a.closePath();
a.fill();
}
circle = {x:w/2, y:h/2, r:25};
function animate(){
requestAnimationFrame(animate);
move();
draw();
}
c.onmousemove = function(e){
m.x = e.pageX;
m.y = e.pageY;
};
animate();
}
<canvas id="canvas" width="450" height="250"></canvas>
Please check out this code :
(function() {
var cnv = document.getElementById('canvas');
if (cnv.getContext) {
var ctx = cnv.getContext('2d');
} else {
alert('God Damn it ...');
}
function initialize() {
window.addEventListener('resize', resizeCanvas, false);
resizeCanvas();
}
function draw() {
for (var i = 0; i < 25; i++) {
width = Math.random() * cnv.width;
height = Math.random() * cnv.height;
ctx.beginPath();
ctx.arc(width, height, 15, 0, Math.PI * 2);
ctx.strokeStyle = '#E1E1E1';
ctx.lineWidth = 1;
ctx.stroke();
}
}
function resizeCanvas() {
cnv.width = window.innerWidth;
cnv.height = window.innerHeight;
draw();
}
initialize();
})();
I have created 25 Circle shape with random position and I want to create an animation that scales up or down in a interval time. I know about setInterval but how should I call my shape to do something on it?
The first thing you will want to do is to have a place to store the position of your circles, since they are all going to be the same radius we can just store the x and y position. For that we can create a Circle function ("class") and have an array of circles:
var circles = []; // Array to store our circles
var minRadius = 1; // The smallest radius we can hit
var maxRadius = 100; // The largest radius we can hit
var currentRadius = 15;// The current radius of all our circles
var scaleBy = 1; // How the radius changes
var cnv = document.getElementById('canvas');
// ...
function initialize() {
window.addEventListener('resize', resizeCanvas, false);
resizeCanvas();
// Populating the array of circles to use when drawing
for (var i = 0; i < 25; i++) { // Make sure to do this after re-sizing the canvas
width = Math.random() * cnv.width;
height = Math.random() * cnv.height;
circles.push(new Circle(width, height));
}
}
// ...
function Circle(x, y){
this.x = x;
this.y = y;
}
Circle.prototype.draw = function(){
ctx.beginPath();
ctx.arc(this.x, this.y, currentRadius, 0, Math.PI * 2);
ctx.strokeStyle = '#E1E1E1';
ctx.lineWidth = 5;
ctx.stroke();
}
Now that you have some circles when you call draw you can iterate through the array and call circle.draw() for each circle element in your array:
function draw() {
// Clear the screen and draw the circles in our array
ctx.clearRect(0,0, cnv.width, cnv.height);
for (var i = 0; i < circles.length; i++) {
circles[i].draw();
}
}
One note is you will want to use ctx.clearRect(0,0, cnv.width, cnv.height) to clear the screen before drawing.
Finally you can now use setInterval to change the currentRadius (*While there is nothing wrong with setInterval I'd recommend using window.requestAnimationFrame for animation as it's a bit more smooth and efficient method). Then when you call draw it will draw the circles with the new value of currentRadius. In this example I'm going to have it start at 15. Then increase by 1 until it hits maxRadius, then we can flip the sign of scaleBy to start decreasing the radius to make them smaller. Finally when it his our minRadius you can flip the sign of scaleBy again to make it start scaling up again:
var timer = setInterval( function(){
// If we hit our min or max start scaling in the other direction
if(currentRadius > maxRadius || currentRadius < minRadius){
scaleBy *= -1;
}
currentRadius += scaleBy;
draw();
}, 50);
Below is a code snippet of the complete program:
(function() {
var circles = [];
var minRadius = 1;
var maxRadius = 100;
var currentRadius = 15;
var scaleBy = 1;
var cnv = document.getElementById('canvas');
if (cnv.getContext) {
var ctx = cnv.getContext('2d');
} else {
alert('God Damn it ...');
}
function initialize() {
window.addEventListener('resize', resizeCanvas, false);
resizeCanvas();
for (var i = 0; i < 25; i++) {
width = Math.random() * cnv.width;
height = Math.random() * cnv.height;
circles.push(new Circle(width, height));
}
}
function draw() {
ctx.clearRect(0,0, cnv.width, cnv.height);
for (var i = 0; i < circles.length; i++) {
circles[i].draw();
}
}
function resizeCanvas() {
cnv.width = window.innerWidth;
cnv.height = window.innerHeight;
}
function Circle(x, y){
this.x = x;
this.y = y;
}
Circle.prototype.draw = function(){
ctx.beginPath();
ctx.arc(this.x, this.y, currentRadius, 0, Math.PI * 2);
ctx.strokeStyle = '#E1E1E1';
ctx.lineWidth = 5;
ctx.stroke();
}
initialize();
var timer = setInterval( function(){
if(currentRadius > maxRadius || currentRadius < minRadius){
scaleBy *= -1;
}
currentRadius += scaleBy;
draw();
}, 50);
})();
<canvas id="canvas"></canvas>
I'm trying to make a simple canvas program where the user clicks to create bouncing moving circles. It keeps freezing but still creates the circles without updating. I'm not sure whats going on, please help!
I'm adding each circle to an array of circles with the constructor
The setInterval loop seems to be freezing but the circles are still created even when this is happening
I'm having a hard time debugging this, any advice is greatly appreciated
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Background Test</title>
<style>
* { margin: 0; padding: 0; overflow: hidden; }
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
// Request animation frame -> Optimizes animation speed
const requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
const c = document.getElementById('canvas');
const ctx = c.getContext('2d');
// Fullscreen
c.width = window.innerWidth;
c.height = window.innerHeight;
ctx.fillStyle = 'red';
let fps = 60;
// FOR MOBILE DEVICES
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent))
fps = 29;
// Options
const background = '#333';
const circleMinSpeed = 3;
const circleMaxSpeed = 6;
const circleMinSize = 3;
const circleMaxSize = 10;
const circles = [];
let circlesCounter = 0;
const circlesTimeAlive = 20 * fps; // seconds
let i = 0;
const interval = 1000 / fps;
let now, delta;
let then = Date.now();
// Coordinate variables
let mouseX, mouseY, clickX, clickY;
// Tracks mouse movement
c.onmousemove = function(event)
{
mouseX = event.clientX;
mouseY = event.clientY;
};
// Tracks mouse click
c.onmousedown = function(event)
{
clickX = event.clientX;
clickY = event.clientY;
circle(clickX, clickY);
};
function draw()
{
// Loop
requestAnimationFrame(draw);
// Set NOW and DELTA
now = Date.now();
delta = now - then;
// New frame
if (delta > interval) {
// Update THEN
then = now - (delta % interval);
// Our animation
// Clear canvas then draw
ctx.clearRect(0, 0, c.width, c.height);
drawBackground();
drawCos();
drawCircles();
drawTest();
}
}
// Circle constructor
function circle(x, y)
{
// Pick random color
let r = Math.floor(Math.random() * 255);
let g = Math.floor(Math.random() * 255);
let b = Math.floor(Math.random() * 255);
self.color = 'rgb(' + r + ', ' + g + ', ' + b + ')';
self.xCo = x;
self.yCo = y;
// Pick random size within ranges
self.size = circleMinSize + Math.floor(Math.random() *
(circleMaxSize - circleMinSize));
// Pick random direction & speed (spdX spdY)
self.speed = circleMinSpeed + Math.floor(Math.random() *
(circleMaxSpeed - circleMinSpeed));
self.spdX = self.speed * (Math.random() * 2) - 1; // picks -1 to 1
self.spdY = self.speed * (Math.random() * 2) - 1;
self.draw = function()
{
ctx.beginPath();
ctx.arc(self.xCo, self.yCo, self.size, 0, 2*Math.PI);
ctx.fillStyle = self.color;
ctx.fill();
};
circles[circlesCounter++] = self;
}
// Draw the background
function drawBackground()
{
ctx.fillStyle = background;
ctx.fillRect(0, 0, c.width, c.height);
}
function drawCircles()
{
for (let i = 0; i < circles.length; i++)
circles[i].draw();
}
function drawTest()
{
ctx.fillStyle = 'red';
ctx.fillRect(i++, i, 5, 5);
}
function drawCos()
{
ctx.fillStyle = 'white';
ctx.fillText("X: " + mouseX + " Y:" + mouseY, 10, 10, 200);
}
// Main loop
setInterval(function()
{
// Loop through circles and move them
for (let i = 0; i < circles.length; i++)
{
if (circle[i])
{
// Check left and right bounce
if (circle[i].xCo <= 0 || circle[i].xCo >= c.width)
circle[i].spdX = -circle[i].spdX;
circle[i].xCo += circle[i].spdX;
// Check left and right bounce
if (circle[i].yCo <= 0 || circle[i].yCo >= c.height)
circle[i].spdY = -circle[i].spdY;
circle[i].yCo += circle[i].spdY;
}
}
// Draw Everything
draw();
}, interval);
</script>
</body>
</html>
This code:
self.draw = function()
{
ctx.beginPath();
ctx.arc(self.xCo, self.yCo, self.size, 0, 2*Math.PI);
ctx.fillStyle = self.color;
ctx.fill();
};
Is overriding this function:
function draw()
{
// Loop
requestAnimationFrame(draw);
// Set NOW and DELTA
now = Date.now();
delta = now - then;
// New frame
if (delta > interval) {
// Update THEN
then = now - (delta % interval);
// Our animation
// Clear canvas then draw
ctx.clearRect(0, 0, c.width, c.height);
drawBackground();
drawCos();
drawCircles();
drawTest();
}
}
You need to rethink how you want to draw your circles because you're re-drawing the black canvas every time a click event is triggered. I mean, when a click is triggered, you're applying new coordinates, color, Etc, and probably that's not what you want to do.
My suggestion is create canvas per circle and append them into a DIV.
Hope it helps!
I am trying to get my balls, in the array, to move directly down the canvas after a short period of time, one after the other to equal exactly 100 balls dropped. I included all of my code to try and show more of what i want the program to do.
Really the balls spawn and a user controls the paddle scoring points for each ball hit. Their are radio buttons that can be checked to make the balls fall faster or slower.
Im just stuck on getting the balls to move down the canvas one at a time.
var posY = 0;
var spawnRateOfDescent = 2;
var spawnRate = 500;
var lastSpawn = -1;
var balls = [100];
var startTime = Date.now();
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
function checkRadio() {
if (document.getElementById("moderate").checked == true) {
spawnRate = 250;
} else if (document.getElementById("hard").checked == true) {
spawnRate = 100;
} else if (document.getElementById("easy").checked == true) {
spawnRate = 500;
}
}
function startGame() {
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
baton = new batonPiece(50, 10, "28E060", 210, 250)
}
function ball() {
this.x = Math.random() * (canvas.width - 30) + 15;
this.y = 0;
this.radius = 10;
this.color = randomColor();
this.draw = function () {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, false);
ctx.fillStyle = randomColor();
ctx.fill();
ctx.closePath();
}
}
var balls = [];
for (var i = 0; i < 100; i++) {
balls[i] = new ball();
balls[i].draw();
}
function batonPiece(width, height, color, x, y) {
this.width = width;
this.height = height;
this.x = x;
this.y = y;
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
function randomColor() {
var letter = "0123456789ABCDEF".split("");
var color = "#";
for (var i = 0; i < 6; i = i + 1) {
color += letter[Math.round(Math.random() * 15)];
}
return color;
}
function moveLeft() {
batonPiece.x -= 1;
}
function moveRight() {
baton.x += 1;
}
function stopMove() {
baton.x = 0;
}
Is there a way to keep count of the number of shapes drawn on a canvas
I'm using a brush of sorts to draw a string of circles on a canvas and would like to find a way to count how many are present
var mainCanvas = document.getElementById('draw1');
mainContext = mainCanvas.getContext('2d');
var CircleBrush = {
iPrevX: 0,
iPrevY: 0,
// initialization function
init: function () {
mainContext.globalCompositeOperation = 'source-over';
mainContext.lineWidth = 1;
mainContext.strokeStyle = '#4679BD';
mainContext.lineWidth = 1;
mainContext.lineJoin = 'round';
},
startCurve: function (x, y) {
this.iPrevX = x;
this.iPrevY = y;
mainContext.fillStyle = '#4679BD';
},
draw: function (x, y) {
var iXAbs = Math.abs(x - this.iPrevX);
var iYAbs = Math.abs(y - this.iPrevY);
var rad = 6;
if (iXAbs > 10 || iYAbs > 10) {
mainContext.beginPath();
mainContext.arc(this.iPrevX, this.iPrevY, rad, Math.PI * 2, false);
mainContext.fill();
mainContext.stroke();
this.iPrevX = x;
this.iPrevY = y;
}
}
};
var circleCounter = [0];
mainContext.font = '21pt Arial';
mainContext.fillStyle = '#262732';
mainContext.textBaseline = 'top';
mainContext.fillText(circleCounter, 20, 20);
CircleBrush.init();
$('#draw1').mousedown(function (e) { // mouse down handler
cMoeDo = true;
var canvasOffset = $('#draw1').offset();
var canvasX = Math.floor(e.pageX - canvasOffset.left);
var canvasY = Math.floor(e.pageY - canvasOffset.top);
CircleBrush.startCurve(canvasX, canvasY);
circleCounter ++1;
});
$('#draw1').mouseup(function (e) { // mouse up handler
cMoeDo = false;
});
$('#draw1').mousemove(function (e) { // mouse move handler
if (cMoeDo) {
var canvasOffset = $('#draw1').offset();
var canvasX = Math.floor(e.pageX - canvasOffset.left);
var canvasY = Math.floor(e.pageY - canvasOffset.top);
CircleBrush.draw(canvasX, canvasY);
circleCounter ++1;
}
})
Demo fiddle http://jsfiddle.net/A2vyY/
Thanks in advance
You need to clear the space for the counter and redraw the count. In order to do so I put the counter and text drawing in the draw function like so
draw: function (x, y) {
var iXAbs = Math.abs(x - this.iPrevX);
var iYAbs = Math.abs(y - this.iPrevY);
var rad = 6;
if (iXAbs > 10 || iYAbs > 10) {
mainContext.beginPath();
mainContext.arc(this.iPrevX, this.iPrevY, rad, Math.PI*2, false);
mainContext.fill();
mainContext.stroke();
this.iPrevX = x;
this.iPrevY = y;
circleCounter ++;
mainContext.clearRect(0,0,50,25);
mainContext.fillText(circleCounter, 5, 5);
}
}
Updated jsFiddle (I moved the counter some so that there is more room for the dots)
You can put the counter in a separate div and just update the text
<div id="content">
<div id="counter">0</div>
<canvas id="draw1" height="500" width="500"></canvas>
</div>
Return true when a circle is drawn, false if not
draw: function (x, y) {
/* ... */
if (iXAbs > 10 || iYAbs > 10) {
/* ... */
return true;
}
return false;
}
increment and show as necessary
if (CircleBrush.draw(canvasX, canvasY)) {
++circleCounter;
$('#counter').text(circleCounter);
}
See modified JSFiddle