I come from a flash animation background, and am learning to create animations with HTML5 <canvas> and plain JavaScript.
In flash, you can draw a movieclip and link it to an actionScript class file. This makes each movieclip somewhat OO / modular and allows you to more easily call and reference them in your app.
As JS has no native class support, and drawing with Canvas seems more rudimentary than in Flash, what is an example of a way to structure your javascript for a large/complex canvas animation?
You can use a pseudo-class approach by using objects.
For example, if you want to lets say move a box across the canvas you define the box as an object and update it for each frame:
Live demo
function ooRect(x, y, w, h, color, dx, dy) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.color = color;
this.dx = dx;
this.dy = dy;
}
Now ooRect is an object which you can update to move around and change color etc.
You can extend it with methods which makes it self-contained update-wise so it updates itself per frame:
function ooRect(x, y, w, h, color, dx, dy) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.color = color;
this.dx = dx;
this.dy = dy;
this.update = function(ctx) {
this.x += this.dx;
this.y += this.dy;
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.w, this.h);
}
}
You can now have a "host" routine (or another special object) to update all your objects stored in the array:
var objects = [
new ooRect(10, 20, 50, 70, 'blue', 2, 3),
new ooRect(200, 300, 50, 70, 'red', -3, 2) /// etc.
];
(function loop() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); /// clear canvas
for(var i = 0, o; o = objects[i++];) /// update all objects
o.update(ctx);
requestAnimationFrame(loop); /// next frame
})();
Now it is a matter of implementing different types of shape objects with its properties and methods.
If you plan to use a lot of objects you can prototype them as that allow the browser to share code-base memory:
function ooRect(x, y, w, h, color, dx, dy) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.color = color;
this.dx = dx;
this.dy = dy;
}
ooRect.prototype.update = function(ctx) {
this.x += this.dx;
this.y += this.dy;
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.w, this.h);
}
Go with requirejs which is JavaScript file and module loader.
http://requirejs.org/
Related
So I'm working on a project where I have a canvas filled with moving balls. Its an extension/inspired by this codepen project : https://codepen.io/zetyler/pen/LergVR .
It essentially runs with the same physics in place as the codepen, but now I'm trying to draw the moving and colliding balls with images instead of random colors.
The original draw() method looks like this :
var pen = canvas.getContext('2d');
const W = canvas.width;
const H = canvas.height;
var numBalls = 30;
var grav = [0,-0.1];
function Ball(x,y,dx,dy,r) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.r = r;
this.color = 'hsl('+(Math.random()*360)+',90%,50%)';
this.draw = function() {
pen.fillStyle = this.color;
pen.beginPath();
pen.arc(this.x,this.y,this.r,0,2*Math.PI);
pen.fill();
}
I'm refactoring the draw method to try and work with an image instead of a random color fill, and so far I can't even get an image to show up. Currently my draw method looks like this:
const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d');
const width = canvas.width;
const height = canvas.height;
let numBalls = 1;
let grav = [0,-0.1];
//try feeding the ball function an object
//and destructuring the inputs
class Ball {
constructor (x, y, dx, dy, r) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.r = r;
//probably won't need this
//this.color = 'hsl(' + (Math.random() * 360) + ', 90%, 50%)';
}
draw() {
var thumbImg = document.createElement('img');
thumbImg.src = './svgs/javascriptIcon.svg';
thumbImg.onload = function() {
context.save();
context.beginPath();
context.arc(25, 25, 25, 0, Math.PI * 2, true);
context.closePath();
context.clip();
context.drawImage(thumbImg, 0, 0, 50, 50);
context.beginPath();
context.arc(0, 0, 25, 0, Math.PI * 2, true);
context.clip();
context.closePath();
context.restore();
};
}
It's been so long since I've used the html canvas. I can't figure out what I'm doing wrong. I thought I would at least be able to get the image to show up, but no such luck.
Thanks for checking it out! Please let me know what you think.
I am not sure why you are using ctx.clip,
but if you just want to replace the coloured balls with images try this in your draw method
this.draw = function() {
// make sure the img is loaded
//pen.fillStyle = this.color;
pen.beginPath();
pen.arc(this.x,this.y,this.r,0,2*Math.PI);
pen.drawImage(img,this.x, this.y,this.r, this.r)
pen.fill();
}
after that play with IMG x and y positions for example pen.drawImage(img,this.x + somefactor, this.y - somefactor,this.r + somefactor, this.r + somefatcor)
just to make sure that img is perfectly cover the coloured ball so it behavies just like it
The snippet code below shows a single function object called "Circle" being drawn to a canvas element. I know how to remove the visual aspect of the circle from the screen. I can simply change its opacity over time with c.globalAlpha=0.0; based on event.listener 's or 'object collision', However if I visually undraw said circle; it still is there and being computed on, its still taking up browser resources as it invisibly bounces to and fro on my canvas element.
So my question is: What is the best way to remove/delete a function object once instantiated/drawn to canvas? =>(so that it is truly removed and not invisibly bouncing in the browser)
let canvas = document.getElementById('myCanvas');
let c = canvas.getContext('2d');
function Circle(x, y, arc, dx, dy, radius){
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.arc = arc;
this.cCnt = 0;
this.radius = radius;
this.draw = function() {
c.beginPath();
//context.arc(x,y,r,sAngle,eAngle,counterclockwise);
c.arc(this.x, this.y, this.radius, this.arc, Math.PI * 2, false); //
c.globalAlpha=1;
c.strokeStyle = 'pink';
c.stroke();
}
this.update = function() {
if (this.x + this.radius > canvas.width || this.x - this.radius < 0){
this.dx = -this.dx;
}
if (this.y + this.radius > canvas.height || this.y - this.radius < 0){
this.dy = -this.dy;
}
this.x += this.dx;
this.y += this.dy;
this.draw();
}
}
var circle = new Circle(2, 2, 0, 1, 1, 2); // x, y, arc, xVel, yVel, radius
function animate() {
requestAnimationFrame(animate);
c.clearRect(0, 0, canvas.width, canvas.height)
circle.update();
}
animate();
body {
background-color: black;
margin: 0;
}
<canvas id="myCanvas" width="200" height="60" style="background-color: white">
A lot of canvas libraries solve this problem by keeping an array of objects that are in the canvas scene. Every time the animate() function is called, it loops through the list of objects and calls update() for each one (I'm using the names you're using for simplicity).
This allows you to control what is in the scene by adding or removing objects from the array. Once objects are removed from the array, they will no longer be updated (and will get trash collected if there are no other references hanging around).
Here's an example:
const sceneObjects = [];
function animate() {
requestAnimationFrame(animate);
c.clearRect(0, 0, canvas.width, canvas.height);
// Update every object in the scene
sceneObjects.forEach(obj => obj.update());
}
// Elsewhere...
function foo() {
// Remove an object
sceneObjects.pop();
// Add a different object
sceneObjects.push(new Circle(2, 2, 0, 1, 1, 2));
}
It's not uncommon to take this a step further by creating a Scene or Canvas class/object that keeps the list of scene objects and gives an interface for other parts of the program to use (for example, Scene.add(myNewCircle) or Scene.remove(myOldCircle)).
I am working on creating squares with HTML canvas and javascript. However, it is glitchy. I don't understand why. I have created two javascript objects, one for hollow blocks, one for filled blocks. I am iterating through both arrays to create the blocks at animate. However, I can only get one filled block at a time. I have not been able to have more than one at a time. However, I am successful creating hollow blocks. The code is the same, but it does not work the same. Why is that?
function hBlock(h,w,x,y){
this.h = h;
this.w = w;
this.x = x;
this.y = y;
this.create = function(){
ctx.rect(x,y,w,h);
ctx.stroke();
}
}
function fBlock(h,w,x,y){
this.h = h;
this.w = w;
this.x = x;
this.y = y;
this.create = function(){
ctx.fillRect(x,y,w,h);
}
}
function newblock(){
if(f==1){fBlocks.push(new fBlock(h,w,x,y));}
else{hBlocks.push(new hBlock(h,w,x,y));}
console.log(hBlocks);
console.log(fBlocks);
animate();
}
function animate(){
requestAnimationFrame(animate);
ctx.clearRect(0, 0, cw, ch);
if(hBlocks.length>1){hBlocks[hBlocks.length-1].create();}
if(fBlocks.length>1){fBlocks[fBlocks.length-1].create();}
}
I'm quite new to JS and have gone through courses online but, very frustratingly, I just seem to have such a hard time on my own so I'm sorry if this question has an obvious answer. Basically, this program bounces a colored ball around within a box. I want that color to change every time it hits a wall. I figured out a way to do so by putting all information under one function but the tutorial I'm using is saying (for tidy code purposes) that 2 functions will be better and so I really just want to understand how to do what I want to do when info is available in different functions since I know I will have to do that in the future. I will comment important code lines. Thank you so much to anyone who can help.
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var x = canvas.width/2;
var y = canvas.height-30;
var dx = 4;
var dy = -4;
var ballRadius = 30;
function drawBall() { \\draws the ball
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI*2);
ctx.fillStyle = "#ff0000";
ctx.fill();
ctx.closePath();
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBall();
x += dx;
y += dy;
if(x + dx > canvas.width-ballRadius || x + dx < ballRadius) { \\says when to bounce
dx = -dx;
drawBall.ctx.fillStyle = "#ff0000"; \\this line and next line are lines I wrote
drawBall.ctx.fill(); \\that are obviously incorrect (same goes for
} \\ if statement below). What am I doing wrong?
if(y + dy > canvas.height-ballRadius || y + dy < ballRadius) {
dy = -dy;
drawBall.ctx.fillStyle = "#0095DD";
drawBall.ctx.fill();
}
}
setInterval(draw, 10);
what you can do is pass parameters that will alter the behavior of the function.
in this case you will be passing the color you want.
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var x = canvas.width/2;
var y = canvas.height-30;
var dx = 4;
var dy = -4;
var ballRadius = 30;
function drawBall(color) { // draws the ball
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI*2);
ctx.fillStyle = color;
ctx.fill();
ctx.closePath();
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBall();
x += dx;
y += dy;
if(x + dx > canvas.width-ballRadius || x + dx < ballRadius) { // says when to bounce
dx = -dx;
drawBall("#ff0000");
}
if(y + dy > canvas.height-ballRadius || y + dy < ballRadius) {
dy = -dy;
drawBall("#0095DD");
}
}
It seems that you mix some concepts of JavaScript. So for reasons of readability and design, I would create a 'class' for the ball. Something like this:
function Ball(x, y, radius, color) {
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
}
You can create an instance of your ball with this:
var ball = new Ball(x, y, radius, color);
and access the properties in Java-style:
ball.color = "#0095DD";
You can also add some methods to your ball:
function Ball(x, y, radius, color) {
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
this.draw = function(ctx) {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2);
ctx.fillStyle = this.color;
ctx.fill();
ctx.closePath();
}
}
You can extend your code with this class and code. I think, you get it.
I would like to know how to get this to work. The argument
g of the function draw is to be the graphics context of a JavaScript canvas object.
What I want is to be able to say is
var c = document.getElementById("canvas");
var g = c.getContext("2d");
b = new Ball(300,200, 50, "red");
b.draw(g)
and have a red ball paint at the center (300,200) of radius 50px.
Here is the code for the class.
class Ball
{
constructor(x, y, radius, color)
{
this.x = x;
this.y = y;
this.radius = radius
this.color=color;
}
draw(g)
{
console.log(g.fillStyle);
g.fillStyle = this.color;
g.beginPath()
//g.arc(this.x, this.y, this.r, 0, 2*Math.PI);
//correct is below.....
g.arc(this.x, this.y, this.radius, 0, 2*Math.PI);
g.fill();
}
}
Thank you for any help you can supply. Please note I have enabled ECMA6 in my browser. The console is not raising any error messages.
You've got a type-o in draw(). Change this.r to this.radius to match the constructed variable.