This is hard to explain in words, rather I uploaded the site here to demonstrate the problem. There's a snippet below that has the code and simulates the same problem.
Notice how when you scroll down, and hover near the edge, white imprints are left outside the canvas on neighbouring divs. Why does this happen, and how do I fix it?
var canvas = document.querySelector('canvas');
canvas.width = document.body.clientWidth;
canvas.height = document.body.clientHeight;
var context = canvas.getContext('2d');
var mouse = {
x: undefined,
y: undefined
}
window.addEventListener('mousemove', function(event){
mouse.x = event.x;
mouse.y = event.y;
});
function Circle(x, y, dx, dy, radius, bg){
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.radius = radius;
this.defaultRadius = radius;
this.bg = bg;
this.draw = function(){
context.beginPath();
context.arc(this.x, this.y, this.radius, 0, Math.PI*2, false);
context.fillStyle = this.bg;
context.fill();
}
this.update = function(){
if (this.x + this.radius > innerWidth || this.x - this.radius < 0) {
this.dx = -this.dx;
}
if (this.y + this.radius > innerHeight || this.y - this.radius < 0) {
this.dy = -this.dy;
}
if (this.x - mouse.x < 50 && this.x - mouse.x > -50 && this.y - mouse.y < 50 && this.y - mouse.y > -50) {
if (this.radius < 30) {
this.radius += 5;
}
} else {
if (this.radius > this.defaultRadius) {
this.radius -= 1;
}
}
this.x += this.dx;
this.y += this.dy;
this.draw();
}
}
var circlesArray = [];
for(var i = 0; i < 300; i++){
addNewCircle();
}
function addNewCircle(){
var radius = (Math.random() * 2) + 1;
var x = (Math.random() * (innerWidth - (radius*2))) + radius;
var y = (Math.random() * (innerHeight - (radius*2))) + radius;
var dx = Math.random() - 0.5;
var dy = Math.random() - 0.5;
var bg = 'rgba(255, 255, 255, 0.1)';
circlesArray.push(new Circle(x, y, dx, dy, radius, bg));
}
function animate(){
requestAnimationFrame(animate);
context.clearRect(0, 0, innerWidth, innerHeight);
for(var i = 0; i < circlesArray.length; i++){
circlesArray[i].update();
}
}
animate();
*{
box-sizing: border-box;
font-family: 'Roboto', sans-serif;
font-weight: 400;
margin: 0;
padding: 0;
color: rgba(0,0,0, 0.8);
}
#page{
width: 100%;
height: 100vh;
background: rgba(0, 0, 0, 0.7);
}
#page canvas{
position: absolute;
top: 0;
left: 0;
}
#top-bar{
width: 100%;
height: 200px;
background: black;
}
<!DOCTYPE html>
<html>
<body>
<div id="page">
<canvas></canvas>
</div>
<div id="top-bar">
</div>
</body>
</html>
Your canvas is setting height based on body, but your #top-bar div is taking up some of that space.
So that extra space is where you #top-bar div is
You could set the canvas size to be (body - #top-bar height).
Related
While learning js I made this one. It is working (moving) on computer with tracking mouse position but i couldnt integrate it to smart phones. I want it to move with touch constantly. I guess i need to use touch event, can anyone help me about this? Also I am a new learner, If you see any ridiculous mistake and inform me I will be so gratefull!
Its not working on safari
// setup canvas
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const width = canvas.width = window.innerWidth;
const height = canvas.height = window.innerHeight;
let Audio1 = new Audio("a.wav")
// function to generate random number
function random(min, max) {
const num = Math.floor(Math.random() * (max - min + 1)) + min;
return num;
}
class Shape {
constructor(x, y, velX, velY, size, color, exists) {
this.x = x;
this.y = y;
this.velX = velX;
this.velY = velY;
this.color = color;
this.size = size;
this.exists = exists;
}
}
class Ball extends Shape {
constructor(x, y, velX, velY, size, color, exists) {
super(x, y, velX, velY, size, color, exists)
}
draw() {
ctx.beginPath();
ctx.fillStyle = this.color;
ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
ctx.fill();
}
update() {
if ((this.x + this.size) >= width) {
this.velX = -(this.velX);
}
if ((this.x - this.size) <= 0) {
this.velX = -(this.velX);
}
if ((this.y + this.size) >= height) {
this.velY = -(this.velY);
}
if ((this.y - this.size) <= 0) {
this.velY = -(this.velY);
}
this.x += this.velX;
this.y += this.velY;
}
collisionDetect() {
for (let j = 0; j < balls.length; j++) {
if (!(this === balls[j]) && balls[j].exists) {
const dx = this.x - balls[j].x;
const dy = this.y - balls[j].y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < this.size + balls[j].size) {
balls[j].color = 'rgb(' + random(0, 255) + ',' + random(0, 255) + ',' + random(0, 255) + ')';
this.color = 'rgb(' + random(0, 255) + ',' + random(0, 255) + ',' + random(0, 255) + ')';
this.velX = -(this.velX) + 1;
this.velY = -(this.velY);
balls[j].velX = (balls[j].velX);
balls[j].velY = (balls[j].velY)
}
}
}
}
}
let balls = [];
let i = balls.length;
for (i = 0; i < 10; i++) {
let size = random(2, 10);
let ball = new Ball(
// ball position always drawn at least one ball width
// away from the edge of the canvas, to avoid drawing errors
random(0 + size, width - size),
random(0 + size, height - size),
random(-9, 9),
random(-9, 9),
size,
'rgb(' + random(0, 255) + ',' + random(0, 255) + ',' + random(0, 255) + ')', true
);
balls.push(ball);
}
let dir = -10;
var pctOpen = 100;
class EvilCircle extends Shape {
constructor(x, y, velX, velY, size) {
super(x, y, velX, velY, size)
}
draw(pctOpen) {
// Convert percent open to a float
var fltOpen = pctOpen / 100;
// An arc which stops at a specific percent to allow for the
// open mouth to be drawn
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, (fltOpen * 0.2) * Math.PI, (2 - fltOpen * 0.2) * Math.PI);
// The line leading back to the center and then closing the
// path to finish the open mouth.
ctx.lineTo(this.x, this.y);
ctx.closePath();
// Fill pacman's head yellow
ctx.fillStyle = "#FF0";
ctx.fill();
// Outline the head
ctx.strokeStyle = '#000';
ctx.stroke();
// A circle for the eye
var angle = Math.PI * (0.3 + fltOpen * 0.2),
xDelta = (this.x + (this.size / 4)),
yDelta = (this.y - (this.size / 2));
ctx.beginPath();
ctx.arc(xDelta, yDelta, this.size / 5, 0, 2 * Math.PI);
ctx.fillStyle = "#000";
ctx.fill();
// Outline the eye
ctx.strokeStyle = '#FFF';
ctx.stroke();
}
checkBounds() {
if ((this.x + this.size) >= width) {
this.x = width - this.size;
}
if ((this.x - this.size) <= 0) {
this.x = this.size;
}
if ((this.y + this.size) >= height) {
this.y = height - this.size;
}
if ((this.y - this.size) <= 0) {
this.y = this.size;
}
}
setControls() {
this.x = window.event.clientX;
this.y = window.event.clientY;
}
setControls2() {
this.x = Touch.clientX;
this.y = Touch.clientY;
}
collisionDetect() {
for (let j = 0; j < balls.length; j++) {
if (!(this === balls[j]) && balls[j].exists) {
const dx = this.x - balls[j].x;
const dy = this.y - balls[j].y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < this.size + balls[j].size) {
balls[j].exists = false;
this.size += balls[j].size;
balls.splice(j, 1);
Audio1.play()
}
}
}
}
}
let EvilCircle1 = new EvilCircle(15, 15, 10, 10, 10);
document.querySelector('canvas').onmousemove = function () { EvilCircle1.setControls() };
document.querySelector('canvas').addEventListener("touchstart", EvilCircle1.setControls2())
document.querySelector('canvas').addEventListener("touchmove", EvilCircle1.setControls2())
document.querySelector('canvas').addEventListener("touchend", EvilCircle1.setControls2())
function loop() {
ctx.fillStyle = 'rgba(0, 0, 0, 0.25)';
ctx.fillRect(0, 0, width, height);
for (let i = 0; i < balls.length; i++) {
balls[i].draw();
balls[i].update();
balls[i].collisionDetect();
}
EvilCircle1.draw(pctOpen += dir);
if (pctOpen % 100 == 0) {
dir = -dir;
}
EvilCircle1.checkBounds();
EvilCircle1.collisionDetect()
requestAnimationFrame(loop);
}
loop();
html, body {
margin: 0;
cursor: none;
}
html {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
height: 100%;
}
body {
overflow: hidden;
height: inherit;
}
h1 {
font-size: 22;
letter-spacing: -1px;
position: absolute;
margin: 0;
top: 20px;
left: 42.5%;
color: white;
}
<!DOCTYPE html>
<html>
<head>
<link rel="shortcut icon" href="#">
<meta charset="utf-8">
<title>Bouncing balls</title>
<link rel="stylesheet" href="egee.css">
</head>
<body>
<h1>Are you high?</h1>
<canvas></canvas>
<script src="eg.js"></script>
</body>
</html>
This:
document.querySelector('canvas').addEventListener("touchstart", EvilCircle1.setControls2())
Is calling the value returned by EvilCircle1.setControls(), not the function. You should instead be using:
document.querySelector('canvas').addEventListener("touchstart", EvilCircle1.setControls2)
// setup canvas
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const width = canvas.width = window.innerWidth;
const height = canvas.height = window.innerHeight;
let Audio1 = new Audio("a.wav")
// function to generate random number
function random(min, max) {
const num = Math.floor(Math.random() * (max - min + 1)) + min;
return num;
}
class Shape {
constructor(x, y, velX, velY, size, color, exists) {
this.x = x;
this.y = y;
this.velX = velX;
this.velY = velY;
this.color = color;
this.size = size;
this.exists = exists;
}
}
class Ball extends Shape {
constructor(x, y, velX, velY, size, color, exists) {
super(x, y, velX, velY, size, color, exists)
}
draw() {
ctx.beginPath();
ctx.fillStyle = this.color;
ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
ctx.fill();
}
update() {
if ((this.x + this.size) >= width) {
this.velX = -(this.velX);
}
if ((this.x - this.size) <= 0) {
this.velX = -(this.velX);
}
if ((this.y + this.size) >= height) {
this.velY = -(this.velY);
}
if ((this.y - this.size) <= 0) {
this.velY = -(this.velY);
}
this.x += this.velX;
this.y += this.velY;
}
collisionDetect() {
for (let j = 0; j < balls.length; j++) {
if (!(this === balls[j]) && balls[j].exists) {
const dx = this.x - balls[j].x;
const dy = this.y - balls[j].y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < this.size + balls[j].size) {
balls[j].color = 'rgb(' + random(0, 255) + ',' + random(0, 255) + ',' + random(0, 255) + ')';
this.color = 'rgb(' + random(0, 255) + ',' + random(0, 255) + ',' + random(0, 255) + ')';
this.velX = -(this.velX) + 1;
this.velY = -(this.velY);
balls[j].velX = (balls[j].velX);
balls[j].velY = (balls[j].velY)
}
}
}
}
}
let balls = [];
let i = balls.length;
for (i = 0; i < 10; i++) {
let size = random(2, 10);
let ball = new Ball(
// ball position always drawn at least one ball width
// away from the edge of the canvas, to avoid drawing errors
random(0 + size, width - size),
random(0 + size, height - size),
random(-9, 9),
random(-9, 9),
size,
'rgb(' + random(0, 255) + ',' + random(0, 255) + ',' + random(0, 255) + ')', true
);
balls.push(ball);
}
let dir = -10;
var pctOpen = 100;
class EvilCircle extends Shape {
constructor(x, y, velX, velY, size) {
super(x, y, velX, velY, size)
}
draw(pctOpen) {
// Convert percent open to a float
var fltOpen = pctOpen / 100;
// An arc which stops at a specific percent to allow for the
// open mouth to be drawn
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, (fltOpen * 0.2) * Math.PI, (2 - fltOpen * 0.2) * Math.PI);
// The line leading back to the center and then closing the
// path to finish the open mouth.
ctx.lineTo(this.x, this.y);
ctx.closePath();
// Fill pacman's head yellow
ctx.fillStyle = "#FF0";
ctx.fill();
// Outline the head
ctx.strokeStyle = '#000';
ctx.stroke();
// A circle for the eye
var angle = Math.PI * (0.3 + fltOpen * 0.2),
xDelta = (this.x + (this.size / 4)),
yDelta = (this.y - (this.size / 2));
ctx.beginPath();
ctx.arc(xDelta, yDelta, this.size / 5, 0, 2 * Math.PI);
ctx.fillStyle = "#000";
ctx.fill();
// Outline the eye
ctx.strokeStyle = '#FFF';
ctx.stroke();
}
checkBounds() {
if ((this.x + this.size) >= width) {
this.x = width - this.size;
}
if ((this.x - this.size) <= 0) {
this.x = this.size;
}
if ((this.y + this.size) >= height) {
this.y = height - this.size;
}
if ((this.y - this.size) <= 0) {
this.y = this.size;
}
}
setControls() {
this.x = window.event.clientX;
this.y = window.event.clientY;
}
setControls2() {
this.x = Touch.clientX;
this.y = Touch.clientY;
}
collisionDetect() {
for (let j = 0; j < balls.length; j++) {
if (!(this === balls[j]) && balls[j].exists) {
const dx = this.x - balls[j].x;
const dy = this.y - balls[j].y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < this.size + balls[j].size) {
balls[j].exists = false;
this.size += balls[j].size;
balls.splice(j, 1);
Audio1.play()
}
}
}
}
}
let EvilCircle1 = new EvilCircle(15, 15, 10, 10, 10);
document.querySelector('canvas').onmousemove = function() {
EvilCircle1.setControls()
};
document.querySelector('canvas').addEventListener("touchstart", EvilCircle1.setControls2)
document.querySelector('canvas').addEventListener("touchmove", EvilCircle1.setControls2)
document.querySelector('canvas').addEventListener("touchend", EvilCircle1.setControls2)
function loop() {
ctx.fillStyle = 'rgba(0, 0, 0, 0.25)';
ctx.fillRect(0, 0, width, height);
for (let i = 0; i < balls.length; i++) {
balls[i].draw();
balls[i].update();
balls[i].collisionDetect();
}
EvilCircle1.draw(pctOpen += dir);
if (pctOpen % 100 == 0) {
dir = -dir;
}
EvilCircle1.checkBounds();
EvilCircle1.collisionDetect()
requestAnimationFrame(loop);
}
loop();
html,
body {
margin: 0;
cursor: none;
}
html {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
height: 100%;
}
body {
overflow: hidden;
height: inherit;
}
h1 {
font-size: 22;
letter-spacing: -1px;
position: absolute;
margin: 0;
top: 20px;
left: 42.5%;
color: white;
}
<!DOCTYPE html>
<html>
<head>
<link rel="shortcut icon" href="#">
<meta charset="utf-8">
<title>Bouncing balls</title>
<link rel="stylesheet" href="egee.css">
</head>
<body>
<h1>Are you high?</h1>
<canvas></canvas>
<script src="eg.js"></script>
</body>
</html>
Basically I want background to be seen between rects. Right now I'm trying to leave a distance which is dependable on rect size between them, can I make that size to be exact number of pixels?
Because right now these empty spaces merge, sometimes are not seen at all, etc.
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
var canvas = document.getElementById("posHourCanvas");
var context = canvas.getContext('2d');
var boxes = [];
function init() {
context.clearRect(0, 0, canvas.width, canvas.height);
boxes.length = 0;
const strokeWidth = 0.2;
canvas.width = $('#two')[0].clientWidth;
var cellSize = canvas.width / 27;
canvas.height = 7 / 27 * canvas.width;
var x = y = 0;
draw(x, y, canvas.width, canvas.height, cellSize, strokeWidth);
}
function Box(x, y, day, hour) {
this.x = x;
this.y = y;
this.day = day;
this.hour = hour;
}
function draw(x, y, w, h, cellSize, strokeWidth) {
let onePixel = cellSize * strokeWidth;
cellSize = cellSize * (1 - strokeWidth);
context.beginPath();
context.lineWidth = 0.01;
context.strokeStyle = 'rgba(255, 255, 255, 0)';
for (let i = 0; i < 7; i++) {
context.beginPath();
dayRect(context, cellSize, i, onePixel);
let startX = 3 * cellSize + onePixel;
for (let j = 0; j < 25; j++) {
context.rect(startX, i * cellSize + i * onePixel, cellSize, cellSize);
context.fillStyle = "white";
context.fill();
startX += cellSize + onePixel;
}
}
}
function dayRect(context, cellSize, day, onePixel) {
var offSet = day * onePixel;
context.rect(0, day * cellSize + offSet, 3 * cellSize, cellSize);
context.fillStyle = "white";
context.fill();
}
init();
window.addEventListener("resize", init, false);
body {
margin: auto;
color: white;
background-color: black;
}
div#two {
margin-left: 5%;
width: 10%;
height: 30%;
}
<script src="https://code.jquery.com/jquery-3.3.1.js" integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60=" crossorigin="anonymous"></script>
<div id="parent">
<div>text above</div>
<div id="two">
<canvas id="posHourCanvas" width="600" height="300"></canvas>
</div>
<div>text under</div>
</div>
JSFIDDLE: http://jsfiddle.net/kgbh9352/
I have a canvas demo that spawns some circles. I can't seem to figure out how to have it be responsive in setting either canvas.width or canvas.style.width (what's the difference here?) on a windows resize using window.innerWidth.
Code works fine but it rerenders strangely on smaller viewports. I tried adding this snippet of code at the bottom of my javascript file, but it broke animation.
// ADDING THESE LINES PREVENTS ANIMATION FROM RUNNING
if (window.innerWidth < 1000) {
canvas.width = window.innerWidth;
} else {
canvas.width = 1000;
}
var canvas = document.querySelector("canvas");
canvas.width = 1000;
canvas.height = 100;
var c = canvas.getContext("2d");
// Constructor Function (object blueprint)
function Circle(x, y, dx, dy, radius, counter) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.radius = radius;
this.counter = counter;
this.draw = function() {
c.beginPath();
c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
c.strokeStyle = "white";
c.stroke();
c.fillStyle = "white";
c.fill();
};
this.update = function() {
if (this.y + this.radius > canvas.height) {
this.y = 0;
}
this.x += this.dx;
this.y += this.dy;
this.draw();
};
}
// Initialize array to store snow objects
var circleArray = [];
// Initialize objects with constructor
for (var i = 0; i < 50; i++) {
var radius = 1 + Math.random() * 5;
var x = Math.random() * canvas.width;
var y = 0 - Math.random() * 50; // start at top, render some circles off screen
var dx = (Math.random() - 0.5) * 2;
var dy = 0.5 + Math.random() * 0.5; // use gravity
circleArray.push(new Circle(x, y, dx, dy, radius, 0));
}
function animate() {
requestAnimationFrame(animate); // recurisvely run
c.clearRect(0, 0, innerWidth, innerHeight); // erases previously drawn content
for (var i = 0; i < circleArray.length; i++) {
circleArray[i].update();
}
// ADDING THESE LINES PREVENTS ANIMATION FROM RUNNING
//if (window.innerWidth < 1000) {
// canvas.width = window.innerWidth;
// } else {
// canvas.width = 1000;
// }
}
animate();
body {
background-color: grey;
display: flex;
justify-content: center;
}
.wrapper {
position: relative;
width: 1000px;
height: 110px;
min-height: 110px;
margin-top: 50vh;
}
.wrapper > * {
width: 1000px;
position: absolute;
}
canvas {
position: absolute;
}
img {
width: 100%;
height: 110px;
}
<div class="wrapper">
<img class="stars" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/867725/stars.png
" alt="" />
<img class="tress" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/867725/trees.png
" alt="" />
<img class="clouds" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/867725/clouds.png" alt="" />
<canvas></canvas>
</div>
The difference between canvas.width and canvas.style.width is that canvas.width specifies the actual size of the canvas in pixels, while canvas.style.width stretches or compresses the canvas to the width you specify. This will set the canvas width to 300px:
canvas.width = 300;
This will also set the canvas width to 300px, but will stretch it until it reaches 600px:
canvas.width = 300;
canvas.style.width = "600px";
Using canvas.width is better practice.
For the animation to run you must first fix the canvas width. Putting the code that resizes the canvas at the beginning of the animate() function solves the problem:
var canvas = document.querySelector("canvas");
canvas.width = 1000;
canvas.height = 100;
var c = canvas.getContext("2d");
// Constructor Function (object blueprint)
function Circle(x, y, dx, dy, radius, counter) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.radius = radius;
this.counter = counter;
this.draw = function() {
c.beginPath();
c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
c.strokeStyle = "white";
c.stroke();
c.fillStyle = "white";
c.fill();
};
this.update = function() {
if (this.y + this.radius > canvas.height) {
this.y = 0;
}
this.x += this.dx;
this.y += this.dy;
this.draw();
};
}
// Initialize array to store snow objects
var circleArray = [];
// Initialize objects with constructor
for (var i = 0; i < 50; i++) {
var radius = 1 + Math.random() * 5;
var x = Math.random() * canvas.width;
var y = 0 - Math.random() * 50; // start at top, render some circles off screen
var dx = (Math.random() - 0.5) * 2;
var dy = 0.5 + Math.random() * 0.5; // use gravity
circleArray.push(new Circle(x, y, dx, dy, radius, 0));
}
function animate() {
if (window.innerWidth < 1000) {
canvas.width = window.innerWidth;
} else {
canvas.width = 1000;
}
requestAnimationFrame(animate); // recurisvely run
c.clearRect(0, 0, innerWidth, innerHeight); // erases previously drawn content
for (var i = 0; i < circleArray.length; i++) {
circleArray[i].update();
}
}
animate();
body {
background-color: grey;
display: flex;
justify-content: center;
}
.wrapper {
position: relative;
width: 1000px;
height: 110px;
min-height: 110px;
margin-top: 50vh;
}
.wrapper>* {
width: 1000px;
position: absolute;
}
canvas {
position: absolute;
}
img {
width: 100%;
height: 110px;
}
<div class="wrapper">
<img class="stars" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/867725/stars.png
" alt="" />
<img class="tress" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/867725/trees.png
" alt="" />
<img class="clouds" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/867725/clouds.png" alt="" />
<canvas></canvas>
</div>
I would like to remove the balls already generated in the canvas on the click and decrease the counter on the bottom, but my function does not work. Here is my code concerning the part of the ball removal.
Is it possible to use a div to get the same result and to facilitate the removal of the balls? thank you
ball.onclick = function removeBalls(event) {
var x = event.clientX;
var y = event.clientY;
ctx.clearRect(x, y, 100, 50);
ctx.fillStyle = "#000000";
ctx.font = "20px Arial";
ctx.fillText("Balls Counter: " + balls.length - 1, 10, canvas.height - 10);
}
below I enclose my complete code
// GLOBAL VARIBLES
var gravity = 4;
var forceFactor = 0.3; //0.3 0.5
var mouseDown = false;
var balls = []; //hold all the balls
var mousePos = []; //hold the positions of the mouse
var ctx = canvas.getContext('2d');
var heightBrw = canvas.height = window.innerHeight;
var widthBrw = canvas.width = window.innerWidth;
var bounciness = 1; //0.9
window.onload = function gameCore() {
function onMouseDown(event) {
mouseDown = true;
mousePos["downX"] = event.pageX;
mousePos["downY"] = event.pageY;
}
canvas.onclick = function onMouseUp(event) {
mouseDown = false;
balls.push(new ball(mousePos["downX"], mousePos["downY"], (event.pageX - mousePos["downX"]) * forceFactor,
(event.pageY - mousePos["downY"]) * forceFactor, 5 + (Math.random() * 10), bounciness, random_color()));
ball
}
function onMouseMove(event) {
mousePos['currentX'] = event.pageX;
mousePos['currentY'] = event.pageY;
}
function resizeWindow(event) {
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
}
function reduceBounciness(event) {
if (bounciness == 1) {
for (var i = 0; i < balls.length; i++) {
balls[i].b = bounciness = 0.9;
document.getElementById("btn-bounciness").value = "⤽ Bounciness";
}
} else {
for (var i = 0; i < balls.length; i++) {
balls[i].b = bounciness = 1;
document.getElementById("btn-bounciness").value = " ⤼ Bounciness";
}
}
return bounciness;
}
function reduceSpeed(event) {
for (var i = 0; i < balls.length; i++) {
balls[i].vx = velocityX = 20 + c;
balls[i].vy = velocityY = 20 + c;
}
}
function speedUp(event) {
for (var i = 0; i < balls.length; i++) {
balls[i].vx = velocityX = 120 + c;
balls[i].vy = velocityY = 120 + c;
}
}
function stopGravity(event) {
if (gravity == 4) {
for (var i = 0; i < balls.length; i++) {
balls[i].g = gravity = 0;
balls[i].vx = velocityX = 0;
balls[i].vy = velocityY = 0;
document.getElementById("btn-gravity").value = "►";
}
} else {
for (var i = 0; i < balls.length; i++) {
balls[i].g = gravity = 4;
balls[i].vx = velocityX = 100;
balls[i].vy = velocityY = 100;
document.getElementById("btn-gravity").value = "◾";
}
}
}
ball.onclick = function removeBalls(event) {
var x = event.clientX;
var y = event.clientY;
ctx.clearRect(x, y, 100, 50);
ctx.fillStyle = "#000000";
ctx.font = "20px Arial";
ctx.fillText("Balls Counter: " + balls.length - 1, 10, canvas.height - 10);
}
document.getElementById("btn-gravity").addEventListener("click", stopGravity);
document.getElementById("btn-speed-up").addEventListener("click", speedUp);
document.getElementById("btn-speed-down").addEventListener("click", reduceSpeed);
document.getElementById("btn-bounciness").addEventListener("click", reduceBounciness);
document.addEventListener("mousedown", onMouseDown);
document.addEventListener("mousemove", onMouseMove);
window.addEventListener('resize', resizeWindow);
}
// GRAPHICS CODE
function circle(x, y, r, c) { // x position, y position, r radius, c color
//draw a circle
ctx.beginPath(); //approfondire
ctx.arc(x, y, r, 0, Math.PI * 2, true);
ctx.closePath();
//fill
ctx.fillStyle = c;
ctx.fill();
//stroke
ctx.lineWidth = r * 0.1; //border of the ball radius * 0.1
ctx.strokeStyle = "#000000"; //color of the border
ctx.stroke();
}
function random_color() {
var letter = "0123456789ABCDEF".split(""); //exadecimal value for the colors
var color = "#"; //all the exadecimal colors starts with #
for (var i = 0; i < 6; i++) {
color = color + letter[Math.round(Math.random() * 15)];
}
return color;
}
function selectDirection(fromx, fromy, tox, toy) {
ctx.beginPath();
ctx.moveTo(fromx, fromy);
ctx.lineTo(tox, toy);
ctx.moveTo(tox, toy);
}
//per velocità invariata rimuovere bounciness
function draw_ball() {
this.vy = this.vy + gravity * 0.1; // v = a * t
this.x = this.x + this.vx * 0.1; // s = v * t
this.y = this.y + this.vy * 0.1;
if (this.x + this.r > canvas.width) {
this.x = canvas.width - this.r;
this.vx = this.vx * -1 * this.b;
}
if (this.x - this.r < 0) {
this.x = this.r;
this.vx = this.vx * -1 * this.b;
}
if (this.y + this.r > canvas.height) {
this.y = canvas.height - this.r;
this.vy = this.vy * -1 * this.b;
}
if (this.y - this.r < 0) {
this.y = this.r;
this.vy = this.vy * 1 * this.b;
}
circle(this.x, this.y, this.r, this.c);
}
// OBJECTS
function ball(positionX, positionY, velosityX, velosityY, radius, bounciness, color, gravity) {
this.x = positionX;
this.y = positionY;
this.vx = velosityX;
this.vy = velosityY;
this.r = radius;
this.b = bounciness;
this.c = color;
this.g = gravity;
this.draw = draw_ball;
}
// GAME LOOP
function game_loop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (mouseDown == true) {
selectDirection(mousePos['downX'], mousePos['downY'], mousePos['currentX'], mousePos['currentY']);
}
for (var i = 0; i < balls.length; i++) {
balls[i].draw();
}
ctx.fillStyle = "#000000";
ctx.font = "20px Arial";
ctx.fillText("Balls Counter: " + balls.length, 10, canvas.height - 10);
}
setInterval(game_loop, 10);
* {
margin: 0px; padding: 0px;
}
html, body {
width: 100%; height: 100%;
}
#canvas {
display: block;
height: 95%;
border: 2px solid black;
width: 98%;
margin-left: 1%;
}
#btn-speed-up, #btn-speed-down, #btn-bounciness, #btn-gravity{
padding: 0.4%;
background-color: beige;
text-align: center;
font-size: 20px;
font-weight: 700;
float: right;
margin-right: 1%;
margin-top:0.5%;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Power Balls</title>
<link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body>
<canvas id="canvas"></canvas>
<input type="button" value="⤼ Bounciness" id="btn-bounciness"></input>
<input type="button" onclick="c+=10" value="+ Speed" id="btn-speed-up"></input>
<input type="button" value="◾" id="btn-gravity"></input>
<input type="button" onclick="c+= -10" value=" - Speed" id="btn-speed-down"></input>
<script src="js/main.js"></script>
</body>
</html>
I see two problems:
ball.onclick will not get triggered, as ball it is not a DOM object. Instead, you need to work with canvas.onclick. Check whether the user clicked on a ball or on empty space to decide whether to delete or create a ball.
To delete the ball, ctx.clearRect is not sufficient. This will clear the ball from the currently drawn frame, but the object still remains in the array balls and will therefore be drawn again in the next frame via balls[i].draw();. Instead, you need to remove the clicked ball entirely from the array, e. g. by using splice.
Otherwise, nice demo! :)
This cannot be done because the canvas is an immediate mode API. All you can do is draw over the top but this is not reliable.
https://en.wikipedia.org/wiki/Immediate_mode_(computer_graphics)
You will have to store each item in a separate data structure, then re-draw whenever a change occurs. In your case there is an array of balls, so you should remove the one that was clicked, before redrawing the entire array.
Each time you remove something, clear the canvas then re-draw each ball.
There are optimisations you can make if this is too slow.
So I have a simple canvas animation I want to start and stop using a toggle switch. The animation works fine and starts up, but the clearTimeout does not seem to work.
I made sure the variable "timeOut" is defined globally so it should have access to it, that seemed to be the usual advice in past questions similar to this one. Am I make a mistake somewhere else? Tried it on Firefox, Chrome and Chromium.
let timeOut;
function circles() {
let canvas = document.querySelector('canvas');
canvas.width = document.querySelector('canvas').offsetWidth;
canvas.height = document.querySelector('canvas').offsetHeight;
let c = canvas.getContext('2d');
let topRadius = 30;
let colorArray = [
'#ff0000',
'#00ff00',
'#0000ff',
]
let mouse = {
x: undefined,
y: undefined
}
window.addEventListener('mousemove', function(e) {
mouse.x = e.x;
mouse.y = e.y;
})
window.addEventListener('resize', function() {
canvas.width = document.querySelector('canvas').offsetWidth;
canvas.height = document.querySelector('canvas').offsetHeight;
init();
});
function Circle(x, y, dx, dy, radius) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dx;
this.radius = radius;
this.minRadius = radius;
this.color = colorArray[Math.floor(Math.random() * colorArray.length)];
this.draw = function() {
if (this.radius < 0) {
this.radius = this.minRadius;
}
c.beginPath();
c.arc(this.x, this.y, this.radius, 0, Math.PI*2, false);
c.fillStyle = this.color;
c.fill();
}
this.update = function() {
if (this.y + this.radius > innerHeight || this.y - this.radius <= 0) {
this.dy = -this.dy;
}
if (this.x + this.radius > canvas.width|| this.x - this.radius <= 0) {
this.dx = -this.dx;
}
this.x+=this.dx;
this.y+=this.dy;
if (mouse.x - this.x < 50 && mouse.x - this.x > -50 && mouse.y - this.y < 50 && mouse.y - this.y > -50) {
if (this.radius < topRadius) {
this.radius += 1;
}
} else if (this.radius > this.minRadius){
this.radius -= 1;
}
this.draw();
}
}
let circleArray = [];
function init() {
circleArray = [];
for (let i =0; i<50; i++) {
let radius = Math.random() * 10;
let x = Math.random() * (canvas.width - radius*2) + radius;
let y = Math.random() * (canvas.height - radius * 2) + radius;
let dx = (Math.random() - 0.5) * 4;
let dy = (Math.random() - 0.5 )* 4;
circleArray.push(new Circle(x, y, dx, dy, radius));
}
}
function animate() {
requestAnimationFrame(animate);
c.clearRect(0,0, canvas.width, innerHeight);
for(let i=0; i < circleArray.length; i++) {
circleArray[i].update();
}
}
init();
animate();
}
$('#startstop').click(function() {
if(!timeOut) {
timeOut = setTimeout(circles, 1000);
} else {
clearTimeout(timeOut);
timeOut = null;
}
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div style="display: flex; justify-content: space-around;">
<canvas style="width: 100px; height: 800px; background: red;"></canvas>
<div id="startstop">toggle</div>
</div>
You were super close to the solution ;)
in the function animate add line #3 (before for loop), that is having this value:
if (!timeOut) return;
Good luck!
This animation is not stopping because you call clearTimeout on your setTimeout but that only runs once. Your issue is further into the animate function. This relatively loops the requestAnimationFrame. You can store this as a request and then cancelAnimationFrame