I am now making Customize roulette, and players can input the text what they wanted.
when I click button, the renderRoulette function is work. And this is the inner context of renderRoulette function
rouletteCanvas.style.display = "block"; // the initial state of rouletteCanvas's display is 'none'
// #customize elements are the inputs of the text that users made.
let customize = document.querySelectorAll("#customize");
let len = customize.length;
const canvas = document.querySelector(".roulette-panel"); // I want to draw here.
let width = canvas.width;
let height = canvas.height;
const ctx = canvas.getContext("2d");
// ctx Initialization
ctx.clearRect(0, 0, width, height);
const devide = len;
const degree = 360;
const goalDegree = 270 + degree / devide;
for (let i = 0; i < devide; i++) {
let json = {
first: (degree / devide) * i,
last: (degree / devide) * (i + 1),
text: `${i + 1}`,
};
data.push(json);
}
// Draw a circular sector
data.forEach((item) => {
ctx.save();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = "gray";
ctx.moveTo(width / 2, height / 2);
ctx.arc(
width / 2,
height / 2,
width / 2,
degreesToRadians(item.first),
degreesToRadians(item.last),
false
);
ctx.closePath();
ctx.stroke();
ctx.restore();
ctx.save();
let half = Math.abs(item.first - item.last) / 2;
let degg = item.first + half;
let xx = ((Math.cos(degreesToRadians(degg)) * width) / 2) * 0.7 + width / 2;
let yy =
((Math.sin(degreesToRadians(degg)) * width) / 2) * 0.7 + height / 2;
let minus = ctx.measureText(item.text).width / 2;
ctx.font = "bold 15px sans-serif";
ctx.fillText(item.text, xx - minus, yy);
ctx.restore();
});
Every time I click the button, I want to delete all the previously drawn circular sector shapes and redraw them, so I used the code, ctx.clearRect(0, 0, width, height); . However, even if you click the button after, it will be drawn over the existing picture. How do I initialize it?
I get a feeling that the save and restore in your code are causing some problems for you, I tried a simpler version of your code and the ctx.clearRect(0, 0, width, height) works just fine.
Here is the code:
const canvas = document.querySelector(".roulette-panel");
const ctx = canvas.getContext("2d");
let width = height = 100;
const devide = 8;
let data = []
for (let i = 0; i < devide; i++) {
data.push({
first: (360 / devide) * i,
last: (360 / devide) * (i + 1)
});
}
function draw() {
ctx.clearRect(0, 0, width, height);
ctx.beginPath();
data.forEach((item) => {
ctx.moveTo(width / 2, height / 2);
ctx.arc(50, 50, 45,
item.first * Math.PI / 180,
item.last * Math.PI / 180
);
item.first += 0.5
item.last += 0.5
});
ctx.stroke();
}
setInterval(draw, 40)
<canvas class="roulette-panel"></canvas>
This code uses setInterval to delete all the previously drawn circular sector shapes and draw new one on a new position creating the effect of movement
Here is that same code but with the clearRect commented:
const canvas = document.querySelector(".roulette-panel");
const ctx = canvas.getContext("2d");
let width = height = 100;
const devide = 8;
let data = []
for (let i = 0; i < devide; i++) {
data.push({
first: (360 / devide) * i,
last: (360 / devide) * (i + 1)
});
}
function draw() {
//ctx.clearRect(0, 0, width, height);
ctx.beginPath();
data.forEach((item) => {
ctx.moveTo(width / 2, height / 2);
ctx.arc(50, 50, 45,
item.first * Math.PI / 180,
item.last * Math.PI / 180
);
item.first += 0.5
item.last += 0.5
});
ctx.stroke();
}
setInterval(draw, 40)
<canvas class="roulette-panel"></canvas>
We can clearly see a different effect
Related
So I have wanted to make an ocean in the night with a canvas. I got the Wave part down but I want them to be filled. So that they are not just a lign anymore but a solid block locking like waves. I really like how they look right now but if you have any tweeks that could make them look even better feel free to share them!
Thanks for the Help in Advance!
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;
//ctx.strokeStyle = "Cornsilk";
ctx.lineWidth = 4;
//first wave
function Draw() {
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 -110); // setting it to the bottom of the page 100= lift
}
ctx.lineTo(w, ch);
ctx.lineTo(0, ch);
ctx.stroke();
requestId = window.requestAnimationFrame(Draw);
}
requestId = window.requestAnimationFrame(Draw);
//second wave
function Draw2() {
frames++
phi = frames / 72 ;
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 -110); // setting it to the bottom of the page 100= lift
}
ctx.lineTo(w, ch);
ctx.lineTo(0, ch);
ctx.stroke();
requestId = window.requestAnimationFrame(Draw2);
}
requestId = window.requestAnimationFrame(Draw2);
canvas {
display:block;
margin:0 auto;
width:100%;
height:100vh;
}
<canvas id="screen2"></canvas>
First and foremost what I really would change is using requestAnimationFrame two times as it's completely redundant. You can group your drawing operations in a single function and just execute this.
As #mousetail hinted a simple 'fix' would be using .fill() instead of .stroke(). The problem is that you would have two completely solid shapes of the same colour making it look like there's just a single wave. So the next step would be adding two different colours. As that might still look a little boring I'd use two linear gradients - a lighter for the foreground and a darker for the background wave.
To make it look even more interesting I'd move the waves up & down over time.
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 gradientA = ctx.createLinearGradient(0, ch / 2, 0, ch);
gradientA.addColorStop(0, "#29c3d3");
gradientA.addColorStop(1, "#15656e");
var gradientB = ctx.createLinearGradient(0, ch / 2, 0, ch);
gradientB.addColorStop(0, "#55dee5");
gradientB.addColorStop(1, "#399499");
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 - 110 + Math.sin(step / 2) * 10); // 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 / 72;
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 - 110 + 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);
canvas {
display: block;
margin: 0 auto;
width: 100%;
height: 100vh;
}
<canvas id="screen2"></canvas>
I'm having difficulties replicating the pyramid below on the canvas.
I'm struggling with the math portion on how to draw a new ball on each new line. Here is my code so far.
<canvas id="testCanvas" width="300" height="300" style="border:1px solid #d3d3d3;"></canvas>
<script>
// Access canvas element and its context
const canvas = document.getElementById('testCanvas');
const context = canvas.getContext("2d");
const x = canvas.width;
const y = canvas.height;
const radius = 10;
const diamater = radius * 2;
const numOfRows = canvas.width / diamater;
function ball(x, y) {
context.arc(x, y, radius, 0, 2 * Math.PI, true);
context.fillStyle = "#FF0000"; // red
context.fill();
}
function draw() {
for (let i = 0; i < numOfRows; i++) {
for (let j = 0; j < i + 1; j++) {
ball(
//Pos X
(x / 2),
//Pos Y
diamater * (i + 1)
);
}
}
ball(x / 2, y);
context.restore();
}
draw();
</script>
I've been stuck on this problem for a while. I appreciate any assistance you can provide.
Thank you.
I noticed that the circle do not touch. I am not sure if you need or want them to but as this presented an interesting problem I create this answer.
Distance between stacked circles.
The distance between rows can be calculated using the right triangle as shown in the following image
Where R is the radius of the circle and D is the distance between rows.
D = ((R + R) ** 2 - R ** 2) ** 0.5;
With that we can get the number of rows we can fit given a radius as
S = (H - R * 2) / D;
Where H is the height of the canvas and S is the number of rows.
Example
Given a radius fits as many rows as possible into the give canvas height.
const ctx = canvas.getContext("2d");
const W = canvas.width, H = canvas.height, CENTER = W / 2;
const cols = ["#E80", "#0B0"];
draw();
function fillPath(path, x, y, color) {
ctx.fillStyle = color;
ctx.setTransform(1, 0, 0, 1, x, y);
ctx.fill(path);
}
function draw() {
const R = 10;
const D = ((R * 2) ** 2 - R ** 2) ** 0.5;
const S = (H - R * 2) / D | 0;
const TOP = R + (H - (R * 2 + D * S)) / 2; // center horizontal
const circle = new Path2D();
circle.arc(0, 0, R, 0, Math.PI * 2);
var y = 0, x;
while (y <= S) {
x = 0;
const LEFT = CENTER - (y * R);
while (x <= y) {
fillPath(circle, LEFT + (x++) * R * 2, TOP + y * D, cols[y % 2]);
}
y ++;
}
}
canvas {
border:1px solid #ddd;
}
<canvas id="canvas" width="300" height="180"></canvas>
Radius to fit n rows of stacked circles
Or if you have the height H and the number of rows S you want to fit. As shown in next image.
We want to find R given H and S we rearrange for H and solve the resulting quadratic with
ss = S * S - 2 * S + 1;
a = 4 / ss;
b = -4 * H / ss;
c = H * H / ss;
R = (-b-(b*b - 4 * a * c) ** 0.5) / (2 * a); // the radius
Example
Given the number of rows (number input) calculates the radius that will fit that number of rows
const ctx = canvas.getContext("2d");
const W = canvas.width, H = canvas.height, CENTER = W / 2;
rowsIn.addEventListener("input", draw)
const cols = ["#DD0", "#0A0"];
draw();
function fillPath(path, x, y, color) {
ctx.fillStyle = color;
ctx.setTransform(1, 0, 0, 1, x, y);
ctx.fill(path);
}
function draw() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0,0,W,H);
const S = Number(rowsIn.value);
const ss = S * S - 2 * S + 1;
const a = 4 / ss - 3, b = -4 * H / ss, c = H * H / ss;
const R = (- b - ((b * b - 4 * a * c) ** 0.5)) / (2 * a); // the radius
const TOP = R;
const D = ((R * 2) ** 2 - R ** 2) ** 0.5;
//const S = (H - R * 2) / D;
const circle = new Path2D();
circle.arc(0, 0, R, 0, Math.PI * 2);
var y = 0, x;
while (y < S) {
x = 0;
const LEFT = CENTER - (y * R);
while (x <= y) {
fillPath(circle, LEFT + (x++) * R * 2, TOP + y * D, cols[y % 2]);
}
y ++;
}
}
canvas {
border:1px solid #ddd;
}
<canvas id="canvas" width="300" height="180"></canvas>
<input type="number" id="rowsIn" min="3" max="12" value="3">Rows
How you can approach this problem is by breaking it down into one step at a time.
On (1)st row draw 1 circle
On (2)nd row draw 2 circles
On (3)rd row draw 3 circles
And so on...
Then you have to figure out where to draw each circle. That also you can break down into steps.
1st-row 1st circle in the center (width)
2nd-row 1st circle in the center minus diameter
2nd-row 2nd circle in the center plus diameter
and so on.
Doing this way you will find a pattern to convert into 2 for loops.
Something like this:
//1st row 1st circle
ball(w/2,radius * 1, red);
//2nd row 1st circle
ball(w/2 - radius,radius * 3, blue);
//2nd row 2nd circle
ball(w/2 + radius,radius * 3, blue);
The code below shows each step how each ball is drawn. I have also done few corrections to take care of the numberOfRows.
const canvas = document.getElementById('testCanvas');
const context = canvas.getContext("2d");
const w = canvas.width;
const h = canvas.height;
const radius = 10;
const diamater = radius * 2;
const numOfRows = Math.min(h / diamater, w / diamater);
const red = "#FF0000";
const blue = "#0000FF";
var k = 1;
function ball(x, y, color) {
setTimeout(function() {
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, true);
context.fillStyle = color;
context.fill();
}, (k++) * 250);
}
for (var i = 1; i <= numOfRows; i++) {
for (var j = 1; j <= i; j++) {
var y = (i * radius * 2) - radius;
var x = (w / 2) - ((i * radius) + radius) + (j * diamater);
ball(x, y, i % 2 ? red : blue);
}
}
<canvas id="testCanvas"
width="300" height="180"
style="border:1px solid #d3d3d3;"></canvas>
Am trying to make the background of this Codepen transparent https://codepen.io/scorch/pen/BZjbmW. I would like to have the Swirls on the a different backgrounds, instead of the colored background that is on the stated Codepen.
I have tried add css code but that did not seem to do anything. I tried messing with the Canvas RGB and that did not seem to do anything either.
// create a canvas element
var canvas = document.createElement("canvas")
// attach element to DOM
document.getElementsByTagName("body")[0].appendChild(canvas)
// background color [r, g, b]
var bg = [20, 0, 30]
var wh = window.innerHeight
// get the canvas context (this is the part we draw to)
var ctx = canvas.getContext("2d")
function setup() {
// setup the canvas size to match the window
canvas.width = window.innerWidth
canvas.height = window.innerHeight
wh = window.innerWidth < window.innerHeight ? window.innerWidth : window.innerHeight
// set the 0,0 point to the middle of the canvas, this is not necessary but it can be handy
ctx.translate(canvas.width / 2, canvas.height / 2)
fill(bg, 1)
}
// fill entire canvas with a preset color
function fill(rgb, amt) {
ctx.beginPath(); // start path
ctx.rect(-canvas.width / 2, -canvas.height / 2, canvas.width, canvas.height) // set rectangle to be the same size as the window
ctx.fillStyle = `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${amt})` // use the rgb array/color for fill, and amt for opacity
ctx.fill() // do the drawing
}
function drawCircle(x, y, r, color) {
ctx.beginPath()
ctx.arc(x, y, r, 0, 2 * Math.PI)
ctx.fillStyle = color || 'white'
ctx.fill()
ctx.closePath()
}
function Particle() {
// initialize loopers with random trange and offset
this.loop1 = new Looper(500 + 200 * Math.random(), 860 * Math.random())
this.loop2 = new Looper(320 + 70 * Math.random(), 20 * Math.random())
this.loop3 = new Looper(120 + 20 * Math.random(), 140 * Math.random())
this.history = []
this.history_max = 40
// this.x = null
// this.y = null
this.offset = Math.random() // some color offset for the color
this.draw = function() {
// set x,y, radius, and color params
var x = this.loop1.sin * (wh / 4 - 10) + this.loop2.sin * (wh / 6 - 10) + this.loop3.sin * 60
var y = this.loop1.cos * (wh / 4 - 10) + this.loop2.cos * (wh / 6 - 10) + this.loop3.cos * 10
var r = 0.2 + 3 * this.loop3.sinNorm * this.loop3.cosNorm // set the radius
var c = `hsla(${280 + 60 * (this.loop3.cosNorm + this.offset) * this.loop2.sinNorm}, ${100}%, ${50 + 10 * this.loop3.sin}%, ${1})`
ctx.beginPath()
ctx.strokeStyle = c
ctx.lineCap = 'round'
ctx.lineWidth = r
var tx = x
var ty = y
for (var i = 0; i < Math.min(this.history_max * this.loop3.cosNorm, this.history.length); i++) {
ctx.moveTo(tx, ty)
tx = this.history[i][0]
ty = this.history[i][1]
ctx.lineTo(tx, ty)
}
ctx.stroke()
drawCircle(x, y, r * 2 + 3, c); // draw the circle
this.loop1.update() // update looper
this.loop2.update() // update looper
this.loop3.update() // update looper
this.history.unshift([x, y])
if (this.history.length > this.history_max) {
this.history.pop()
}
}
}
// initialize a set of particle
var particles = []
for (var i = 0; i < 90; i++) {
particles.push(new Particle())
}
function draw() {
// fill context with background color
fill(bg, 0.36)
// update all the particles
for (var i = 0; i < particles.length; i++) {
particles[i].draw() // do it once
}
// this is a draw loop, this will execute frequently and is comparable to EnterFrame on other platform
window.requestAnimationFrame(function() {
draw()
})
}
// start enterFrame loop
window.requestAnimationFrame(draw);
// force running setup
setup()
// re-setup canvas when the size of the window changes
window.addEventListener("resize", setup)
// create a class to hold value and have built in incrementing functionality
function Looper(steps, start) {
this.val = start || 0 // set value to start value if defined, or 1
this.steps = steps || 100 // set steps to passed value or default to 100
this.norm = this.val / this.range // initialize normalized value (between 0 and 1)
this.sin = Math.sin(this.norm * Math.PI * 2) // get sine value from norm normalized to [0, 2PI]
this.sinNorm = (this.sin + 1) / 2 // normalize sin to [0,1]
this.cos = Math.cos(this.norm * Math.PI * 2) // get cosine value from norm normalized to [0, 2PI]
this.cosNorm = (this.cos + 1) / 2 // normalize cos to [0,1]
this.update = function() {
this.val = (this.val + 1) % this.steps // update value
this.norm = this.val / this.steps // update normalize value (between 0 and 1)
this.sin = Math.sin(this.norm * Math.PI * 2) // get sine value from norm normalized to [0, 2PI]
this.sinNorm = (this.sin + 1) / 2 // normalize sine to [0,1]
this.cos = Math.cos(this.norm * Math.PI * 2) // get cosine value from norm normalized to [0, 2PI]
this.cosNorm = (this.cos + 1) / 2 // normalize cos to [0,1]
}
}
ctx.fillStyle = rgba(255,255,255,0)
// create a canvas element
var canvas = document.createElement("canvas")
// attach element to DOM
document.getElementsByTagName("body")[0].appendChild(canvas)
// background color [r, g, b]
var bg = [20, 0, 30]
var wh = window.innerHeight
// get the canvas context (this is the part we draw to)
var ctx = canvas.getContext("2d")
function setup() {
// setup the canvas size to match the window
canvas.width = window.innerWidth
canvas.height = window.innerHeight
wh = window.innerWidth < window.innerHeight ? window.innerWidth : window.innerHeight
// set the 0,0 point to the middle of the canvas, this is not necessary but it can be handy
ctx.translate(canvas.width / 2, canvas.height / 2)
fill(bg, 1)
}
// fill entire canvas with a preset color
function fill(rgb, amt) {
ctx.beginPath(); // start path
ctx.rect(-canvas.width / 2, -canvas.height / 2, canvas.width, canvas.height) // set rectangle to be the same size as the window
ctx.fillStyle = `rgba(255,255,255,0)` // use the rgb array/color for fill, and amt for opacity
ctx.fill() // do the drawing
}
function drawCircle(x, y, r, color) {
ctx.beginPath()
ctx.arc(x, y, r, 0, 2 * Math.PI)
ctx.fillStyle = color || 'white'
ctx.fill()
ctx.closePath()
}
function Particle() {
// initialize loopers with random trange and offset
this.loop1 = new Looper(500 + 200 * Math.random(), 860 * Math.random())
this.loop2 = new Looper(320 + 70 * Math.random(), 20 * Math.random())
this.loop3 = new Looper(120 + 20 * Math.random(), 140 * Math.random())
this.history = []
this.history_max = 40
// this.x = null
// this.y = null
this.offset = Math.random() // some color offset for the color
this.draw = function() {
// set x,y, radius, and color params
var x = this.loop1.sin * (wh / 4 - 10) + this.loop2.sin * (wh / 6 - 10) + this.loop3.sin * 60
var y = this.loop1.cos * (wh / 4 - 10) + this.loop2.cos * (wh / 6 - 10) + this.loop3.cos * 10
var r = 0.2 + 3 * this.loop3.sinNorm * this.loop3.cosNorm // set the radius
var c = `hsla(${280 + 60 * (this.loop3.cosNorm + this.offset) * this.loop2.sinNorm}, ${100}%, ${50 + 10 * this.loop3.sin}%, ${1})`
ctx.beginPath()
ctx.strokeStyle = c
ctx.lineCap = 'round'
ctx.lineWidth = r
var tx = x
var ty = y
for (var i = 0; i < Math.min(this.history_max * this.loop3.cosNorm, this.history.length); i++) {
ctx.moveTo(tx, ty)
tx = this.history[i][0]
ty = this.history[i][1]
ctx.lineTo(tx, ty)
}
ctx.stroke()
drawCircle(x, y, r * 2 + 3, c); // draw the circle
this.loop1.update() // update looper
this.loop2.update() // update looper
this.loop3.update() // update looper
this.history.unshift([x, y])
if (this.history.length > this.history_max) {
this.history.pop()
}
}
}
// initialize a set of particle
var particles = []
for (var i = 0; i < 90; i++) {
particles.push(new Particle())
}
function draw() {
// fill context with background color
fill(bg, 0.36)
// update all the particles
for (var i = 0; i < particles.length; i++) {
particles[i].draw() // do it once
}
// this is a draw loop, this will execute frequently and is comparable to EnterFrame on other platform
window.requestAnimationFrame(function() {
draw()
})
}
// start enterFrame loop
window.requestAnimationFrame(draw);
// force running setup
setup()
// re-setup canvas when the size of the window changes
window.addEventListener("resize", setup)
// create a class to hold value and have built in incrementing functionality
function Looper(steps, start) {
this.val = start || 0 // set value to start value if defined, or 1
this.steps = steps || 100 // set steps to passed value or default to 100
this.norm = this.val / this.range // initialize normalized value (between 0 and 1)
this.sin = Math.sin(this.norm * Math.PI * 2) // get sine value from norm normalized to [0, 2PI]
this.sinNorm = (this.sin + 1) / 2 // normalize sin to [0,1]
this.cos = Math.cos(this.norm * Math.PI * 2) // get cosine value from norm normalized to [0, 2PI]
this.cosNorm = (this.cos + 1) / 2 // normalize cos to [0,1]
this.update = function() {
this.val = (this.val + 1) % this.steps // update value
this.norm = this.val / this.steps // update normalize value (between 0 and 1)
this.sin = Math.sin(this.norm * Math.PI * 2) // get sine value from norm normalized to [0, 2PI]
this.sinNorm = (this.sin + 1) / 2 // normalize sine to [0,1]
this.cos = Math.cos(this.norm * Math.PI * 2) // get cosine value from norm normalized to [0, 2PI]
this.cosNorm = (this.cos + 1) / 2 // normalize cos to [0,1]
}
}
I'm trying to create a little circular "equalizer" effect using JavaScript and HTML canvas for a little project I'm working on, and it works great, except one little thing. It's just a series of rectangular bars moving in time to an mp3 - nothing overly fancy, but at the moment all the bars point in one direction (i.e. 0 radians, or 90 degrees).
I want each respective rectangle around the edge of the circle to point directly away from the center point, rather than to the right. I have 360 bars, so naturally, each one should be 1 degree more rotated than the previous.
I thought that doing angle = i*Math.PI/180 would fix that, but it doesn't seem to matter what I do with the rotate function - they always end up pointing in weird and wonderful directions, and being translated a million miles from where they were. And I can't see why. Can anyone see where I'm going wrong?
My frame code, for reference, is as follows:
function frames() {
// Clear the canvas and get the mp3 array
window.webkitRequestAnimationFrame(frames);
musicArray = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(musicArray);
ctx.clearRect(0, 0, canvas.width, canvas.height);
bars = 360;
for (var i = 0; i < bars; i++) {
// Find the rectangle's position on circle edge
distance = 100;
var angle = i * ((Math.PI * 2) / bars);
var x = Math.cos(angle) * distance + (canvas.width / 2);
var y = Math.sin(angle) * distance + (canvas.height / 2);
barWidth = 5;
barHeight = (musicArray[i] / 4);
// Fill with a blue-green gradient
var grd = ctx.createLinearGradient(x, 0, x + 40, 0);
grd.addColorStop(0, "#00CCFF");
grd.addColorStop(1, "#00FF7F");
ctx.fillStyle = grd;
// Rotate the rectangle according to position
// ctx.rotate(i*Math.PI/180); - DOESN'T WORK
// Draw the rectangle
ctx.fillRect(x, y, barHeight, barWidth);
}
For clarity I've removed part of your code. I'm using rotate as you intended. Also I'm using barHeight = (Math.random()* 50); instead your (musicArray[i]/4); because I wanted to have something to show.
Also I've changed your bars to 180. It's very probable that you won't have 360 bars but 32 or 64 or 128 or 256 . . . Now you can change the numbers of bare to one of these numbers to see the result.
I'm drawing everything around the origin of the canvas and translating the context in the center.
I hope it helps.
const canvas = document.getElementById("c");
const ctx = canvas.getContext("2d");
let cw = canvas.width = 400;
let ch = canvas.height = 400;
let bars = 180;
let r = 100;
ctx.translate(cw / 2, ch / 2)
for (var i = 0; i < 360; i += (360 / bars)) {
// Find the rectangle's position on circle edge
var angle = i * ((Math.PI * 2) / bars);
//var x = Math.cos(angle)*r+(canvas.width/2);
//var y = Math.sin(angle)*r+(canvas.height/2);
barWidth = 2 * Math.PI * r / bars;
barHeight = (Math.random() * 50);
ctx.fillStyle = "green";
// Rotate the rectangle according to position
// ctx.rotate(i*Math.PI/180); - DOESN'T WORK
// Draw the rectangle
ctx.save();
ctx.rotate(i * Math.PI / 180);
ctx.fillRect(r, -barWidth / 2, barHeight, barWidth);
//ctx.fillRect(r ,0, barHeight, barWidth);
ctx.restore();
}
canvas {
border: 1px solid
}
<canvas id="c"></canvas>
Here is another solution, I'm preserving your initial trigonometry approach.
But instead of rectangles I used lines, I don't think it makes a difference for you, if what you need is bars moving in time to an mp3 all you need to do is change the var v = Math.random() + 1; to a reading from the Amplitude, and those bars will be dancing.
const canvas = document.getElementById("c");
canvas.width = canvas.height = 170;
const ctx = canvas.getContext("2d");
ctx.translate(canvas.width / 2, canvas.height / 2)
ctx.lineWidth = 2;
let r = 40;
let bars = 180;
function draw() {
ctx.clearRect(-100, -100, 200, 200)
for (var i = 0; i < 360; i += (360 / bars)) {
var angle = i * ((Math.PI * 2) / bars);
var x = Math.cos(angle) * r;
var y = Math.sin(angle) * r;
ctx.beginPath();
var v = Math.random() + 1;
ctx.moveTo(x, y);
ctx.lineTo(x * v, y * v)
grd = ctx.createLinearGradient(x, y, x*2, y*2);
grd.addColorStop(0, "blue");
grd.addColorStop(1, "red");
ctx.strokeStyle = grd;
ctx.stroke();
}
}
setInterval(draw, 100)
<canvas id="c"></canvas>
I have a canvas animation which you can see here.
I've noticed that once you have watched the animation after a while (approximately 25 seconds) the animation starts to jump around. I'm struggling to figure out how to make it so that it is one constant fluid motion?
Code below:
<canvas id="canvas"></canvas>
var canvas = document.querySelector("#canvas");
var ctx = canvas.getContext("2d");
canvas.width = parseInt(getComputedStyle(canvas).width);
canvas.height = parseInt(getComputedStyle(canvas).height);
var P = 4;
var A = 4;
function draw(shift) {
var w = canvas.width;
var h = canvas.height;
shift = shift >= 500*Math.PI ? shift - 100*Math.PI : shift;
ctx.clearRect(0, 0, w, h);
var grd = ctx.createLinearGradient(0, 0, w, h);
grd.addColorStop(0, "#4a8bf5");
grd.addColorStop(1, "#f16b55");
ctx.strokeStyle = grd;
ctx.lineCap = "round";
for (var i = 0; i < w; ) {
var _A = Math.abs(A*Math.cos(2*i));
ctx.beginPath();
var pos = Math.exp(-_A * i / w) * Math.sin(P * Math.PI * (i + shift) / w);
pos *= h / 2;
var lw = Math.exp(-_A * i / w) * Math.sin(3 * Math.PI * (i - shift) / w) * 2;
ctx.lineWidth = (lw)+1;
ctx.lineTo(i, h / 2 - pos);
ctx.closePath();
ctx.stroke();
i += 1;
}
window.requestAnimationFrame(function(){
draw(shift + 1);
});
}
draw(0);
The solution is to make sure that the change in shift results in the sin() argument changing by a multiple of 2π.
Given
Math.sin((i + shift) / (w / P))
this can be done using something like
if (shift > 500) shift -= 2 * Math.PI * (w / P);
There will still be a jump in the 2nd sin() argument here, the line width. To avoid this, shift has to be reduced by a number that causes both arguments to change by multiples of 2π, the LCM if you will.
var canvas = document.querySelector("#canvas");
var ctx = canvas.getContext("2d");
canvas.width = parseInt(getComputedStyle(canvas).width);
canvas.height = parseInt(getComputedStyle(canvas).height);
var P = 10;
var A = 4;
var shift = 0;
function draw() {
var w = canvas.width;
var h = canvas.height;
shift += 1;
if (shift > 500) shift -= 2 * Math.PI * (w / P);
shift_el.innerHTML = shift;
ctx.clearRect(0, 0, w, h);
var grd = ctx.createLinearGradient(0, 0, w, h);
grd.addColorStop(0, "#4a8bf5");
grd.addColorStop(1, "#f16b55");
ctx.strokeStyle = grd;
ctx.lineCap = "round";
for (var i = 0; i < w;) {
var _A = Math.abs(A * Math.cos(2 * i));
ctx.beginPath();
var pos = Math.exp(-_A * i / w) * Math.sin((i + shift) / (w / P));
pos *= h / 2;
var lw = Math.exp(-_A * i / w) * Math.sin(3 * Math.PI * (i - shift) / w) * 2;
ctx.lineWidth = (lw) + 1;
ctx.lineTo(i, h / 2 - pos);
ctx.closePath();
ctx.stroke();
i += 1;
}
window.requestAnimationFrame(draw);
}
draw();
body {
background: #ffffff;
padding: 0;
margin: 0;
}
canvas {
height: 200px;
width: 100%;
}
#shift_el {
position: absolute;
top: 0
}
<canvas id="canvas"></canvas>
<div id="shift_el"></div>