The solution that I'm trying to achieve is to have a html5 canvas with bouncing balls inside. But the thing is that this balls need to bounce WITHIN the bound a certain shape (say a SVG map).
I've used Sketch.js to create my bouncing particles, and this can be illustrated in this pen. But I can't set the SVG element as the container for the sketch.
So, here the question stands: how can I do that? Is there a way using sketch? Or there is another framework that allows this behavior? Or even any known live example of this?
My JS code, which is a fork on one of rglazebrook's works, is
var particles = [],
particleCount = 150;
Particle = function(x, y) {
this.x = x;
this.y = y;
this.radius = random(7, 20);
this.rgba = 'rgba(' + floor(random(0, 255)) + ',' + floor(random(0, 255)) + ',' + floor(random(0, 255)) + ',' + random(.1, .8) + ')';
this.vx = random(-1, 1);
this.vy = random(-1, 1);
// Draw our particle to the canvas.
this.draw = function(ctx) {
ctx.fillStyle = this.rgba;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, TWO_PI);
ctx.fill();
};
// Update our position.
this.update = function(ctx) {
this.x += this.vx;
this.y += this.vy;
// Bounce off edges.
if (this.x + this.radius > ctx.width) {
this.vx *= -1;
this.x = ctx.width - this.radius;
}
if (this.x - this.radius < 0) {
this.vx *= -1;
this.x = this.radius;
}
if (this.y + this.radius > ctx.height) {
this.vy *= -1;
this.y = ctx.height - this.radius;
}
if (this.y - this.radius < 0) {
this.vy *= -1;
this.y = this.radius;
}
}
};
var sketch = Sketch.create({
container: document.getElementById("brazil"),
fullscreen: false,
width: 500,
height: 500,
setup: function() {
var i = particleCount;
while (i--) {
var p = new Particle(random(0, this.width), random(0, this.height));
particles.push(p);
}
},
update: function() {
var i = particleCount;
while (i--) {
particles[i].update(this);
}
},
draw: function() {
var i = particleCount;
while (i--) {
particles[i].draw(this);
}
}
});
Related
Background Information
I'm currently working on a project and I want the background to be a blank canvas with balls bouncing off the walls.
I've managed so far, but I've come across a problem.
Whenever I resize my browser window, neither my canvas or the balls in it follow through. I have no clue what I'm doing wrong.
codepen
Code
const canvas = document.querySelector('#responsive-canvas')
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let c = canvas.getContext('2d');
function Circle(x, y, dx, dy, radius, colour) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.radius = radius;
this.colour = colour;
this.draw = function() {
this.getNewColour = function() {
let symbols, colour;
symbols = "0123456789ABCDEF";
colour = "#";
for (let i = 0; i < 6; i++) {
colour += symbols[Math.floor(Math.random() * 16)];
}
c.strokeStyle = colour;
c.fillStyle = colour;
}
this.getNewColour();
this.x = x;
this.y = y;
this.radius = radius;
//this.getNewColour().colour = colour;
c.beginPath();
c.arc(x, y, radius, 0, Math.PI * 2, false);
// c.strokeStyle = 'blue';
c.stroke();
//c.fill();
}
this.update = function() {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.radius = radius;
if (x + radius > innerWidth || x - radius < 0) {
dx = -dx;
}
if (y + radius > innerHeight || y - radius < 0) {
dy = -dy;
}
x += dx;
y += dy;
this.draw();
}
}
let circleArr = [];
for (let i = 0; i < 23; i++) {
let radius = 50;
let x = Math.random() * (innerWidth - radius * 2) + radius;
let y = Math.random() * (innerHeight - radius * 2) + radius;
let dx = (Math.random() - 0.5);
let dy = (Math.random() - 0.5);
circleArr.push(new Circle(x, y, dx, dy, radius));
};
function animate() {
requestAnimationFrame(animate);
c.clearRect(0, 0, innerWidth, innerHeight)
for (var i = 0; i < circleArr.length; i++) {
circleArr[i].update();
}
};
animate();
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* BODY
*/
html,
body {
width: 100%;
height: 100%;
margin: 0;
}
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<meta name="description" content="">
<link rel="stylesheet" href="../dist/css/style.css">
<body>
<canvas id="responsive-canvas"></canvas>
</body>
<script src="js/canvas.js"></script>
</html>
you can use
ctx.scale(x,y);
to scale everything on the canvas by the given factor, the X and Y are scalling on X and Y axis respectively.
You might want to reset the canvas using
ctx.resetTransform() or ctx.setTransform(1,0,0,1,0,0);
Then draw your background from 0,0 to canvas.width and canvas.height
Then draw everything (all of the circles) and finally set the scalling to your desired value.
If you want the width and height to change, you just need to add the width and height in the same CSS area that the `{border-box} is in.
but if you want to make the images inside it to not be stretched, you would need to access the heigh specifically in the canvas using the above comments methods.
Preferably using:
width:() => document.documentElement.clientWidth
First you must fix your collision logic, then you can safely change the canvas size as needed.
Always resolve the collisions fully.
An unresolved collision means that your sim is in an impossible state (eg ball inside a wall). From that point the following states will be meaning less.
Fix
The reason why the balls move out of the canvas is because you are not moving them back onto the canvas if they are outside.
Also when you change the ball direction when it hits the side you do not ensure that the direction is the correct sign.
// if the dx = 1 and x > innerWidth + 1000 then dx becomes -1
// Next tine x is still > innerWidth + 1000 and you flip the sign of dx again to 1,
// Then dx to -1 and so on. You never move the ball
if(x + radius > innerWidth || x - radius < 0) {
dx = -dx;
}
Be systematic in the collision testing so that when the playfield (canvas) changes size you don't get unexpected results
When you update, first move then check for collisions. If there is a collision, move the ball again to ensure not magic ball in wall thing happening.
update() {
this.x += this.dx;
this.y += this.dy;
if (this.x < this.radius) {
this.x = this.radius;
this.dx = Math.abs(this.dx);
}
if (this.y < this.radius) {
this.y = this.radius;
this.dy = Math.abs(this.dy);
}
if (this.x > ctx.canvas.width - this.radius) {
this.x = ctx.canvas.width - this.radius;
this.dx = -Math.abs(this.dx);
}
if (this.y > ctx.canvas.height - this.radius) {
this.y = ctx.canvas.height - this.radius;
this.dy = -Math.abs(this.dy);
}
}
Resize the canvas
With the collision problem solved you can now change the canvas size whenever you need to. One way is to do it inside the animate function.
If the canvas size does not match the page size change the canvas size to match.
function animate () {
if (ctx.canvas.width !== innerWidth || ctx.canvas.height !== innerHeight) {
ctx.canvas.width = innerWidth;
ctx.canvas.height = innerHeight;
} else {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
}
// ... do stuff
requestAnimationFrame(animate);
}
Note that changing the canvas size also clears the canvas, that is why the clear is on the else clause.
Copy paste demo
There were many bad points in the code. You can copy paste the snippet below into code pen or use the information above to modify your code in your style.
const canvas = document.querySelector('#responsive-canvas')
canvas.width = innerWidth;
canvas.height = innerHeight;
// Set constants in one place so you can make changes quickly and easily
const DEFAULT_RADIUS = 50;
const MAX_SPEED = 5;
const CIRCLE_COUNT = 100;
Math.TAU = Math.PI * 2
// Use function to do repetitive code
Math.rand = (min, max) => Math.random() * (max - min) + min; //
function randomHexColor() {
return "#" + ((Math.random() * 0xFFFFFF | 0).toString(16).padStart(6,"0"));
}
// pulral names for arrays and variables that do not change should be constants
const circles = [];
const ctx = canvas.getContext('2d');
requestAnimationFrame(animate); // start frame renderer with a request, dont call it directly
function Circle( // using default params to set random values
radius = Math.rand(DEFAULT_RADIUS/4, DEFAULT_RADIUS), // radius must be first argument as its used to set random x, and y
x = Math.rand(radius, ctx.canvas.width - radius),
y = Math.rand(radius, ctx.canvas.height - radius),
dx = Math.rand(-MAX_SPEED, MAX_SPEED),
dy = Math.rand(-MAX_SPEED, MAX_SPEED),
colour = randomHexColor()
) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.radius = radius;
this.colour = colour;
}
// Define Circle functions as prototype outside the function Circle (runs faster)
Circle.prototype = {
draw() {
ctx.strokeStyle = ctx.fillStyle = this.colour;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.TAU);
ctx.stroke();
},
update() {
this.x += this.dx;
this.y += this.dy;
if (this.x < this.radius) {
this.x = this.radius;
this.dx = Math.abs(this.dx);
}
if (this.y < this.radius) {
this.y = this.radius;
this.dy = Math.abs(this.dy);
}
if (this.x > ctx.canvas.width - this.radius) {
this.x = ctx.canvas.width - this.radius;
this.dx = -Math.abs(this.dx);
}
if (this.y > ctx.canvas.height - this.radius) {
this.y = ctx.canvas.height - this.radius;
this.dy = -Math.abs(this.dy);
}
}
};
for (let i = 0; i < CIRCLE_COUNT; i++) { circles.push(new Circle()) }
function animate () {
if (ctx.canvas.width !== innerWidth || ctx.canvas.height !== innerHeight) {
ctx.canvas.width = innerWidth;
ctx.canvas.height = innerHeight;
} else {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
}
// use for of loop (saves having to mess with index)
for (const circle of circles) {
circle.update(); // seperate update and draw
circle.draw()
}
requestAnimationFrame(animate);
}
I run into a nice JS from codepen that outputs bouncing balls https://codepen.io/rglazebrook/pen/JbxaI
I tried to use it as a background to a specific div inside a webpage but when I use that code the output appears under the website footer (if you look into this empty page http://www.doronwolf.com/home2019/ and scroll way down you will see the bouncing are successfully loaded but only under the footer) so, how can I make that js code output run within the page and preferably within specific div called 'herosection' inside my page and not under the footer? down beleow, you can see the js code but please note that it depends on an external script that can be found here: https://rawgithub.com/soulwire/sketch.js/master/js/sketch.min.js
var particles = [],
particleCount = 200;
Particle = function(x,y) {
this.x = x;
this.y = y;
this.radius = random(3,30);
this.rgba = 'rgba('+floor(random(0,255))+','+floor(random(0,255))+','+floor(random(0,255))+','+random(.1,.8)+')';
this.vx = random(-2,2);
this.vy = random(-2,2);
// Draw our particle to the canvas.
this.draw = function(ctx) {
ctx.fillStyle = this.rgba;
ctx.beginPath();
ctx.arc(this.x,this.y,this.radius,0,TWO_PI);
ctx.fill();
};
// Update our position.
this.update = function(ctx) {
this.x += this.vx;
this.y += this.vy;
// Bounce off edges.
if(this.x + this.radius > ctx.width) {
this.vx *= -1;
this.x = ctx.width - this.radius;
}
if(this.x - this.radius < 0) {
this.vx *= -1;
this.x = this.radius;
}
if(this.y + this.radius > ctx.height) {
this.vy *= -1;
this.y = ctx.height - this.radius;
}
if(this.y - this.radius < 0) {
this.vy *= -1;
this.y = this.radius;
}
}
};
var sketch = Sketch.create({
setup: function() {
var i = particleCount;
while(i--) {
var p = new Particle(random(0, this.width),random(0, this.height));
particles.push(p);
}
},
update: function() {
var i = particleCount;
while(i--) {
particles[i].update(this);
}
},
draw: function() {
var i = particleCount;
while(i--) {
particles[i].draw(this);
}
}
});
there isn't a simple way of doing this with only the sketchjs librarie. try wrapping it in the p5 framework, they have helpers for that.
I did basic positing using
container: document.getElementById("YOURID")
fullscreen: false,
width: 3000,
height: 400,
as options to the Sketch.create() function
link to pen
A few of my circle objects are not bouncing back, even though I have tried to take care of the edge cases.
I followed everything from here -
https://www.youtube.com/watch?v=yq2au9EfeRQ
I've tried different ways of passing the Window width and height and changing them in different ways using the radius:
var canvas = document.querySelector('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var iW = window.innerWidth;
var iH = window.innerHeight;
var c = canvas.getContext('2d');
function Circle(x, y, dx, dy, radius) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.radius = radius;
this.draw = function() {
c.beginPath();
c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
c.strokeStyle = "orange";
c.stroke();
}
this.update = function() {
if (this.x + this.radius > iW || this.x - this.radius < 0) {
this.dx = ~this.dx;
}
if (this.y + this.radius > iH || this.y - this.radius < 0) {
this.dy = ~this.dy;
}
this.x += this.dx;
this.y += this.dy;
this.draw();
}
}
var circleArray = [];
for (var i = 0; i < 100; i++) {
var radius = 30;
var x = Math.random() * (iW - radius * 2) + radius;
var y = Math.random() * (iH - radius * 2) + radius;
var dx = Math.random() * 5;
var dy = Math.random() * 5;
circleArray.push(new Circle(x, y, dx, dy, radius));
}
function animate() {
requestAnimationFrame(animate);
c.clearRect(0, 0, iW, iH);
for (var i = 0; i < circleArray.length; i++) {
circleArray[i].update();
}
}
animate();
canvas { border : 1px solid black; }
body { margin :0; }
<canvas>abcd</canvas>
The circles are getting stuck and then are going out of the window after vibrating where they get stuck. They should bounce back but only a few are bouncing back. I am using brackets and also am getting an undefined error I don't know if that error is what is causing this problem.
You are not negating the dx and dy values. Instead you are flipping all their binary bits. But then note how ~(-1) === 0. This roughly explains the problem you see: circles that only move with -1 in either direction, will "bounce" to a move of 0 in that same direction. Consequently they are bouncing again on the next cycle, where they start moving with -1 again, ... and so they wiggle off the canvas.
So just use the minus operator:
this.dx = -this.dx;
this.dy = -this.dy;
Following the collision between a ball from an array and an object (rectangle), the ball doesn't seem to have the same bounce affect as it has when it hits the ground.
When coming into contact with the object, it seems to pick up speed and suddenly glitches through and comes to rest on the ground.
Questions:
Why does it seem to want to rest on the ground and not on the object itself?
How can I make the ball have the same bounce affect when coming into contact with the object as it has when coming into contact with the ground?
Code:
var balls = [];
var obstacle;
function setup() {
createCanvas(400, 400);
obstacle = new Obstacle();
}
function draw() {
background(75);
obstacle.display();
for (var i = 0; i < balls.length; i++) {
balls[i].display();
balls[i].update();
balls[i].edges();
RectCircleColliding(balls[i], obstacle);
//console.log(RectCircleColliding(balls[i], obstacle));
}
}
function mousePressed() {
balls.push(new Ball(mouseX, mouseY));
}
function Ball(x, y) {
this.x = x;
this.y = y;
this.r = 15;
this.gravity = 0.5;
this.velocity = 0;
this.display = function() {
fill(255, 0, 100);
stroke(255);
ellipse(this.x, this.y, this.r * 2);
}
this.update = function() {
this.velocity += this.gravity;
this.y += this.velocity;
}
this.edges = function() {
if (this.y >= height - this.r) {
this.y = height - this.r;
this.velocity = this.velocity * -1;
this.gravity = this.gravity * 1.1;
}
}
}
function Obstacle() {
this.x = width - width;
this.y = height / 2;
this.w = 200;
this.h = 25;
this.display = function() {
fill(0);
stroke(255);
rect(this.x, this.y, this.w, this.h);
}
}
function RectCircleColliding(Ball, Obstacle) {
// define obstacle borders
var oRight = Obstacle.x + Obstacle.w;
var oLeft = Obstacle.x;
var oTop = Obstacle.y;
var oBottom = Obstacle.y + Obstacle.h;
//compare ball's position (acounting for radius) with the obstacle's border
if (Ball.x + Ball.r > oLeft) {
if (Ball.x - Ball.r < oRight) {
if (Ball.y + Ball.r > oTop) {
if (Ball.y - Ball.r < oBottom) {
Ball.y = Obstacle.y - Ball.r * 2;
Ball.velocity = Ball.velocity * -1;
Ball.gravity = Ball.gravity * 1.1;
Ball.velocity += Ball.gravity;
Ball.y += Ball.velocity;
return (true);
}
}
}
}
return false;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.min.js"></script>
The main issue with this question's code is that we need to check for collisions and only allow updates when the ball is not colliding. Another issue was that when collisions occur we must cap the gravity to prevent the ball from plunging all the way to the ground.
Here is the corrected code:
var balls = [];
var obstacle;
function setup() {
createCanvas(400, 400);
obstacle = new Obstacle();
}
function draw() {
background(75);
obstacle.display();
for (var i = 0; i < balls.length; i++) {
balls[i].display();
if (!RectCircleColliding(balls[i], obstacle)){
balls[i].update();
balls[i].edges();
}
//console.log(RectCircleColliding(balls[i], obstacle));
}
}
function mousePressed() {
balls.push(new Ball(mouseX, mouseY));
}
function Ball(x, y) {
this.x = x;
this.y = y;
this.r = 15;
this.gravity = 0.5;
this.velocity = 0;
this.display = function() {
fill(255, 0, 100);
stroke(255);
ellipse(this.x, this.y, this.r * 2);
}
this.update = function() {
this.velocity += this.gravity;
this.y += this.velocity;
}
this.edges = function() {
if (this.y >= height - this.r) {
this.y = height - this.r;
this.velocity = this.velocity * -1;
this.gravity = this.gravity * 1.1;
}
}
}
function Obstacle() {
this.x = width - width;
this.y = height / 2;
this.w = 200;
this.h = 25;
this.display = function() {
fill(0);
stroke(255);
rect(this.x, this.y, this.w, this.h);
}
}
function RectCircleColliding(Ball, Obstacle) {
// define obstacle borders
var oRight = Obstacle.x + Obstacle.w;
var oLeft = Obstacle.x;
var oTop = Obstacle.y;
var oBottom = Obstacle.y + Obstacle.h;
//compare ball's position (acounting for radius) with the obstacle's border
if (Ball.x + Ball.r > oLeft) {
if (Ball.x - Ball.r < oRight) {
if (Ball.y + Ball.r > oTop) {
if (Ball.y - Ball.r < oBottom) {
let oldY = Ball.y;
Ball.y = oTop - Ball.r;
Ball.velocity = Ball.velocity * -1;
if (Ball.gravity < 2.0){
Ball.gravity = Ball.gravity * 1.1;
} else {
Ball.velocity = 0;
Ball.y = oldY;
}
return (true);
}
}
}
}
return false;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.min.js"></script>
Been trying to animate an object on canvas by putting it in an array but it wont seem to work.
Only have one object drawn at the moment but the thought is to add several more objects to the canvas hence the array.
Is there something wrong with the functions im using to draw and animate the object?
var draw;
function Canvas(canvas, ctx) {
this.canvas = canvas;
this.ctx = ctx;
}
function Direction(x, y) {
this.x = x;
this.y = y;
}
function Measures(cW, cH, radius, hR, degree, dirX, dirY, degree) {
this.cW = cW;
this.cH = cH;
this.radius = radius;
this.hR = hR;
this.dirX = dirX;
this.dirY = dirY;
this.degree = degree;
}
function Drawing(cW, cH, width, height, radius, hR, color) {
this.cW = canvas.width;
this.cH = canvas.height;
this.width = width;
this.height = height;
this.radius = height / 2;
this.hR = width - this.radius;
this.color = color;
this.render = function() {
ctx.fillStyle = this.color;
ctx.strokeStyle = this.color;
ctx.lineWidth = 1;
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.degree * Math.PI / 180);
ctx.translate(-this.x, -this.y);
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.lineTo(x + this.hR, this.y);
ctx.arc(this.x + this.hR, this.y + this.radius, this.radius, - Math.PI / 2, Math.PI / 2);
ctx.lineTo(this.x, this.y + height);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.restore();
}
this.move = function(multiplier) {
var multiplier = 2;
var borders = 5;
if(this.dirX > 0 && this.x > this.cW - borders - width){
this.degree = 90;
this.dirX = 0;
this.dirY = 1;
this.x = cW - borders;
}
else if(this.dirY > 0 && this.y > this.cH - borders - width){
this.degree = 180;
this.dirX = -1;
this.dirY = 0;
this.y = this.cH - borders;
}
else if(this.dirX < 0 && this.x < borders + width){
this.degree = -90;
this.dirX = 0;
this.dirY = -1;
this.x = borders;
}
else if(this.dirY < 0 && this.y < borders + width){
this.degree = 0;
this.dirX = 1;
this.dirY = 0;
this.y = borders;
}
this.x += this.dirX * multiplier;
this.y += this.dirY * multiplier;
this.render();
}
}
function animate() {
ctx.clearRect(0, 0, this.cW, this.cH);
draw.forEach(function(object) {
object.render(2);
});
requestAnimationFrame(animate);
}
function init() {
this.canvas = document.getElementById("my_canvas");
this.ctx = canvas.getContext("2d");
this.degree = Math.PI / 2;
draw = [];
draw.push(new Drawing(5, 5, 80, 60, new Direction(1,0), "#E5E5E5"));
animate();
}
window.onload = init;
</script>
<canvas id="my_canvas" width="1000" height="800" style="background-color:#33CC33"></canvas>