I am trying to imitate a pattern I found on the internet, but I get weird lines in the middle and when trying to connect another set of circles on top.
Also, when I try to fill, it becomes fully black.
console.log("grid");
var canvas = document.getElementById("canvas");
var image_b = document.getElementById("brown");
var image_g = document.getElementById("grey");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var ctx = canvas.getContext("2d");
var side = 160;
var side2 = 150;
ctx.strokeStyle = 'black';
ctx.fillStyle = 'white';
function draw() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
var widthNbr = Math.ceil(window.innerWidth / side) + 1;
var heightNbr = Math.ceil(window.innerHeight / side) + 1;
var counter = 0;
for (var i = 0; i < widthNbr; i++) {
for (var j = 0; j < heightNbr; j++) {
ctx.beginPath();
var x = side * i + side / 2;
var y = side * j + side / 2;
var a = side * i + side / 2;
var s = side * j + side / 2;
var d = side * i + side / 2;
var f = side * j + side / 2;
var g = side * i + side / 2;
var h = side * j + side / 2;
var q = side * i + side / 2;
var w = side * j + side / 2;
var o = side * i + side / 2;
var p = side * j + side / 2;
var x1 = side2 * i + side2;
var y1 = side2 * j + side2;
var a1 = side2 * i + side2;
var s1 = side2 * j + side2;
var d1 = side2 * i + side2;
var f1 = side2 * j + side2;
var g1 = side2 * i + side2;
var h1 = side2 * j + side2;
var q1 = side2 * i + side2;
var w1 = side2 * j + side2;
var o1 = side2 * i + side2;
var p1 = side2 * j + side2;
ctx.arc(x, y, side / 2, 0, Math.PI * 2);
ctx.arc(a, s, side / 2.5, 0, Math.PI * 2);
ctx.arc(d, f, side / 3.5, 0, Math.PI * 2);
ctx.arc(g, h, side / 5.3, 0, Math.PI * 2);
ctx.arc(q, w, side / 9, 0, Math.PI * 2);
ctx.arc(o, p, side / 18, 0, Math.PI * 2);
ctx.lineWidth = 5;
ctx.arc(x1, y1, side2 / 2, 0, Math.PI * 2);
ctx.arc(a1, s1, side2 / 2.5, 0, Math.PI * 2);
ctx.arc(d1, f1, side2 / 3.5, 0, Math.PI * 2);
ctx.arc(g1, h1, side2 / 5.3, 0, Math.PI * 2);
ctx.arc(q1, w1, side2 / 9, 0, Math.PI * 2);
ctx.arc(o1, p1, side2 / 18, 0, Math.PI * 2);
ctx.stroke();
// ctx.fill();
ctx.closePath();
counter++;
}
}
}
draw();
<canvas id="canvas"></canvas>
You have to think about canvas Path drawings as pencil drawing on a paper :
Just after the path declaration (beginPath), when you say ctx.arc(x, y, rad, 0, Math.PI*2) your pen goes to coordinates (x, y), and because x and y are the center position of your arc it will be putted at a rad distance from this center to draw the circle. Your 0 tells it to start at 3 o'clock, so in this case, we just need to add this rad to the x value.
At this moment, your pen is on the paper.
It draws the arc, and when you tell it arc(x1, y1, rad, ...), it goes directly to coordinates (x1+rad, y1) and draws the new arc.
The problem here is that you never told it to raise the pencil from the paper, so you can see the line that goes from the last point on the first arc to the first point on the next one.
Fortunately, Canvas API comes with a handy set of operations, and the "Raise_the_pen_and_move_to_coordinates_x,y_without_ruining_my_paper" is simply called moveTo.
By telling the context to gently raise the pencil and to move to the next first drawing point, before actually drawing the arc, you'll avoid all these trailing lines.
So basically, for three arcs it would be :
// initialize a new drawing
ctx.beginPath();
// here we can set it directly because the pen is not on the paper yet
ctx.arc(x, y, rad, 0, Math.PI*2);
// tell it to raise the pen off the paper
// and to go to the next starting point (3 o'clock in our case)
ctx.moveTo(x1 + rad, y1);
ctx.arc(x1, y1, rad, 0, Math.PI*2);
// once again
ctx.moveTo(x2 + rad, y2);
ctx.arc(x2, y2, rad, 0, Math.PI*2);
// now we've got clear independents arcs
ctx.stroke();
And with your code (That you could clean a lot by using arrays btw)
var canvas = document.getElementById("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var ctx = canvas.getContext("2d");
var side = 160;
var side2 = 150;
ctx.strokeStyle = 'black';
ctx.fillStyle = 'white';
function draw() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
var widthNbr = Math.ceil(window.innerWidth / side) + 1;
var heightNbr = Math.ceil(window.innerHeight / side) + 1;
var counter = 0;
for (var i = 0; i < widthNbr; i++) {
for (var j = 0; j < heightNbr; j++) {
ctx.beginPath();
var x = side * i + side / 2;
var y = side * j + side / 2;
var a = side * i + side / 2;
var s = side * j + side / 2;
var d = side * i + side / 2;
var f = side * j + side / 2;
var g = side * i + side / 2;
var h = side * j + side / 2;
var q = side * i + side / 2;
var w = side * j + side / 2;
var o = side * i + side / 2;
var p = side * j + side / 2;
var x1 = side2 * i + side2;
var y1 = side2 * j + side2;
var a1 = side2 * i + side2;
var s1 = side2 * j + side2;
var d1 = side2 * i + side2;
var f1 = side2 * j + side2;
var g1 = side2 * i + side2;
var h1 = side2 * j + side2;
var q1 = side2 * i + side2;
var w1 = side2 * j + side2;
var o1 = side2 * i + side2;
var p1 = side2 * j + side2;
ctx.moveTo(x + side / 2, y);
ctx.arc(x, y, side / 2, 0, Math.PI * 2);
ctx.moveTo(a + side / 2.5, s);
ctx.arc(a, s, side / 2.5, 0, Math.PI * 2);
ctx.moveTo(d + side / 3.5, f)
ctx.arc(d, f, side / 3.5, 0, Math.PI * 2);
ctx.moveTo(g + side / 5.3, h)
ctx.arc(g, h, side / 5.3, 0, Math.PI * 2);
ctx.moveTo(q + side / 9, w)
ctx.arc(q, w, side / 9, 0, Math.PI * 2);
ctx.moveTo(o + side / 18, p)
ctx.arc(o, p, side / 18, 0, Math.PI * 2);
ctx.lineWidth = 5;
ctx.moveTo(x1 + side2 / 2, y1)
ctx.arc(x1, y1, side2 / 2, 0, Math.PI * 2);
ctx.moveTo(a1 + side2 / 2.5, s1)
ctx.arc(a1, s1, side2 / 2.5, 0, Math.PI * 2);
ctx.moveTo(d1 + side2 / 3.5, f1)
ctx.arc(d1, f1, side2 / 3.5, 0, Math.PI * 2);
ctx.moveTo(g1 + side2 / 5.3, h1)
ctx.arc(g1, h1, side2 / 5.3, 0, Math.PI * 2);
ctx.moveTo(q1 + side2 / 9, w1)
ctx.arc(q1, w1, side2 / 9, 0, Math.PI * 2);
ctx.moveTo(o1 + side2 / 18, p1)
ctx.arc(o1, p1, side2 / 18, 0, Math.PI * 2);
ctx.stroke();
counter++;
}
}
}
draw();
<canvas id="canvas"></canvas>
As correctly noted by Spencer Wieczorek in comments above, to get the result you wanted, you'll also have to white-fill the largest arcs, but I let you find the way to do it as a training.
Also, a small note on closePath() that you were using in your code, his name might be quite confusing when we see the number of people misusing it, but note that it doesn't ends your Path declaration. All it does is a lineTo(last_time_I_putted_the_pencil). In the case of closed circle, it doesn't have any effect because last_time_I_putted_the_pencil === current_pencil_position_on_the_paper, but it's often the source of a lot of problems.
And an other small note, for users a bit more experienced (probably OP in few days / weeks) :
Other operations allow us to raise our pencil from the paper : the transformation commands.
(mainly setTransform, and its subsets transform, translate, rotate and scale).
These operations will first raise the pen, and then move the paper rather than the pen. This comes handy in a lot of situations.
And to set it back to its normal position, you just have to call setTransform(1,0,0,1,0,0).
As I was far too slow in responding to this question please consider this an addendum to the excellent answer already provided by Kaiido.
When thinking about problems like this it is sometimes useful to separate the calculation of values from their application. Of course, this kind of understanding only comes with experience, unless that is we can plug into the experience of others - which is exactly what sites like StackOverflow are for! :)
The image we're intending to make is made entirely of circles, so we can greatly reduce repetition in our code by creating a function that deals with just that one thing for us. Something like...
/* draw circle */
function drawCircle(x, y, r, LW) {
context.lineWidth = LW;
context.beginPath();
context.arc(x, y, r, 0, Math.PI*2, true);
context.fill();
context.stroke();
}
Although this function only draws a single circle to the canvas we can pass it values that can be used to draw every element we need.
The reference image is built out of sets of circles, where each circle-set has the same (x,y) position. If we know those starting co-ordinates for X & Y, and the radius of the largest circle in the set, then we can create a function to calculate the values we need and pass them into the drawCircles() function above...
function circleSet(X, Y, Radius) {
var count = 5; /* number of circles in each set */
var step = Radius / count;
var ln_width;
var rad;
while (count > 0) {
ln_width = count > 3 ? 3 : (count > 1 ? 2 : 1);
rad = count * step;
drawCircle(X, Y, rad, ln_width);
count--;
}
}
The while-loop reduces the count variable from 5 to 1 and line width and radius values are calculated before being passed into the aforementioned drawCircle() function, along with (x,y) co-ordinates.
The conditional in the first line of the while-loop says:
if count is 4 or 5 then ln_width equals 3;
or else if count is 2 or 3 then ln_width equals 2;
or else ln_width equals 1.
The rad variable holds a value that starts at Radius and is decreased by 1 x steps for each iteration of the loop.
Now that the circle-set parameters are ready to be calculated all that's needed is to calculate the values to pass to the circleSet() function, that is; the (x,y) co-ordinates and starting radius of each set.
As your original code snippet showed, we can use two nested for-loops for this. one to deal with the vertical and one to deal with the horizontal, but first we need to make some decisions.
If we want to have 10 circle-sets across the canvas then the width of each set would be...
var circleSet_Size = canvas.width / 10;
...and the maximum radius of each circle-set would therefore be...
var circleSet_Radius = circleSet_Size / 2;
We also need to draw two lots of circle-sets to make something like the reference image, one lot for the 'background' and one lot for the 'foreground'. So we need to determine the starting (x,y) co-ordinates of each pass as well as width/height of the area we want to draw over. We can create a function for that too. Something like...
function loopXYPosition(startX, startY) {
for (var y = startY; y < (canvas.height + circleSet_Radius); y += circleSet_Size) {
for (var x = startX; x < (canvas.width + circleSet_Radius); x += circleSet_Size) {
circleSet(x, y, setRadius);
}
}
}
/* 'background' pass */
loopXYPosition(0, 0);
/* 'foreground' pass */
loopXYPosition(circleSet_Radius, circleSet_Radius);
With all that in place we can collate our functions into a single script. But before we do that it's worth taking note of those values which need to be calculated each time a function is called and which values remain static once calculated. Bearing all that in mind we end up with something like...
var circlePattern = (function() {
/* define variables available
to all functions */
var canvas
, ctx
, cWidth
, cHeight
, circleCount
, circleSet_Size
, circleSet_Radius
, P360;
/* draw each cicle */
function drawCircle(x, y, r, LW) {
ctx.beginPath();
ctx.lineWidth = LW;
ctx.arc(x, y, r, 0, P360, true);
ctx.fill();
ctx.stroke();
}
/* calculate each set of circles (circle-set) */
function circleSet(X, Y, R) {
var count = circleCount,
radiusSteps = R / count,
ln_width,
rad;
while (count > 0) {
ln_width = count > 3 ? 2.5 : (count > 1 ? 2 : 1.5);
rad = count * radiusSteps;
drawCircle(X, Y, rad, ln_width);
count--;
}
}
function loopXYPosition(startX, startY) {
/* add circleSet_Radius to canvas width
and height to make sure we draw right
up to the edges of the canvas */
var cHcR = cHeight + circleSet_Radius;
var cWcR = cWidth + circleSet_Radius;
/* to get the effect we want we need to create
a little padding around each circle-set,
therefore we reduce the circelSet_Radius value
by 5% before passing to drawCircleSet() */
var setRadius = circleSet_Radius * 0.95;
/* step across and down canvas in
increments of 'circleSet_Size' */
for (var y = startY; y < cHcR; y += circleSet_Size) {
for (var x = startX; x < cWcR; x += circleSet_Size) {
circleSet(x, y, setRadius);
}
}
}
function begin(NoC, cCount) {
var numberOfCircles = NoC;
/* set variables needed later */
circleSet_Size = cWidth / numberOfCircles;
circleSet_Radius = circleSet_Size / 2;
circleCount = cCount;
/* draw rows of circles */
loopXYPosition(0, 0);
loopXYPosition(circleSet_Radius, circleSet_Radius);
}
/* initialise canvas */
function init(e) {
/* Set variables to use later */
canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d');
cWidth = canvas.width;
cHeight = canvas.height;
P360 = Math.PI * 2;
/* fillStyle & strokeStyle are properties
of the Canvas, so we only need to set
them once here */
ctx.fillStyle = '#fff';
ctx.strokeStyle = '#000';
/* fill canvas (background) */
ctx.fillRect(0, 0, cWidth, cHeight);
/* first argument: number of horizontal circle-sets
second argument: number of circles in each set
!Try different values here! */
begin(7, 6);
}
return {
go: init
};
}());
window.onload = circlePattern.go;
body {
color:#000;
background-color:#fff;
font-family:sans-serif;
}
div.box {
width:100%;
height:auto;
text-align:center;
margin:2em auto;
display:block;
}
div.box h3 {
font-size:1.3em;
line-height:2.3em;
}
#refimg, #canvas {
width:600px;
margin:0 auto;
clear:both;
display:block;
}
#refimg {
height:360px;
}
#canvas {
height:400px;
}
<div class="box">
<h3>Refernece Image</h3>
<img id="refimg" src="http://i.stack.imgur.com/Uxm2Z.jpg)" alt="image" title="reference image" />
</div>
<div class="box">
<h3>Canvas Image</h3>
<canvas id="canvas" width="600" height="400" title="Canvas image"></canvas>
</div>
Not exactly the same results, though pretty close. With the exception of the (x,y) co-ordinates which pass through it the circleSet() function produces the same sequence of values each time it is called, so those values could be calculated once and stored in an Object, but I've included it here for simplicity and to highlight the sequence of events.
Feeding different values into the begin() function and playing with setRadius in the loopXYPosition() function yields some interesting results.
I hope the process outlined here gives you some hints in your continuing exploration of the HTML5 Canvas API.
;)
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>
I created this codepen to show what I got.
I managed to generate a hexagon avatar with progressbar around it using the awesome open source Hexagon Progress jQuery Plugin from Max Lawrence.
He also helped me to improve his own code a little but I don't want to bother him again.
Maybe someone here can help me to round the corners of this hexagon.
I want it to looks something like this (from the awesome Vikinger Html template) but need to be open source because my software is all open source. I can't use the Vikinger code.
So far I read that I have to stop the line before the end and add a quadratic curve to the next line start but I could not managed to do that.
His code do something like this on line 505:
ctx.moveTo(this.coordBack[0].x + offset, this.coordBack[0].y + offset);
for(var i = 0; i < this.coordBack.length; i++) {
ctx.lineTo(this.coordBack[i].x + offset, this.coordBack[i].y + offset);
}
Unfortunatelly, I am not that good in javascript or math.
Two ways to do this. The easy way, and the long winded, lots of math way.
Easy rounded corners
To create simple rounded polygons you can use ctx.arcTo. It will do all the math for the corners.
To create the polygon the following functions create a point and a path (array of points)
const Point = (x,y) => ({x, y});
function polygon(sides, rad, rot = 0) {
var i = 0, step = Math.PI * 2 / sides, path = [];
while (i < sides) {
path.push(Point(Math.cos(i * step + rot) * rad, Math.sin((i++) * step + rot) * rad));
}
return path;
}
To create a hexagon. Note that the polygon is centered over its local origin 0,0
const hexagon = polygon(6, 100);
To render the rounded polygon you need to work from the line segment centers. The following function will stroke the path with the rounded corners.
function strokeRoundedPath(cx, cy, path, radius, style, width) {
ctx.setTransform(1,0,0,1,cx,cy);
var i = 0;
const len = path.length
var p1 = path[i++], p2 = path[i];
ctx.lineWidth = width;
ctx.lineCap = "round";
ctx.strokeStyle = style;
ctx.beginPath();
ctx.lineTo((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
while (i <= len) {
p1 = p2;
p2 = path[(++i) % len];
ctx.arcTo(p1.x, p1.y, (p1.x + p2.x) / 2, (p1.y + p2.y) / 2, radius);
}
ctx.closePath();
ctx.stroke();
ctx.setTransform(1,0,0,1,0,0);
}
strokeRoundedPath(200, 200, hexagon, 20, "#000", 18);
Progress bar
Creating a progress bar is not as simple as the starting point can not be on a rounded corner, and moving over the rounded corners will need a lot of math to get the correct coordinates. This will negate the point of using easy arcToand need us to write the equivalent in JS (Way to slack for that today)
Using line dash for progress
There is however a hack that uses the line dash to create the effect you may be happy with. The snippet demonstrates this
const barWidth = 10;
const cornerRadius = barWidth * 2 + 8;
const polyRadius = 100;
const inset = 1;
const barRadius = polyRadius - barWidth * inset;
var progress = 0.0;
const approxLineLen = barRadius * Math.PI * 2;
const hexBar = polygon(6, barRadius);
const hexPoly = polygon(6, polyRadius);
const hexPolyInner = polygon(6, polyRadius - barWidth * 2 * inset);
const ctx = canvas.getContext("2d");
ctx.setLineDash([approxLineLen]);
loop()
function point(x,y) { return {x, y} }
function polygon(sides, radius, rot = 0) {
var i = 0;
const step = Math.PI * 2 / sides, path = [];
while (i < sides) {
path.push(point(Math.cos(i * step + rot) * radius, Math.sin((i++) * step + rot) * radius));
}
return path;
}
function roundedPath(path, radius) {
var i = 0, p1 = path[i++], p2 = path[i];
const len = path.length
ctx.moveTo((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
while (i <= len) {
p1 = p2;
p2 = path[(++i) % len];
ctx.arcTo(p1.x, p1.y, (p1.x + p2.x) / 2, (p1.y + p2.y) / 2, radius);
}
}
function strokeRoundedPath(cx, cy, path, radius, style, width) {
ctx.setTransform(1,0,0,1,cx,cy);
ctx.lineWidth = width;
ctx.lineCap = "round";
ctx.strokeStyle = style;
ctx.beginPath();
roundedPath(path, radius);
ctx.closePath();
ctx.stroke();
}
function fillRoundedPath(cx, cy, path, radius, style) {
ctx.setTransform(1,0,0,1,cx,cy);
ctx.fillStyle = style;
ctx.beginPath();
roundedPath(path, radius);
ctx.fill();
}
function loop() {
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,canvas.width,canvas.height);
fillRoundedPath(polyRadius, polyRadius, hexPoly, cornerRadius, "#000");
fillRoundedPath(polyRadius, polyRadius, hexPolyInner, cornerRadius - barWidth * inset * 2, "#F80");
ctx.lineDashOffset = approxLineLen - (progress % 1) * approxLineLen;
strokeRoundedPath(polyRadius, polyRadius, hexBar, cornerRadius - barWidth * inset, "#09C", barWidth);
progress += 0.005;
requestAnimationFrame(loop);
}
<canvas id="canvas" width = "210" height="210"></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>