Aim triangle point towards center of circle while moving - javascript

I want the triangle to aim towards the center at all time while it is spinning as it is right now
var element = document.getElementById('background');
var ctx = element.getContext("2d");
var camera = {};
camera.x = 0;
camera.y = 0;
var scale = 1.0;
var obj = [];
var t = {};
t.angle = Math.random() * Math.PI * 2; //start angle
t.radius = 200;
t.x = Math.cos(t.angle) * t.radius; // start position x
t.y = Math.sin(t.angle) * t.radius; //start position y
t.duration = 10000; //10 seconds per rotation
t.rotSpeed = 2 * Math.PI / t.duration; // Rotational speed (in radian/ms)
t.start = Date.now();
obj.push(t);
function update() {
for (var i = 0; i < obj.length; i++) {
var delta = Date.now() - obj[i].start;
obj.start = Date.now();
var angle = obj[i].rotSpeed * delta;
// The angle is now already in radian, no longer need to convert from degree to radian.
obj[i].x = obj[i].radius * Math.cos(angle);
obj[i].y = obj[i].radius * Math.sin(angle);
}
}
function draw() {
update();
ctx.clearRect(0, 0, element.width, element.height);
ctx.save();
ctx.translate(0 - (camera.x - element.width / 2), 0 - (camera.y - element.height / 2));
ctx.scale(scale, scale);
ctx.fillStyle = 'blue';
for (var i = 0; i < obj.length; i++) {
/*Style circle*/
ctx.beginPath();
ctx.arc(0, 0, obj[i].radius, 0, Math.PI * 2);
ctx.lineWidth = 60;
ctx.strokeStyle = "black";
ctx.stroke();
//Dot style
ctx.beginPath();
ctx.arc(obj[i].x,obj[i].y,10,0,Math.PI*2);
ctx.moveTo(0 + obj[i].x, 0 + obj[i].y);
ctx.lineTo(75 + obj[i].x, 25 + obj[i].y);
ctx.lineTo(75 + obj[i].x, -25 + + obj[i].y);
ctx.lineWidth = 1.5;
ctx.strokeStyle = "red";
ctx.stroke();
ctx.fillStyle = "red";
ctx.fill();
}
ctx.restore();
requestAnimationFrame(draw);
}
draw();
<canvas id="background" width="500" height="500"></canvas>
Here is a quick fiddle that has all the code already in it just for convenience :)
https://jsfiddle.net/4xwmo5tj/5/
I have already made it spin (the dot is there for now but will be removed later so that can be ignored) the only thing that I still need to do is aim it towards the center at all time. I think I have to use css transform translate for it. but I don't know how to

Use CSS animations.
.outer-circle {
height: 200px;
width: 200px;
background-color: black;
padding: 25px;
position: relative;
border-radius: 50%;
-webkit-animation:spin 4s linear infinite;
-moz-animation:spin 4s linear infinite;
animation:spin 4s linear infinite;
}
.inner-cricle {
width: 150px;
height: 150px;
background: white;
margin: auto;
margin-top: 25px;
border-radius: 50%;
}
.triangle {
width: 0;
height: 0;
border-left: 20px solid transparent;
border-right: 20px solid transparent;
border-top: 20px solid #f00;
position: absolute;
left: 40%;
top: 6%;
}
#-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } }
#-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } }
#keyframes spin { 100% { -webkit-transform: rotate(360deg); transform:rotate(360deg); } }
<div class="outer-circle">
<div class="triangle"></div><div class="inner-cricle"></div>
</div>

The triangle is not spinning at all. There is just a red dot circle in circumference and triangle inside black circle which is still. So what do you want to do?

The block of the code responsible for the triangle is under // Dot Style. The lines ctx.lineTo plot two dots at (x,y) coordinates. I fiddle around with the two coordinates until the triangle was facing down. Below is the end result:
var element = document.getElementById('background');
var ctx = element.getContext("2d");
var camera = {};
camera.x = 0;
camera.y = 0;
var scale = 1.0;
var obj = [];
var t = {};
t.angle = Math.random() * Math.PI * 2; //start angle
t.radius = 200;
t.x = Math.cos(t.angle) * t.radius; // start position x
t.y = Math.sin(t.angle) * t.radius; //start position y
t.duration = 10000; //10 seconds per rotation
t.rotSpeed = 2 * Math.PI / t.duration; // Rotational speed (in radian/ms)
t.start = Date.now();
obj.push(t);
function update() {
for (var i = 0; i < obj.length; i++) {
var delta = Date.now() - obj[i].start;
obj.start = Date.now();
var angle = obj[i].rotSpeed * delta;
// The angle is now already in radian, no longer need to convert from degree to radian.
obj[i].x = obj[i].radius * Math.cos(angle);
obj[i].y = obj[i].radius * Math.sin(angle);
}
}
function draw() {
update();
ctx.clearRect(0, 0, element.width, element.height);
ctx.save();
ctx.translate(0 - (camera.x - element.width / 2), 0 - (camera.y - element.height / 2));
ctx.scale(scale, scale);
ctx.fillStyle = 'blue';
for (var i = 0; i < obj.length; i++) {
/*Style circle*/
ctx.beginPath();
ctx.arc(0, 0, obj[i].radius, 0, Math.PI * 2);
ctx.lineWidth = 60;
ctx.strokeStyle = "white";
ctx.stroke();
//Dot style
ctx.beginPath();
ctx.arc(obj[i].x, obj[i].y, 10, 0, Math.PI * 2);
ctx.moveTo(0 + obj[i].x, 0 + obj[i].y);
ctx.lineTo(25 + obj[i].x, -75 + obj[i].y);
ctx.lineTo(-25 + obj[i].x, -75 + obj[i].y);
ctx.lineWidth = 1.5;
ctx.strokeStyle = "red";
ctx.stroke();
ctx.fillStyle = "red";
ctx.fill();
}
ctx.restore();
requestAnimationFrame(draw);
}
draw();
#background {
background: #000000;
border: 1px solid black;
}
<canvas id="background" width="500" height="500"></canvas>
Sorry, I can't provide any more specific details. The Cavas API and drawing are not my turfs.

Related

Progress bar view without rotating around does not work

The following code is generating a circle bar by using canvas. Everything works fine.
But when I tried to change the code so that the percent circle and value view
dont rotate/circulate around until they reach the real position/value I run into
problems. The graph should only view the percentage position within the circle
and the value in the mittle of the circle without rotating/couting up to the real end value.
I know I have to change/delete some things in the JS arcMove() function like
deegres += 1 which is responsible for the rotation steps and so on, but if I tried it didnt work like it should.
window.onload = function() {
var can = document.getElementById('canvas'),
spanProcent = document.getElementById('procent'),
c = can.getContext('2d');
var posX = can.width / 2,
posY = can.height / 2,
fps = 1000 / 200,
procent = 0,
oneProcent = 360 / 100,
result = oneProcent * 64;
c.lineCap = 'round';
arcMove();
function arcMove(){
var deegres = 0;
var acrInterval = setInterval (function() {
deegres += 1;
c.clearRect( 0, 0, can.width, can.height );
procent = deegres / oneProcent;
spanProcent.innerHTML = procent.toFixed();
c.beginPath();
c.arc( posX, posY, 70, (Math.PI/180) * 270, (Math.PI/180) * (270 + 360) );
c.strokeStyle = '#b1b1b1';
c.lineWidth = '10';
c.stroke();
c.beginPath();
c.strokeStyle = '#3949AB';
c.lineWidth = '10';
c.arc( posX, posY, 70, (Math.PI/180) * 270, (Math.PI/180) * (270 + deegres) );
c.stroke();
if( deegres >= result ) clearInterval(acrInterval);
}, fps);
}
}
:root {
background: #fff;
}
span#procent {
display: block;
position: absolute;
left: 50%;
top: 50%;
font-size: 50px;
transform: translate(-50%, -50%);
color: #3949AB;
}
span#procent::after {
content: '%';
}
.canvas-wrap {
position: relative;
width: 300px;
height: 300px;
}
<div class="canvas-wrap">
<canvas id="canvas" width="300" height="300"></canvas>
<span id="procent"></span>
</div>
In the arcMove function you have to set degree to result so the interval is done on first call. But if you do not want an animation than you should use a pure css way of displaying it. Checkout the following answer: https://stackoverflow.com/a/41147560/8820118
window.onload = function() {
var can = document.getElementById('canvas'),
spanProcent = document.getElementById('procent'),
c = can.getContext('2d');
var posX = can.width / 2,
posY = can.height / 2,
fps = 1000 / 200,
procent = 0,
oneProcent = 360 / 100,
result = oneProcent * 64;
c.lineCap = 'round';
arcMove();
function arcMove(){
var deegres = result; // change degrees to result, than you won't have the animation.
var acrInterval = setInterval (function() {
deegres += 1;
c.clearRect( 0, 0, can.width, can.height );
procent = deegres / oneProcent;
spanProcent.innerHTML = procent.toFixed();
c.beginPath();
c.arc( posX, posY, 70, (Math.PI/180) * 270, (Math.PI/180) * (270 + 360) );
c.strokeStyle = '#b1b1b1';
c.lineWidth = '10';
c.stroke();
c.beginPath();
c.strokeStyle = '#3949AB';
c.lineWidth = '10';
c.arc( posX, posY, 70, (Math.PI/180) * 270, (Math.PI/180) * (270 + deegres) );
c.stroke();
if( deegres >= result ) clearInterval(acrInterval);
}, fps);
}
}
:root {
background: #fff;
}
span#procent {
display: block;
position: absolute;
left: 50%;
top: 50%;
font-size: 50px;
transform: translate(-50%, -50%);
color: #3949AB;
}
span#procent::after {
content: '%';
}
.canvas-wrap {
position: relative;
width: 300px;
height: 300px;
}
<div class="canvas-wrap">
<canvas id="canvas" width="300" height="300"></canvas>
<span id="procent"></span>
</div>

Adding Background on Canvas

So I have made some waves on a canvas. Now I would also like to add a background to make like a nice sunset with waves. But my problem occurs when I make ctx.fillRect to draw the background. As I need to clear the area around the bottom for the waves to work, the whole screen is being cleared. Therefore also clearing the background
whole code
var c = document.getElementById("screen2");
var ctx = c.getContext("2d");
var cw = c.width = window.innerWidth;
var ch = c.height = window.innerHeight;
var cx = cw / 2,
cy = ch / 2;
var rad = Math.PI / 180;
var w = window.innerWidth;
var h = 100;
var amplitude = h;
var frequency = .01;
var phi = 0;
var frames = 0;
var stopped = true;
var gradientSky = ctx.createLinearGradient(0, ch / 2, 0, ch);
gradientSky.addColorStop(0, "#C1274E");
gradientSky.addColorStop(0.25, "#C344B7");
gradientSky.addColorStop(0.5, "#B2244A");
gradientSky.addColorStop(0.75, "#B2244A");
gradientSky.addColorStop(1, "#B2244A");
ctx.fillStyle = gradientSky;
ctx.fillRect(0, 0, cw, ch);
var gradientA = ctx.createLinearGradient(0, ch / 2, 0, ch);
gradientA.addColorStop(0, "#7a88d9");
gradientA.addColorStop(1, "#5a6bd0");
var gradientB = ctx.createLinearGradient(0, ch / 2, 0, ch);
gradientB.addColorStop(0, "#646593");
gradientB.addColorStop(1, "#0f2460");
ctx.lineWidth = 4;
var step = 0;
function drawWaves() {
frames++;
phi = frames / 88;
ctx.clearRect(0, 0, cw, ch);
ctx.beginPath();
ctx.moveTo(0, ch);
for (var x = 0; x < w; x++) {
y = Math.sin(x * frequency + phi) * amplitude / 2 + amplitude / 2;
//y = Math.cos(x * frequency + phi) * amplitude / 2 + amplitude / 2;
ctx.lineTo(x, y + ch - 390 + Math.sin(step / 2) * 20); // setting it to the bottom of the page 100= lift
}
ctx.lineTo(w, ch);
ctx.lineTo(0, ch);
ctx.fillStyle = gradientA;
ctx.fill();
frames++;
phi = frames / 60;
ctx.beginPath();
ctx.moveTo(0, ch);
for (var x = 0; x < w; x++) {
y = Math.sin(x * frequency + phi) * amplitude / 4 + amplitude / 4;
//y = Math.cos(x * frequency + phi) * amplitude / 2 + amplitude / 2;
ctx.lineTo(x, y + ch - 380 + Math.sin(step) * 20); // setting it to the bottom of the page 100= lift
}
ctx.lineTo(w, ch);
ctx.lineTo(0, ch);
ctx.fillStyle = gradientB;
ctx.fill();
step += 0.02;
requestId = window.requestAnimationFrame(drawWaves);
}
requestId = window.requestAnimationFrame(drawWaves);
I'm pretty sure that has to do what x and y coordinates I inserted into the ctx.fillRect(); and ctx.clearRect();. But I have tried all variants I could think of but still nothing works. Sometimes I get the Background to appear but then the clear tag wont clear the waves properly. Just for better understanding the ch and cw are window.innerHeight and window.innerWidth although this can also be seen in the code.
The trick here is really simple. As most background images, your sunset also should fill the whole screen and will later be covered by the waves and ultimately the bubbles. This is how a painter would draw a painting. Since the background covers the whole canvas, you even don't need to clear it before because this is essentially being done by filling the canvas with the background.
So all you need to change is remove the clearReact() call and replace it by a fillRect(0,0,cw,ch) after setting the fillStyle to gradientSky.
Here's your modified example:
// Author:
// Name:
// URL: https://jsfiddle.net/7z2yh3pg/
function Blasen() {
const section = document.querySelector('#screen')
const createElement = document.createElement('spawn')
var size = Math.random() * 60;
createElement.style.width = 30 + size + 'px';
createElement.style.height = 30 + size + 'px';
createElement.style.left = Math.random() * innerWidth + "px";
section.appendChild(createElement);
setTimeout(() => {
createElement.remove()
}, 600)
}
const Blaseninterval = setInterval(Blasen, 100)
var c = document.getElementById("screen2");
var ctx = c.getContext("2d");
var cw = c.width = window.innerWidth;
var ch = c.height = window.innerHeight;
var cx = cw / 2,
cy = ch / 2;
var rad = Math.PI / 180;
var w = window.innerWidth;
var h = 100;
var amplitude = h;
var frequency = .01;
var phi = 0;
var frames = 0;
var stopped = true;
var gradientSky = ctx.createLinearGradient(0, 0, 0, ch);
gradientSky.addColorStop(0, "#C1274E");
gradientSky.addColorStop(0.25, "#C344B7");
gradientSky.addColorStop(0.5, "#B2244A");
gradientSky.addColorStop(0.75, "#B2244A");
gradientSky.addColorStop(1, "#B2244A");
ctx.fillStyle = gradientSky;
ctx.fill();
var gradientA = ctx.createLinearGradient(0, ch / 2, 0, ch);
gradientA.addColorStop(0, "#7a88d9");
gradientA.addColorStop(1, "#5a6bd0");
var gradientB = ctx.createLinearGradient(0, ch / 2, 0, ch);
gradientB.addColorStop(0, "#646593");
gradientB.addColorStop(1, "#0f2460");
ctx.lineWidth = 4;
var step = 0;
function drawWaves() {
frames++;
phi = frames / 88;
ctx.fillStyle = gradientSky;
ctx.beginPath();
ctx.rect(0, 0, cw, ch);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(0, ch);
for (var x = 0; x < w; x++) {
y = Math.sin(x * frequency + phi) * amplitude / 2 + amplitude / 2;
//y = Math.cos(x * frequency + phi) * amplitude / 2 + amplitude / 2;
ctx.lineTo(x, y + ch - 250 + Math.sin(step / 2) * 20); // setting it to the bottom of the page 100= lift
}
ctx.lineTo(w, ch);
ctx.lineTo(0, ch);
ctx.fillStyle = gradientA;
ctx.fill();
frames++;
phi = frames / 60;
ctx.beginPath();
ctx.moveTo(0, ch);
for (var x = 0; x < w; x++) {
y = Math.sin(x * frequency + phi) * amplitude / 4 + amplitude / 4;
//y = Math.cos(x * frequency + phi) * amplitude / 2 + amplitude / 2;
ctx.lineTo(x, y + ch - 240 + Math.sin(step) * 20); // setting it to the bottom of the page 100= lift
}
ctx.lineTo(w, ch);
ctx.lineTo(0, ch);
ctx.fillStyle = gradientB;
ctx.fill();
step += 0.02;
requestId = window.requestAnimationFrame(drawWaves);
}
requestId = window.requestAnimationFrame(drawWaves);
body,
html {
margin: 0;
padding: 0;
overflow: hidden;
}
canvas {
display: block;
margin: 0 auto;
width: 100%;
height: 100vh;
}
#screen {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
#screen spawn {
position: absolute;
bottom: -80px;
background: transparent;
border-radius: 50%;
pointer-events: none;
box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.5);
animation: animate 3s linear infinite;
}
#screen spawn:before {
content: '';
position: absolute;
width: 100%;
height: 100%;
transform: scale(0.25) translate(-70%, -70%);
background: radial-gradient(#fff, transparent);
opacity: 0.6;
border-radius: 50%;
}
#keyframes animate {
0% {
transform: translateY(0%);
opacity: 1;
}
99% {
opacity: 1;
}
100% {
transform: translateY(-2000%);
opacity: 0;
}
}
<canvas id="screen2"></canvas>
<div id="screen"></div>

js canvas object movement animation using pythagorean theorem

I am trying to make an object move to click position, like in rpgs like diablo or modern moba games, i needed to find a way to animate to click coordinates and i have found a method that uses pythagorean theorem, i understood and custimized the code to a certain degree, but there is a bug where the ball keeps bouncing in the very end of animation. I know this happens because of the "moves" variable and the loop, but can't understand exactly why.
here's the function that draws the object
function drawPlayer() {
//
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(player.x, player.y, 12, 0, Math.PI*2);
ctx.fillStyle = 'white';
ctx.fill();
ctx.closePath();
//Calculate variables
let dx = player.newx - player.x;
let dy = player.newy - player.y;
let distance = Math.sqrt(dx*dx + dy*dy);
let moves = distance/player.speed;
let xunits = (player.newx - player.x)/moves;
let yunits = (player.newy - player.y)/moves;
if (moves > 0 ) {
moves--;
player.x += xunits;
player.y += yunits;
console.log(moves);
}
}
const canvas = document.getElementById('canvas1');
const ctx = canvas.getContext('2d');
canvas.addEventListener('click', e => {
setClickCoords(e);
drawPlayer();
})
canvas.width = 300;
canvas.height = 300;
const player = {
x: 0,
y: 0,
newx: 0,
newy: 0,
speed: 1,
radius: 15,
}
function drawPlayer() {
//Draw
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(player.x, player.y, 12, 0, Math.PI * 2);
ctx.fillStyle = 'white';
ctx.fill();
ctx.closePath();
//Calculate variables
let dx = player.newx - player.x;
let dy = player.newy - player.y;
let distance = Math.sqrt(dx * dx + dy * dy);
let moves = distance / player.speed;
let xunits = (player.newx - player.x) / moves;
let yunits = (player.newy - player.y) / moves;
console.log(moves);
if (moves > 0) {
moves--;
player.x += xunits;
player.y += yunits;
console.log(moves);
}
}
function setClickCoords(e) {
player.newx = e.offsetX;
player.newy = e.offsetY;
document.getElementById('oldcoords').innerHTML = "old Coords " + player.x + " " + player.y;
document.getElementById('newcoords').innerHTML = "new Coords " + player.newx + " " + player.newy;
}
function gameLoop() {
window.setTimeout(gameLoop, 24);
drawPlayer()
}
gameLoop();
body {
background: black;
display: flex;
flex-direction: column;
}
#canvas1 {
border: 3px solid white;
top: 50%;
left: 50%;
position: absolute;
width: 300px;
height: 300px;
transform: translate(-50%, -50%);
}
#oldcoords,
#newcoords {
color: white;
font-size: 18px;
}
<span id="oldcoords"></span>
<span id="newcoords"></span>
<canvas id="canvas1"> </canvas>
Check if moves is between 0 and 1. If it's between, move the ball to the desired position. Currently, the ball goes too far in the direction and has to return in the next animation.
const canvas = document.getElementById('canvas1');
const ctx = canvas.getContext('2d');
canvas.addEventListener('click', e => {
setClickCoords(e);
drawPlayer();
})
canvas.width = 300;
canvas.height = 300;
const player = {
x: 0,
y: 0,
newx: 0,
newy: 0,
speed: 1,
radius: 15,
}
function drawPlayer() {
//Draw
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(player.x, player.y, 12, 0, Math.PI * 2);
ctx.fillStyle = 'white';
ctx.fill();
ctx.closePath();
//Calculate variables
let dx = player.newx - player.x;
let dy = player.newy - player.y;
let distance = Math.sqrt(dx * dx + dy * dy);
let moves = distance / player.speed;
console.log(moves);
if (moves > 1) {
let xunits = (player.newx - player.x) / moves;
let yunits = (player.newy - player.y) / moves;
moves--;
player.x += xunits;
player.y += yunits;
console.log(moves);
} else if (moves > 0) {
moves = 0;
player.x = player.newx;
player.y = player.newy;
console.log(moves);
}
}
function setClickCoords(e) {
player.newx = e.offsetX;
player.newy = e.offsetY;
document.getElementById('oldcoords').innerHTML = "old Coords " + player.x + " " + player.y;
document.getElementById('newcoords').innerHTML = "new Coords " + player.newx + " " + player.newy;
}
function gameLoop() {
window.setTimeout(gameLoop, 24);
drawPlayer()
}
gameLoop();
body {
background: black;
display: flex;
flex-direction: column;
}
#canvas1 {
border: 3px solid white;
top: 50%;
left: 50%;
position: absolute;
width: 300px;
height: 300px;
transform: translate(-50%, -50%);
}
#oldcoords,
#newcoords {
color: white;
font-size: 18px;
}
<span id="oldcoords"></span>
<span id="newcoords"></span>
<canvas id="canvas1"> </canvas>

Javascript Canvas - CSS-rotating canvas elements leaves empty spaces between -- circular menu

I'm making a menu for my Sudoku game to choose numbers for selected cells.
I managed to do a circular animated menu with X canvas elements each representing one number (+ one is empty). Then I rotate all these elements with CSS transform to create a perfect circle.
And here the problem appears - there are ugly transparent strokes between each element that I can't manage to remove.
This is how I draw it:
My app is a bit complicated but I managed to create a demo:
let parent = document.getElementsByClassName('menu')[0];
let elSize = parent.getBoundingClientRect().width;
let upscale = 2;
let total = 10;
let length = elSize / 2;
for (let i = 0; i < total; i++) {
// create new canvas
let val = document.createElement('canvas');
val.classList.add("value");
let deg = 360 / total;
//set sizes and rotation
val.height = length * upscale;
val.width = elSize * upscale;
val.style.width = elSize + "px";
val.style.height = length + "px";
val.style.setProperty("--rotation", (i / total * 360) + "deg");
// get context
let ctx = val.getContext("2d");
ctx.fillStyle = "blue";
ctx.imageSmoothingEnabled = true;
// full circle center
let center = {
x: length * upscale,
y: length * upscale
}
//function to fill the circle part (step 1 and 2 on the image)
const fillWedge = (cx, cy, radius, startAngle, endAngle, fillcolor, stroke = false) => {
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(cx, cy, radius, startAngle, endAngle);
ctx.closePath();
if (stroke) {
ctx.lineWidth = 1;
ctx.strokeStyle = fillcolor;
ctx.stroke();
} else {
ctx.fillStyle = fillcolor;
ctx.fill();
}
}
const degToAngle = (deg) => {
let start = -Math.PI / 2;
let fullCircle = Math.PI * 2;
return (start + fullCircle * (deg / 360));
}
ctx.save();
ctx.imageSmoothingEnabled = false;
ctx.globalCompositeOperation = "source-out"; {
//smaller circle
let cx = center.x;
let cy = center.y;
let radius = length * upscale / 3;
let startAngle = -((deg + 1) / 2) % 360;
let endAngle = ((deg + 1) / 2) % 360;
fillWedge(cx, cy, radius, degToAngle(startAngle), degToAngle(endAngle), ctx.fillStyle);
}
//make it semi-transparent
ctx.globalAlpha = 0.8; {
//bigger circle
let cx = center.x;
let cy = center.y;
let radius = length * upscale;
let startAngle = -(deg / 2) % 360;
let endAngle = (deg / 2) % 360;
fillWedge(cx, cy, radius, degToAngle(startAngle), degToAngle(endAngle), ctx.fillStyle);
}
ctx.restore();
//draw text
if (i !== 0) {
ctx.save();
ctx.translate(length * upscale, length / 3 * upscale);
ctx.rotate(-(i / total * 360) / 180 * Math.PI);
ctx.font = "600 " + (18 * upscale) + 'px Consolas';
ctx.textAlign = "center";
ctx.fillStyle = "white";
ctx.fillText((i) + "", 0, 5 * upscale);
ctx.restore()
}
//add element to menu
parent.appendChild(val);
}
html {
background: url(https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fcdn.cienradios.com%2Fwp-content%2Fuploads%2Fsites%2F13%2F2020%2F06%2FShrek-portada.jpg&f=1&nofb=1) no-repeat center;
display: flex;
justify-content: center;
align-content: center;
}
.menu {
width: 400px;
aspect-ratio: 1;
position: relative;
border-radius: 50%;
}
.menu .value {
--rotation: 0deg;
position: absolute;
top: 0;
bottom: 50%;
left: 50%;
transform-origin: bottom;
transform: translate(-50%, 0) rotate(var(--rotation));
}
p {
color: white;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Sudoku Test</title>
</head>
<body>
<div class="menu">
</div>
<p>
See, it's semi-transparent and you can clearly see lines between elements
<br>
<b>How to fix it?!</b>
</p>
</body>
</html>
Any way to remove those spaces? I know it's a bit too much detailed question, but I really can't manage to fix it. Thanks.
These are caused by antialiasing. Since you do draw on diagonals, the shape doesn't fall on full pixel boundaries. So to smooth the lines, the browser will make those pixels that should have been painted only partially, more transparent. When stacked these transparent pixels won't add up exactly to full opacity (0.5 opacity + 0.5 opacity = 0.75 opacity, not 1). So you'll see these lines.
Removing the smoothing here won't help, because the alternative would be to fill in a marching-square fashion, but that would result in either complete holes in some places, either in overlapping pixels, which would be visible since your shapes aren't fully opaque.
Usually the cheap trick for that issue is to stroke a couple of pixels around the shape in the same color as it's filled. But once again since your shapes are filled with semi-transparent colors this trick won't do it.
You could hack something around by drawing all your shapes at full opacity, and applying the transparency on a common container. But this means that your texts would need their own canvas, and their own container (otherwise they'd be transparent too).
let parent = document.getElementsByClassName('menu')[0];
const textsParent = document.getElementsByClassName('texts')[0];
let elSize = parent.getBoundingClientRect().width;
let upscale = 2;
let total = 10;
let length = elSize / 2;
for (let i = 0; i < total; i++) {
// create new canvas
let val = document.createElement('canvas');
val.classList.add("value");
let deg = 360 / total;
//set sizes and rotation
val.height = length * upscale;
val.width = elSize * upscale;
val.style.width = elSize + "px";
val.style.height = length + "px";
val.style.setProperty("--rotation", (i / total * 360) + "deg");
// get context
let ctx = val.getContext("2d");
ctx.fillStyle = "blue";
// full circle center
let center = {
x: length * upscale,
y: length * upscale
}
//function to fill the circle part (step 1 and 2 on the image)
const fillWedge = (cx, cy, radius, startAngle, endAngle, fillcolor, stroke = false) => {
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(cx, cy, radius, startAngle, endAngle);
ctx.closePath();
ctx.lineWidth = 2;
ctx.strokeStyle = fillcolor;
ctx.stroke();
ctx.fillStyle = fillcolor;
ctx.fill();
}
const degToAngle = (deg) => {
let start = -Math.PI / 2;
let fullCircle = Math.PI * 2;
return (start + fullCircle * (deg / 360));
}
ctx.save();
ctx.imageSmoothingEnabled = false;
{
//smaller circle
let cx = center.x;
let cy = center.y;
let radius = length * upscale / 3;
let startAngle = -((deg + 1) / 2) % 360;
let endAngle = ((deg + 1) / 2) % 360;
fillWedge(cx, cy, radius, degToAngle(startAngle), degToAngle(endAngle), ctx.fillStyle);
}
{
//bigger circle
let cx = center.x;
let cy = center.y;
let radius = length * upscale;
let startAngle = -(deg / 2) % 360;
let endAngle = (deg / 2) % 360;
fillWedge(cx, cy, radius, degToAngle(startAngle), degToAngle(endAngle), ctx.fillStyle);
}
ctx.restore();
//draw text
if (i !== 0) {
// we need a new canvas just for the text
const val2 = val.cloneNode();
const ctx = val2.getContext("2d");
ctx.save();
ctx.translate(length * upscale, length / 3 * upscale);
ctx.rotate(-(i / total * 360) / 180 * Math.PI);
ctx.font = "600 " + (18 * upscale) + 'px Consolas';
ctx.textAlign = "center";
ctx.fillStyle = "white";
ctx.fillText((i) + "", 0, 5 * upscale);
ctx.restore()
textsParent.appendChild(val2);
}
//add element to menu
parent.appendChild(val);
}
html {
background: url(https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fcdn.cienradios.com%2Fwp-content%2Fuploads%2Fsites%2F13%2F2020%2F06%2FShrek-portada.jpg&f=1&nofb=1) no-repeat center;
display: flex;
justify-content: center;
align-content: center;
}
.menu, .texts {
width: 400px;
aspect-ratio: 1;
position: relative;
border-radius: 50%;
}
.menu .value, .texts canvas {
--rotation: 0deg;
position: absolute;
top: 0;
bottom: 50%;
left: 50%;
transform-origin: bottom;
transform: translate(-50%, 0) rotate(var(--rotation));
}
.menu { opacity: 0.8 }
.text-container {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-content: center;
}
<div class="menu">
</div>
<div class="text-container">
<div class="texts">
</div>
</div>
So instead the best is probably to refactor your code entirely to use a single canvas instead. If you tell the browser to draw all your shapes in a single subpath, it will be able to place the tracing perfectly where it should be and will be able to draw all this without any line in between:
const canvas = document.querySelector("canvas");
canvas.width = canvas.height = 500;
const ctx = canvas.getContext("2d");
const parts = 10;
const theta = Math.PI / (parts / 2);
const trace = (cx, cy, r1, r2, t) => {
ctx.moveTo(cx + r2, cy);
ctx.lineTo(cx + r1, cy);
ctx.arc(cx, cy, r1, 0, t);
ctx.arc(cx, cy, r2, t, 0, true);
};
ctx.translate(250, 250);
ctx.rotate(-Math.PI / 2 - theta);
// draw all but the last part
for (let i = 0; i < parts - 1; i++) {
ctx.rotate(theta);
trace(0, 0, 50, 200, theta);
}
ctx.globalAlpha = 0.8;
ctx.fillStyle = "blue";
ctx.fill(); // in a single pass
// draw the last part in red
ctx.fillStyle = "red";
ctx.rotate(theta);
ctx.beginPath();
trace(0, 0, 50, 200, theta);
ctx.fill();
canvas {
/* checkered effect from https://stackoverflow.com/a/51054396/3702797 */
--tint:rgba(255,255,255,0.9);background-image:linear-gradient(to right,var(--tint),var(--tint)),linear-gradient(to right,black 50%,white 50%),linear-gradient(to bottom,black 50%,white 50%);background-blend-mode:normal,difference,normal;background-size:2em 2em;
}
<canvas></canvas>

Changing number of array elements during a simulation

I am trying to change the number of bouncing balls in a simulation. I am passing the required number using Socket.IO, but I'm struggling to change the number of balls. Here is the JavaScript:
var width = 100,
height = 200,
numBalls,
balls;
$(document).ready(function() {
var socket = io();
socket.on('message', function (data) {
console.log(data.count);
numBalls = data.count
});
$('#myCanvas').click(bounce);
// create an array of balls
balls = new Array(numBalls);
for(i = 0 ; i < numBalls ; i++){
balls[i] = new Ball();
}
});
function Ball(){
// random radius
this.radius = Math.floor(Math.random()*(10-5+1))+5;
// random x and y
this.x = Math.floor(Math.random()*(width-this.radius+1))+this.radius;
this.y = Math.floor(Math.random()*(width-this.radius+1))+this.radius;
// random direction, +1 or -1
this.dx = Math.floor(Math.random()*2) * 2 - 1;
this.dy = Math.floor(Math.random()*2) * 2 - 1;
//random colour, r, g or b
var rcol = Math.floor(Math.random()*3);
this.col = rcol==0 ? "red" :
rcol==1 ? "blue" : "green";
}
// draw the balls on the canvas
function draw(){
var canvas = document.getElementById("myCanvas");
// check if supported
if(canvas.getContext){
var ctx=canvas.getContext("2d");
//clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.globalAlpha = 0.5;
ctx.strokeStyle="black";
// draw each ball
for(i = 0; i < numBalls ; i++){
var ball = balls[i];
ctx.fillStyle=ball.col;
ctx.beginPath();
// check bounds
// change direction if hitting border
if(ball.x<=ball.radius ||
ball.x >= (width-ball.radius)){
ball.dx *= -1;
}
if(ball.y<=ball.radius ||
ball.y >= (height-ball.radius)){
ball.dy *= -1;
}
// move ball
ball.x += ball.dx;
ball.y += ball.dy;
// draw it
ctx.arc(ball.x, ball.y, ball.radius, 0, 2*Math.PI, false);
ctx.stroke();
ctx.fill();
}
}
else{
//canvas not supported
}
}
// calls draw every 10 millis
function bounce(){
setInterval(draw, 10);
}
Let's say newNumBalls is the new number of balls.
If newNumBalls is less than numBalls, you want to remove elements from balls. You can do that by taking a slice of balls and assigning it to balls.
If newNumBalls is greater than numBalls, you want to make new balls and add them to balls. You can do that with push.
The complete logic is this:
if (newNumBalls < numBalls) {
balls = balls.slice(0, newNumBalls);
} else {
for (var i = numBalls; i < newNumBalls; ++i) {
balls.push(new Ball());
}
}
numBalls = newNumBalls;
Below is a snippet that implements this logic.
var width,
height,
numBalls = 10,
balls;
$('#setNumBalls').click(function () {
var newNumBalls = parseInt($('#inputNumBalls').val(), 10);
if (newNumBalls < numBalls) {
balls = balls.slice(0, newNumBalls);
//$('#display').html('Removed ' + (numBalls - newNumBalls) + ' balls');
} else {
for (var i = numBalls; i < newNumBalls; ++i) {
balls.push(new Ball());
}
//$('#display').html('Added ' + (newNumBalls - numBalls) + ' new balls');
}
numBalls = newNumBalls;
});
$(document).ready(function() {
width = $('#myCanvas').width();
height = $('#myCanvas').height();
var canvas = $('#myCanvas')[0];
canvas.width = width;
canvas.height = height;
$('#inputNumBalls').val(numBalls);
// create an array of balls
balls = new Array(numBalls);
for(i = 0 ; i < numBalls ; i++){
balls[i] = new Ball();
}
bounce();
});
function Ball(){
// random radius
this.radius = Math.floor(Math.random()*(10-5+1))+5;
// random x and y
var margin = 2 * this.radius;
this.x = Math.floor(Math.random()*(width-margin))+margin/2;
this.y = Math.floor(Math.random()*(width-margin+1))+margin/2;
// random direction, +1 or -1
this.dx = Math.floor(Math.random()*2) * 2 - 1;
this.dy = Math.floor(Math.random()*2) * 2 - 1;
//random colour, r, g or b
var rcol = Math.floor(Math.random()*3);
this.col = rcol==0 ? "red" :
rcol==1 ? "blue" : "green";
}
// draw the balls on the canvas
function draw(){
var canvas = $('#myCanvas')[0];
// check if supported
if (canvas.getContext) {
var ctx = canvas.getContext("2d");
//clear canvas
ctx.clearRect(0, 0, width, height);
ctx.globalAlpha = 0.5;
ctx.strokeStyle="black";
// draw each ball
for(var i = 0; i < numBalls ; i++){
var ball = balls[i];
ctx.fillStyle = ball.col;
ctx.beginPath();
// check bounds
// change direction if hitting border
if(ball.x <= ball.radius ||
ball.x >= (width - ball.radius)) {
ball.dx *= -1;
}
if(ball.y <= ball.radius ||
ball.y >= (height - ball.radius)) {
ball.dy *= -1;
}
// move ball
ball.x += ball.dx;
ball.y += ball.dy;
// draw it
ctx.arc(ball.x, ball.y, ball.radius, 0, 2*Math.PI, false);
ctx.stroke();
ctx.fill();
}
}
else{
//canvas not supported
}
}
// Calls draw frameRate times a second.
function bounce() {
var frameRate = 60;
setInterval(draw, 1000 / frameRate);
}
body {
font-family: sans-serif;
}
#myCanvas {
float: left;
margin: 0 10px 0 0;
width: 160px;
height: 160px;
border: 1px solid #888;
}
#inputNumBalls {
font-size: 18px;
padding: 5px 8px;
margin: 5px;
text-align: center;
outline: none;
}
.button {
display: inline;
cursor: pointer;
padding: 2px 8px;
border-radius: 5px;
border: 2px solid #888;
}
.button:hover {
background: #ffd;
border-color: #000;
}
#display {
width: 200px;
height: 50px;
padding: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<canvas id="myCanvas"> Canvas not supported. </canvas>
<div>
Number of balls:
<input type="text" id="inputNumBalls" size="3" />
<div class="button" id="setNumBalls">Set</div>
<div id="display"></div>
</div>

Categories