Drawing Object Arrays with JS Canvas - javascript

I'm new to JS, and I have not gone into jQuery or such yet. All that I'm trying to do is draw 4 simple circles on the canvas. I put the objects into an array because I planned to possibly expand in the future, but that information is irrelevant.
What I need help with, is understanding the basics of how to create objects within an array and then display them on a canvas. Here is my dysfunctionate code:
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d");
var W = 400, H = 500
canvas.width = W, canvas.height = H;
var ball = [3];
for (i===0;i<=3; i++){
ball[i] = {
x: (Math.floor(Math.random() * 350) + 50),
y: (Math.floor(Math.random() * 450) + 50),
color: "red",
radius: (Math.floor(Math.random() * 35) + 15),
draw: balls(x, y, color, radius)
};
}
function balls(x, y, color, radius){
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2*Math.PI, false);
ctx.fillStyle = color;
ctx.fill();
ctx.closePath();
}

You are making it very complex use this method:
function ball(){
this.x = Math.random() * 100;
this.y = Math.random() * 100;
this.radius = Math.random() * 10;
this.color = "#" + (Math.random() * 16777215).toString(16);
this.draw = function(){
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, 2*Math.PI, false);
ctx.fillStyle = color;
ctx.fill();
ctx.closePath();
}
}
var balls = [];
for(var i = 0; i < 4; i++) {
balls[i] = new ball();
balls[i].draw();
}

You can simply assign draw to the balls function like so:
ball[i] = {
....
draw: balls
};
Then to draw it, once you defined ball[i] call it's draw() function:
var b = ball[i];
b.draw(b.x, b.y, b.color ,b.radius);
Example Here
Edit: Using no parentheses like ball, is how to reference the function ball() itself. And one last thing, you need to change for (i===0;i<=3; i++) to for (i=0;i<=3; i++). The === is a strict comparison operator, = is assignment.

Related

Accessing array from objects method within array

I have researched this question but cannot find an answer. I am new to JavaScript arrays and method scope.
I have a circles array which contains Circle Object. This has an update method which ultimately lets it draw itself to the canvas and move
Problem is the Circles need to be able to access the location of other Circles in their circles array to see if they've collided.
But when I use the Circles method they don't recognise their holding array - circles:
let circles;
function init() {
circles = [];
for (i = 0; i < 5; i++) {
let dy = (Math.random() - 0.5) * 8;
let radius = Math.random() * 30;
let color = colors[Math.floor(Math.random() * colors.length)];
let x = Math.random() * (innerWidth - radius * 2) + radius;
let dx = (Math.random() - 0.5) * 8;
let y = Math.random() * (innerHeight - radius * 2) + radius;
circles.push(new Circle(x, y, dx, dy, radius, color));
}
}
function animate() {
requestAnimationFrame(animate);
c.clearRect(0, 0, innerWidth, innerHeight);
circles.forEach(circle => {
circle.update();
});
}
function Circle(x, y, dy, dx, radius, color) {
this.x = x;
this.y = y;
this.dy = dy;
this.dx = dx;
this.radius = radius;
this.color = color;
this.update = function (circles) {
for (i = 0; i < circles.length; i++) {
if (this === circles[i]) {
continue;
}
if (getDistance(this.x, this.y, circles[i].x, circles[i].y) - radius * 2 < 0) {
console.log('TOUCH')
}
}
this.draw();
}
this.draw = function () {
c.beginPath();
c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
// c.strokeStyle = 'black';
c.fillStyle = this.color;
c.fill();
c.closePath();
}
}
This function leads to error:
Uncaught TypeError: Cannot read property 'length' of undefined
Because it doesn't recognise the circles array.
How can I give my objects method access to the array the objects in?
Your update method on Circle expects a circles array to be passed, but you're not passing anything:
function animate() {
requestAnimationFrame(animate);
c.clearRect(0, 0, innerWidth, innerHeight);
circles.forEach(circle => {
circle.update(circles); // <--- HERE
});
}

JS target function comes to constructor not working

I am working on drawing moving rectangles on my canvas. I made a template function for the test purpose and it works, but since i want to draw more of the rectangles with same animation effect I have to make this template function comes to the constructor, getContextand now the problem occurs:
the template function:
ctx = getContext('2d');
var boxHeight = canvas.height/40;
var boxWidth = canvas.width/20;
function drawBox(){
var x = 20;
var y = canvas.height;
var w = boxWidth;
var h = boxHeight;
var timer = 0;
var ladder = Math.floor(Math.random()*((canvas.height*0.5)/boxHeight)) + 1;
for(var i = 0; i < ladder; i++){
ctx.fillStyle = 'hsl('+Math.abs(Math.sin(timer) * 255)+', 40%, 50%)';
ctx.fillRect(x,y,w,h);
ctx.strokeRect(x,y,w,h);
ctx.lineWidth = 2;
ctx.stroke();
y -= boxHeight;
timer += Math.random()*0.3;
}
}
function animate(){
ctx.clearRect(0,0,canvas.width,canvas.height);
window.requestAnimationFrame(animate);
drawBox();
}
animate();
this template function drawBox()just working fine, then i tried to enclose its properties into a Box()constructor object:
function Box(x, width) {
this.postion = {
x: x,
y: canvas.height
};
this.width = width;
this.height = canvas.height / 40;
this.colorTimer = 0;
this.draw = function() {
this.colorTimer += Math.random() * 0.3;
var ladder = Math.floor(Math.random() * ((canvas.height * 0.5) / boxHeight)) + 1;
for (var i = 0; i < ladder; i++) {
ctx.fillStyle = 'hsl(' + Math.abs(Math.sin(this.colorTimer) * 255) + ', 40%, 50%)';
ctx.fillRect(this.postion.x, this.postion.y, this.width, this.height);
ctx.strokeRect(this.postion.x, this.postion.y, this.width, this.height);
ctx.lineWidth = 2;
ctx.stroke();
this.postion.y -= this.height;
}
}
}
var myBox = new Box(20, boxWidth);
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
window.requestAnimationFrame(animate);
myBox.draw();
}
animate();
this is not working, i have been stuck with this about 2 hours and i don't think there is any method or properties difference between my Boxconstructor and my drawBoxobject. When it comes to myBoxobject to calling its draw()method, there is nothing pop out on the screen.
I am wondering did i just miss something important when creating Boxconstructor object? Could someone give me a hint please?
As #Todesengel mentioned, the real issue here is, you are re-initializing all the variables each time the template function (drawBox) is called. But you are not doing the same for the constructor. To resolve this, put this.colorTimer = 0 and this.postion.y = canvas.height insde the draw method (as these are the variables that need to be re-initialized).
However, there are other issues :
you are increasing the timer variable inside the for loop, in template function, but not doing the same for constructor
as #Barmar mentioned, you should define draw method as Box.prototype.draw, for efficiency (not mandatory though)
Here is the revised version of your code :
ctx = canvas.getContext('2d');
var boxHeight = canvas.height / 40;
var boxWidth = canvas.width / 20;
function Box(x, width) {
this.postion = {
x: x,
y: canvas.height
};
this.width = width;
this.height = canvas.height / 40;
}
Box.prototype.draw = function() {
this.colorTimer = 0;
this.postion.y = canvas.height;
var ladder = Math.floor(Math.random() * ((canvas.height * 0.5) / this.height)) + 1;
for (var i = 0; i < ladder; i++) {
ctx.fillStyle = 'hsl(' + Math.abs(Math.sin(this.colorTimer) * 255) + ', 40%, 50%)';
ctx.fillRect(this.postion.x, this.postion.y, this.width, this.height);
ctx.strokeRect(this.postion.x, this.postion.y, this.width, this.height);
ctx.lineWidth = 2;
ctx.stroke();
this.postion.y -= this.height;
this.colorTimer += Math.random() * 0.3;
}
}
var myBox = new Box(20, boxWidth);
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
myBox.draw();
window.requestAnimationFrame(animate);
}
animate();
<canvas id="canvas" width="300" height="300"></canvas>
I believe the important thing to note here is that in your first case, there is a new drawBox function being called every time, with the variables being instantiated and initialized, or "reset", each time. In your second case, the myBox object is not being recreated each time, so you have left over variables. These will not behave the same way. It should work as expected if you move var myBox = new Box(20, boxWidth); into the animate function.
Another fix, if you don't want to do recreate the myBox object for each call, is to reset the left over variables after each animate call. It would be more efficient, and probably more desirable, to do it this way.
You should not modify this.position.y in the draw method.
So remove this assignment from the loop:
this.postion.y -= this.height;
... and change the following lines to dynamically add -i*this.height:
ctx.fillRect(this.postion.x, this.postion.y-i*this.height, this.width, this.height);
ctx.strokeRect(this.postion.x, this.postion.y-i*this.height, this.width, this.height);
As others have said, you should better define the method on the prototype. And colorTimer should change in the for loop. I think you could do with a local variable though.
Demo:
var ctx = canvas.getContext('2d');
var boxHeight = canvas.height/40;
var boxWidth = canvas.width/20;
function Box(x, width) {
this.postion = {
x: x,
y: canvas.height
};
this.width = width;
this.height = canvas.height / 40;
}
Box.prototype.draw = function() {
var ladder = Math.floor(Math.random() * ((canvas.height * 0.5) / boxHeight)) + 1;
var colorTimer = 0;
for (var i = 0; i < ladder; i++) {
ctx.fillStyle = 'hsl(' + Math.abs(Math.sin(colorTimer) * 255) + ', 40%, 50%)';
ctx.fillRect(this.postion.x, this.postion.y-i*this.height, this.width, this.height);
ctx.strokeRect(this.postion.x, this.postion.y-i*this.height, this.width, this.height);
ctx.lineWidth = 2;
ctx.stroke();
colorTimer += Math.random() * 0.3;
}
};
var myBox = new Box(20, boxWidth);
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
window.requestAnimationFrame(animate);
myBox.draw();
}
animate();
<canvas id="canvas"></canvas>

Constructor, loops and arrays practice in vanilla JS

I'm trying to make an animation where particles flow from one side the other like a flock of birds. You can see a live version on my semi-finished portfolio here: https://benjamingibbsportfolio.000webhostapp.com/
I'm in the process of learning constructor functions, so I've decided to redo the above project using that type of programming.
I've managed to basically complete it, apart from the fact it only displays one particle/flake - it should show 100?
for(var i = 0; i < 100; i++)
{
flakes[i] = new Flake();
}
Where have I gone wrong?
Are all the particles simply being drawn at the same place?
I've uploaded the code here: https://jsfiddle.net/q7ja8qxv/
A single flake is drawn with this function:
this.display = function() {
ctx.clearRect(0, 0, w, h);
ctx.fillStyle = "#ffff00";
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2, true);
ctx.fill();
}
The source of the problem is the first line of the function:
ctx.clearRect(0, 0, w, h);
This clears the whole canvas...
This means that every flake "erases" the previous flake(s).
As a fix you have to remove the ctx.clearRect() call from Flake.display() and instead call it in a place where it is only executed once, before you start drawing the flakes. For example in drawFlakes() right before the loop:
function drawFlakes() {
ctx.clearRect(0, 0, w, h);
for (i = 0; i < flakes.length; i++) {
flakes[i].display();
flakes[i].move();
flakes[i].update();
}
angle += 0.01;
}
Complete example:
var canvas = document.getElementById('stars');
var ctx = canvas.getContext('2d')
var w = window.innerWidth;
var h = window.innerHeight;
var flakes = [];
var angle = 0;
canvas.width = w;
canvas.height = h;
for (var i = 0; i < 20; i++) {
flakes[i] = new Flake();
}
function drawFlakes() {
ctx.clearRect(0, 0, w, h);
for (i = 0; i < flakes.length; i++) {
flakes[i].display();
flakes[i].move();
flakes[i].update();
}
angle += 0.01;
}
function Flake() {
this.x = Math.random() * w;
this.y = Math.random() * h;
this.r = Math.random() * 5 + 2;
this.d = Math.random() * 1;
this.display = function() {
ctx.fillStyle = "#ffff00";
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2, true);
ctx.fill();
}
this.move = function() {
this.y += Math.pow(this.d, 2) + 1;
this.x += Math.sin(angle) * 60;
};
this.update = function() {
if (this.y > h) {
this.x = Math.random() * w;
this.y = 0;
this.r = this.r;
this.d = this.d;
}
};
}
setInterval(drawFlakes, 25);
body {
background: black;
overflow: hidden;
}
<canvas id="stars"></canvas>

Js object in a canvas game Uncaught TypeError: Cannot read property 'x' of undefined

I am making a canvas game, which draws an object on the canvas. But it keeps giving me an error x is undefined. It was working before but, when I added the star functions it broke. Any help as I am at a loss.
var spaceShip = {
position: {
x: canvas.width / 2,
y: canvas.height - 20
},
size: {
width: 50,
height: 12
},
Velocity: {
x: 20
},
drawSpaceShip: function(){ // Draw Spaceship Object
ctx.beginPath();
ctx.fillStyle = "blue";
ctx.fillRect(this.position.x, this.position.y, this.size.width, this.size.height);
ctx.fillRect(this.position.x + 15, this.position.y, this.size.width - 30, this.size.height / 2 - 12);
ctx.fillRect(this.position.x + 22.5, this.position.y, this.size.width - 45, this.size.height / 2 - 15);
ctx.closePath();
ctx.fill();
requestAnimationFrame(this.drawSpaceShip);
}// End drawShip function
};// End spaceShip Object
function Star(x, y, rad, velocity, fill){
this.x = Math.floor(Math.random() * 599);//this create a random number between 0 and 599 on the x axis
this.y = 0;
this.rad = Math.floor((Math.random() * 30) + 1);//this create a random number between 10 and 30 for the radius
this.velocity = 6;
this.fill = fill
this.draw = function(){
ctx.beginPath();
ctx.fillStyle = this.fill;
ctx.arc(this.x, this.y, this.rad, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
this.y += this.velocity;
}
}
function createMultipleStars(){
for (var i = 0; i <= 4; i++)
stars[i] = new Star(i * 50, 10, i, i, "rgba(255,215,0,0.6)");
}
createMultipleStars();
function step() {
ctx.clearRect(0,0,canvas.width, canvas.height);
for (var i = 0; i<= 4; i++)
stars[i].draw();
requestAnimationFrame(step);
}
spaceShip.drawSpaceShip();
step();
You are loosing spaceShip object context when detaching this.drawSpaceShip method and passing it to requestAnimationFrame function. In this case drawSpaceShip is invoked with global object context (this === window). You can bind context explicitly with Function.prototype.bind method:
requestAnimationFrame(this.drawSpaceShip.bind(this));

How to draw polygons on an HTML5 canvas?

I need to know how to draw polygons on a canvas. Without using jQuery or anything like that.
Create a path with moveTo and lineTo (live demo):
var ctx = canvas.getContext('2d');
ctx.fillStyle = '#f00';
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(100,50);
ctx.lineTo(50, 100);
ctx.lineTo(0, 90);
ctx.closePath();
ctx.fill();
from http://www.scienceprimer.com/drawing-regular-polygons-javascript-canvas:
The following code will draw a hexagon. Change the number of sides to create different regular polygons.
var ctx = document.getElementById('hexagon').getContext('2d');
// hexagon
var numberOfSides = 6,
size = 20,
Xcenter = 25,
Ycenter = 25;
ctx.beginPath();
ctx.moveTo (Xcenter + size * Math.cos(0), Ycenter + size * Math.sin(0));
for (var i = 1; i <= numberOfSides;i += 1) {
ctx.lineTo (Xcenter + size * Math.cos(i * 2 * Math.PI / numberOfSides), Ycenter + size * Math.sin(i * 2 * Math.PI / numberOfSides));
}
ctx.strokeStyle = "#000000";
ctx.lineWidth = 1;
ctx.stroke();
#hexagon { border: thin dashed red; }
<canvas id="hexagon"></canvas>
//poly [x,y, x,y, x,y.....];
var poly=[ 5,5, 100,50, 50,100, 10,90 ];
var canvas=document.getElementById("canvas")
var ctx = canvas.getContext('2d');
ctx.fillStyle = '#f00';
ctx.beginPath();
ctx.moveTo(poly[0], poly[1]);
for(let item=2 ; item < poly.length-1 ; item+=2 ){ctx.lineTo( poly[item] , poly[item+1] )}
ctx.closePath();
ctx.fill();
//create and fill polygon
CanvasRenderingContext2D.prototype.fillPolygon = function (pointsArray, fillColor, strokeColor) {
if (pointsArray.length <= 0) return;
this.moveTo(pointsArray[0][0], pointsArray[0][1]);
for (var i = 0; i < pointsArray.length; i++) {
this.lineTo(pointsArray[i][0], pointsArray[i][1]);
}
if (strokeColor != null && strokeColor != undefined)
this.strokeStyle = strokeColor;
if (fillColor != null && fillColor != undefined) {
this.fillStyle = fillColor;
this.fill();
}
}
//And you can use this method as
var polygonPoints = [[10,100],[20,75],[50,100],[100,100],[10,100]];
context.fillPolygon(polygonPoints, '#F00','#000');
Here is a function that even supports clockwise/anticlockwise drawing do that you control fills with the non-zero winding rule.
Here is a full article on how it works and more.
// Defines a path for any regular polygon with the specified number of sides and radius,
// centered on the provide x and y coordinates.
// optional parameters: startAngle and anticlockwise
function polygon(ctx, x, y, radius, sides, startAngle, anticlockwise) {
if (sides < 3) return;
var a = (Math.PI * 2)/sides;
a = anticlockwise?-a:a;
ctx.save();
ctx.translate(x,y);
ctx.rotate(startAngle);
ctx.moveTo(radius,0);
for (var i = 1; i < sides; i++) {
ctx.lineTo(radius*Math.cos(a*i),radius*Math.sin(a*i));
}
ctx.closePath();
ctx.restore();
}
// Example using the function.
// Define a path in the shape of a pentagon and then fill and stroke it.
context.beginPath();
polygon(context,125,125,100,5,-Math.PI/2);
context.fillStyle="rgba(227,11,93,0.75)";
context.fill();
context.stroke();
In addition to #canvastag, use a while loop with shift I think is more concise:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var poly = [5, 5, 100, 50, 50, 100, 10, 90];
// copy array
var shape = poly.slice(0);
ctx.fillStyle = '#f00'
ctx.beginPath();
ctx.moveTo(shape.shift(), shape.shift());
while(shape.length) {
ctx.lineTo(shape.shift(), shape.shift());
}
ctx.closePath();
ctx.fill();
You can use the lineTo() method same as:
var objctx = canvas.getContext('2d');
objctx.beginPath();
objctx.moveTo(75, 50);
objctx.lineTo(175, 50);
objctx.lineTo(200, 75);
objctx.lineTo(175, 100);
objctx.lineTo(75, 100);
objctx.lineTo(50, 75);
objctx.closePath();
objctx.fillStyle = "rgb(200,0,0)";
objctx.fill();
if you not want to fill the polygon use the stroke() method in the place of fill()
You can also check the following: http://www.authorcode.com/draw-and-fill-a-polygon-and-triangle-in-html5/
thanks
For the people looking for regular polygons:
function regPolyPath(r,p,ctx){ //Radius, #points, context
//Azurethi was here!
ctx.moveTo(r,0);
for(i=0; i<p+1; i++){
ctx.rotate(2*Math.PI/p);
ctx.lineTo(r,0);
}
ctx.rotate(-2*Math.PI/p);
}
Use:
//Get canvas Context
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.translate(60,60); //Moves the origin to what is currently 60,60
//ctx.rotate(Rotation); //Use this if you want the whole polygon rotated
regPolyPath(40,6,ctx); //Hexagon with radius 40
//ctx.rotate(-Rotation); //remember to 'un-rotate' (or save and restore)
ctx.stroke();
To make a simple hexagon without the need for a loop, Just use the beginPath() function. Make sure your canvas.getContext('2d') is the equal to ctx if not it will not work.
I also like to add a variable called times that I can use to scale the object if I need to.This what I don't need to change each number.
// Times Variable
var times = 1;
// Create a shape
ctx.beginPath();
ctx.moveTo(99*times, 0*times);
ctx.lineTo(99*times, 0*times);
ctx.lineTo(198*times, 50*times);
ctx.lineTo(198*times, 148*times);
ctx.lineTo(99*times, 198*times);
ctx.lineTo(99*times, 198*times);
ctx.lineTo(1*times, 148*times);
ctx.lineTo(1*times,57*times);
ctx.closePath();
ctx.clip();
ctx.stroke();
Let's do that with HTML and get that down to this:
<!DOCTYPE html>
<html>
<head>
<title> SVG hexagon </title>
</head>
<body>
<svg width="300" height="110" >
<polygon point="50 3, 100 28, 100 75, 50 100, 3 75, 3 25" stroke="red" fill="lime" stroke-width="5"/>
</svg>
</body>
</html>
var ctx = document.getElementById('hexagon').getContext('2d');
// hexagon
var numberOfSides = 4,
size = 25,
Xcenter = 40,
Ycenter = 40;
ctx.beginPath();
ctx.moveTo (Xcenter + size * Math.cos(0), Ycenter + size * Math.sin(0));
for (var i = 1; i <= numberOfSides;i += 1) {
ctx.lineTo (Xcenter + size * Math.cos(i * 2 * Math.PI / numberOfSides), Ycenter + size * Math.sin(i * 2 * Math.PI / numberOfSides));
}
ctx.strokeStyle = "#000000";
ctx.lineWidth = 1;
ctx.stroke();
#hexagon { border: thin dashed red; }
<canvas id="hexagon"></canvas>

Categories

Resources