I am trying to draw an object using numerous points and I am trying to assign a unique color to each point by assigning the color to strokeStyle. I am using HSL style of coloring.
It is only taking the first color or black.
Below is the code that I have tried.
const c = document.getElementById("myCanvas");
c.width=window.innerWidth;
c.height=window.innerHeight - 150;
let ctx = c.getContext("2d");
let cx = c.width/2, cy = c.height/2;
let n = 6, d = 71;
ctx.translate(cx,cy);
ctx.save();
ctx.beginPath();
for(let i = 0; i < 361; i++){
let k = i * d * Math.PI/180;
let r = 150 * Math.sin(n*k);
let x = r * Math.cos(k);
let y = r * Math.sin(k);
ctx.arc(x, y, 0.5, 0, 2 * Math.PI);
ctx.strokeStyle = "hsl("+Math.random() * 360 | 0+",100%,50%)"; // assign a random color to each point
}
ctx.stroke();
<canvas id="myCanvas"></canvas>
If you want to change the color for each point you need to begin a new path with every step of the loop. The arcs in your code are just small dots. If you want to see anything you need to draw lines between the previous point (last) and the new one.
In my code I've commented out the part where you draw the arc.
const c = document.getElementById("myCanvas");
c.width=window.innerWidth;
c.height=window.innerHeight;
let ctx = c.getContext("2d");
let cx = c.width/2, cy = c.height/2;
let n = 6, d = 71;
ctx.translate(cx,cy);
ctx.save();
let last = {x:0,y:0}
for(let i = 0; i < 361; i++){
let k = i * d * Math.PI/180;
let r = 150 * Math.sin(n*k);
let x = r * Math.cos(k);
let y = r * Math.sin(k);
ctx.beginPath();
ctx.moveTo(last.x,last.y);
ctx.lineTo(x,y)
ctx.strokeStyle = "hsl("+ Math.random() * 360 + ",100%,50%)";
ctx.stroke();
/*
ctx.beginPath();
ctx.arc(x, y, 0.5, 0, 2 * Math.PI);
ctx.stroke();*/
last={x,y}
}
<canvas id="myCanvas"></canvas>
The main reason is the ctx.stroke(); command. You are not actually drawing anything in the loop. Just constructing a very long path. At the point where you call the stroke() method it strokes the entire path with the current color. You need to find a way to break up the the paths into the segments you want and stroke each one independently. That is, call beginPath() when you want a new segment and stroke() when that segment is ready to be drawn.
This answer also provides some details.
Related
I have drawn a circle with canvas using following code.
<div class="abc"><canvas id="myCanvas">HTML5 canvas tag.</canvas></div>
<script>
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.arc(100,75,50,-0.1*Math.PI,1.7*Math.PI);
ctx.lineWidth = 15;
ctx.strokeStyle = "#c32020";
ctx.stroke();
var c2 = document.getElementById("myCanvas");
var ctx2 = c2.getContext("2d");
ctx2.beginPath();
ctx2.arc(100,75,50,1.7*Math.PI,-0.1*Math.PI);
ctx2.lineWidth = 15;
ctx2.strokeStyle = "#a9a9a9";
ctx2.stroke();
</script>
Here is the result on the web browser.
Now I need to call two different javascript function when the user clicks on red part and gray part of the circle.
On click of the red part it should call function1 and on click of gray part, it should call function2.
I've tried a lot but didn't succeed. I need some expert's help to implement it.
Letting the browser do (most of) the work
On click of the red part it should call function1 and on click of gray part, it should call function2.
You can use reusable path objects to store the different paths you want to test against and then use isPointInPath() with path and coordinates as arguments.
By checking each path for a hit you can assign a different call based on for example index.
And there is no need to do this:
var c2 = document.getElementById("myCanvas");
var ctx2 = c2.getContext("2d");
This will simply reference the same context as a canvas can only have one - if requested more than once the same will be given (or null if different type).
How to use multiple click event on a canvas
You can share the click handler to do what you want as shown below -
For modern browsers you can use both Path2D objects to store path information you want to use later (I'll address older browsers in the second example).
Example
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var p1 = new Path2D();
var p2 = new Path2D();
var paths = [p1, p2]; // store paths in array for check later
// store arc parts to respective path objects
p1.arc(100, 75, 50, -0.1 * Math.PI, 1.7 * Math.PI); // red part
p2.arc(100, 75, 50, 1.7 * Math.PI, -0.1 * Math.PI); // grey part
// render the two path objects using a common context, but different style
ctx.lineWidth = 15;
ctx.strokeStyle = "#c32020";
ctx.stroke(p1);
ctx.strokeStyle = "#a9a9a9";
ctx.stroke(p2);
// check for clicks on common canvas
c.onclick = function(e) {
var rect = this.getBoundingClientRect(), // adjust click coordinates
x = e.clientX - rect.left,
y = e.clientY - rect.top;
// iterate through path array to test each path for hits
for(var i = 0; i < paths.length; i++) {
if (ctx.isPointInStroke(paths[i], x, y)) { // check which path
console.log("Path " + (i+1) + " clicked");
break;
}
}
};
<canvas id="myCanvas"></canvas>
Browser compatibility
Older browsers however, won't support the isPointInStroke(), such as IE11.
For this scenario you can use simple math to figure out if the click is within the radius of the arcs:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.lineWidth = 15;
ctx.arc(100, 75, 50, -0.1 * Math.PI, 1.7 * Math.PI); // red part
ctx.strokeStyle = "#c32020";
ctx.stroke();
ctx.beginPath();
ctx.arc(100, 75, 50, 1.7 * Math.PI, -0.1 * Math.PI); // grey part
ctx.strokeStyle = "#a9a9a9";
ctx.stroke();
// check for clicks on common canvas
c.onclick = function(e) {
var rect = this.getBoundingClientRect(), // adjust click coordinates
x = e.clientX - rect.left,
y = e.clientY - rect.top,
diffX = 100 - x,
diffY = 75 - y,
len = diffX*diffX + diffY*diffY, // we don't need to square-root it:
r1 = 43*43, // it's faster to scale up radius (50-50% of linewidth)
r2 = 57*57; // radius + 50% of linewidth
// are we on the edge of the circle?
if (len >= r1 && len <= r2) {
// now find angle to see if we're in red or grey area
var angle = Math.atan2(diffY, diffX);
if (angle > 0.7 * Math.PI && angle < 0.9 * Math.PI) {
console.log("Grey part");
}
else {
console.log("Red part");
}
}
};
<canvas id="myCanvas"></canvas>
Note that a special case in the latter example, i.e. when the arc crosses the 0/360° point, you will want to split the checks [0, angle> and [angle, 360>.
You will need to add a click event to the canvas element and calculate if the red or gray is clicked.
Here's some code to get you started, but you'll still have to figure out what coordinates your circle area has.
var elem = document.getElementById('myCanvas'),
elemLeft = elem.offsetLeft,
elemTop = elem.offsetTop;
// Add event listener for `click` events.
elem.addEventListener('click', function(event) {
var x = event.pageX,
y = event.pageY;
// use x and y to determine if the colored regions are clicked and execute code accordingly
}, false);
Code gotten from here: How do I add a simple onClick event handler to a canvas element?
I have set up a sort of a reflection vector simulator here: https://jsfiddle.net/ahvonenj/1re5n8jg/
I am using Vectorious for my vector calculations and objects.
The program kind of works:
I am casting rays from the middle of the canvas towards the mouse cursor. I can successfully detect ray intersections with walls and the point of the intersection on the wall (red dots). I am also able to get the middle point of the wall (blue dots). I THINK that when I normalize the middle point vector, that is the normal of the wall.
Here is the part that listens to mouse movement, casts the ray, gets the intersection and tried to calculate the reflection vectors ( I have added some comments regarding the actual calculations for the reflection vector and what I think I am calculating):
Global.$canvas.addEventListener('mousemove', function(e)
{
var ctx = Global.ctx;
var rect = canvas.getBoundingClientRect();
Global.Mouse = new Vector([e.clientX - rect.left, e.clientY - rect.top]);
var v1 = Vector.subtract(Global.Mouse, Global.Canvas.Center).normalize();
var mag = Vector.subtract(Global.Canvas.Center, Global.Mouse).magnitude();
var ray = Vector.scale(v1, mag * 3).add(Global.Canvas.Center);
ctx.clearRect(0, 0, 600, 600);
ctx.beginPath();
for(var i = 0; i < Global.Canvas.Walls.length; i++)
{
var wall = Global.Canvas.Walls[i];
if(rayIntersectsWith(wall, [Global.Canvas.Center, ray]))
{
// x_1' - x_0 = v - 2(v dot ñ)ñ
// Where ñ = Normal of the wall
// and v = Vector from center to the point of intersection with the wall
// http://mathworld.wolfram.com/Reflection.html
// This is the point on wall where the intersection happens
var point = rayIntersectionPoint(wall, [Global.Canvas.Center, ray])
// This is the full ray cast from center towards the mouse
var d = ray;
// This (probably) is the vector from center to the intersection point on the wall
var v = Vector.subtract(point, Global.Canvas.Center);
// This represents the wall vector, or a middle of the wall (blue dot on wall)
var wallVector = Vector.add(wall[0], wall[1]).scale(0.5);
// This is supposed to be the normal of the wall
var wallNormal = Vector.normalize(wallVector);
// This is supposed to be the 2(v dot ñ) part of the equation
var v_dot_n = Vector.dot(v, wallNormal) * 2;
// This is supposed to be the v_dot_n * ñ of the equation
var v_dot_n_scaled_by_n = Vector.scale(wallNormal, v_dot_n);
// This is supposed to be the v - v_dot_n_scale_by_n part of the equation
var dot_vector = Vector.subtract(v, v_dot_n_scaled_by_n);
console.log('w1', wall[0].x, wall[0].y, ", w2", wall[1].x, wall[1].y)
var bounceVector = dot_vector
console.log(wallVector.x, wallVector.y, wallVector.magnitude())
console.log(wallNormal.x, wallNormal.y, wallNormal.magnitude())
ctx.beginPath();
ctx.arc(wallVector.x, wallVector.y, Global.isecRadius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'blue';
ctx.fill();
ctx.lineWidth = 0;
ctx.strokeStyle = 'blue';
ctx.beginPath();
ctx.arc(dot_vector.x, dot_vector.y, Global.isecRadius * 1.5, 0, 2 * Math.PI, false);
ctx.fillStyle = 'blue';
ctx.fill();
ctx.lineWidth = 0;
ctx.strokeStyle = 'blue';
ctx.beginPath();
ctx.lineWidth = 3;
ctx.strokeStyle = '#00FF00';
ctx.moveTo(wall[0].x, wall[0].y);
ctx.lineTo(wall[1].x, wall[1].y);
ctx.stroke();
console.log(bounceVector.x, bounceVector.y)
ctx.beginPath();
ctx.arc(bounceVector.x, bounceVector.y, Global.isecRadius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'red';
ctx.fill();
ctx.lineWidth = 0;
ctx.strokeStyle = 'red';
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = '#000000';
ctx.moveTo(point.x, point.y);
ctx.lineTo(bounceVector.x, bounceVector.y);
ctx.stroke();
ctx.beginPath();
ctx.arc(point.x, point.y, Global.isecRadius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'red';
ctx.fill();
ctx.lineWidth = 0;
ctx.strokeStyle = 'red';
}
}
ctx.stroke();
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = '#000000';
ctx.moveTo(Global.Canvas.Center.x, Global.Canvas.Center.y);
ctx.lineTo(ray.x, ray.y);
ctx.stroke();
});
My calculations are based on equations found here: http://mathworld.wolfram.com/Reflection.html
But if you try the fiddle, you will notice that the reflection calculations barely work for the angled wall and currently do not work at all for the outside walls, so this is where I need some help.
The equation looks like this:
x_1'- x_0 = v - 2(v·ñ)ñ.
Where x_1´ - x_0 is the reflection vector from the intersection point of the wall to where ever it should be pointing. Probably the same length as v.
v is the vector from x_1 to the intersection point of the wall, so in my code it is named as v and goes from the center to the wall intersection.
ñ is the normal of the wall.
Rest of the equation should also be commented in my code and the variables named accordingly I hope.
I am a bit confused with the stuff subtracted from v in the equation. v dot ñ returns a scalar, which is then multiplied by 2. However ñ is a vector so 2(v dot ñ) must mean scaling vector ñ by the scalar value of 2(v dot ñ)?
Best guess I have been able to conclude from debugging this thing is that the wall normal is most likely note correctly calculated.
From my own Geom library I have.
// v1, v2 are Vec
// reflected vec for line hitting this
// argument line is the line to be reflected
// retVec is the returned resulting vector.
reflectAsVec : function(line, retVec = new Vec()){ // returns a vector...
v2.x = this.p2.x - this.p1.x;
v2.y = this.p2.y - this.p1.y;
v1.x = line.p2.x - line.p1.x;
v1.y = line.p2.y - line.p1.y;
var len = v1.dot(v2.norm()) * 2;
retVec.x = v2.x * len - v1.x;
retVec.y = v2.y * len - v1.y;
return retVec;
},
this is a line with two points p1,p2 defining the ends. Note the v2 is normalised for the last two lines calculating the reflected vec.
The function v2.norm() converts a Vec to a normalised vector as follows
// u is a number
norm : function(){ // normalises this to be a unit length.
u = Math.hypot(this.x,this.y);
this.x /= u;
this.y /= u;
return this; // returns this
},
And for v1.dot(vec)
dot : function(vec){ // get the dot product of this and {avec}
return this.x * vec.x + this.y * vec.y; // returns number
},
Once you have the reflected vector you can create the reflected line from the intercept point and the reflected vector.
Can you please take a look at this demo and let me know how I can draw multiple circles in a canvas with different coordinate without repeating bunch of codes?
As you can see on Demo and following code
var ctx = $('#canvas')[0].getContext("2d");
ctx.fillStyle = "#00A308";
ctx.beginPath();
ctx.arc(150, 50, 5, 0, Math.PI * 2, true);
ctx.arc(20, 85, 5, 0, Math.PI * 2, true);
ctx.arc(160, 95, 5, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
I tried to have them under ctx but it is not correct so I tried to use for loop to create 50 points but I have issue on repeating and adding code like ctx.fill(); for all of them.
Can you please let me know how I can fix this?
Thanks
Constantly creating and closing new paths is not good advice.
You should batch together all fills / strokes of the same style, and execute them in a single draw call. The performance difference between these approaches becomes apparent very quickly with increasing polygon count.
The way to go about it is to move the pen and make the path construction call for each circle; stroke/fill in the end in one shot. However, there's a quirk involved here. When you have the point moved to the center and draw the circle, you'd still see a horizontal radius line, drawn from the center of the circle, to the circumference.
To avoid this artefact, instead of moving to the center, we move to the circumference. This skips the radius drawing. Essentially all these commands are for tracing a path and there's no way to describe a discontinuity without calling closePath; usually moveTo does it but HTML5 canvas API it doesn't. This is a simple workaround to counter that.
const pi2 = Math.PI * 2;
const radius = 5;
ctx.fillStyle = '#00a308';
ctx.beginPath();
for( let i=0, l=coords.length; i < l; i++ )
{
const p = coords[i],
x = p.x,
y = p.y;
ctx.moveTo( x + radius, y ); // This was the line you were looking for
ctx.arc( x, y, radius, 0, pi2 );
}
// Finally, draw outside loop
ctx.stroke();
ctx.fill();
Also worth considering, is using transformations, and drawing everything relative to a local frame of reference.
ctx.fillStyle = '#00a308';
ctx.beginPath();
for( let i=0, l=coords.length; i < l; i++ )
{
const p = coords[i];
ctx.save();
ctx.translate( p.x + radius, p.y );
ctx.moveTo( 0, 0 );
ctx.arc( 0, 0, radius, 0, pi2 );
ctx.restore();
}
ctx.stroke();
ctx.fill();
This is because you are not closing the path, either using fill() or closePath() will close the path so it does not try and connect all the items. fill() fills in the circles and closes the path so we can just use that. Also you need to use beginPath(), so that they are separate from each other. Here is your three circles:
var coords = [ [150,50], [20,85], [160,95] ];
for(var i = 0; i < coords.length; i++){
ctx.beginPath();
ctx.arc(coords[i][0], coords[i][1], 5, 0, Math.PI * 2, true);
ctx.fill();
}
To not repeat a bunch of code and have unique coordinates store your X and Y position in an array and use a for loop to go through it.
Update:
A more efficient way to do which achieves the same effect this would be to only use a single path and use moveTo() instead of creating a new path when drawing each circle:
ctx.beginPath();
for(var i = 0; i < coords.length; i++){
ctx.moveTo(coords[i][0], coords[i][1]);
ctx.arc(coords[i][0], coords[i][1], 5, 0, Math.PI * 2, true);
}
ctx.fill();
ctx.beginPath();
points.forEach(point => {
ctx.moveTo( point.x, point.y );
ctx.arc(point.x,point.y,1,0,Math.PI*2,false);
});
ctx.fill();
You can easily create several circles with a for loop. You really just need to draw an arc and fill it each time. Using your example, you could do something like this.
var ctx = $('#canvas')[0].getContext("2d");
ctx.fillStyle = "#00A308";
for (var i = 0; i < 3; i++) {
ctx.arc(50 * (i+1), 50 + 15 * i, 5, 0, Math.PI * 2, true);
ctx.fill();
}
Example Fiddle of lots of circles in different locations drawn in a loop.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext( '2d' );
var cx = canvas.width/2;
var cy = canvas.height/2;
ctx.fillStyle = "#00A308";
var total_circles = 50;
var radius = 100;
for(i = 0; i < total_circles; i++){
var angle = i * 2 * Math.PI/total_circles;
var x = cx + Math.cos(angle) * radius;
var y = cy + Math.sin(angle) * radius;
ctx.beginPath();
ctx.arc(x, y, 2, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
}
I have a pie chart in canvas and I wanted to plot random points in each sector of that pie.
I have got the area of each sector. using the arc sector
var arcsector = Math.PI * (2 * sector / total);
var startAngle = (lastend - offset) * (radius/Math.PI);
var endAngle = (lastend + arcsector - offset) * (radius/Math.PI);
var sectorAngle = arcsector * (radius/Math.PI);
var sectorArea = .5 * (sectorAngle*Math.PI/180) * (radius*radius);
How can I randomly plot points within that area?
A pie is a part of a circle, which, with your notations, starts at startAngle and ends at endAngle.
Most simple way to get a random point is to build a random angle (between
startAngle and endAngle) and a random radius, then you have your point with those lines :
var randAngle = startAngle + Math.random()*( endAngle - startAngle );
var randRadius = Math.random()*radius;
var randX = centerX + randRadius * Math.cos(randAngle);
var randY = centerY + randRadius * Math.sin(randAngle);
ctx.fillRect ( randX, randY, 1, 1 ) ;
repeat the number of times required !
The simple approach is to:
Create a temporary arc shape on path
Create a random point
Hit-test the point against the shape and plot if inside
You can create a temporary arc path by doing something like this (adjust to match your situation) (and no need to stroke/fill):
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(cx, cy, radius, startAngle, endAngle);
ctx.closePath();
Then create random points within the bounds of that arc, or just use a very basic approach (which is probably fast enough in most case unless you would need a lot of points) - and the spread is even compared to using a radius based approach:
var randomX = cx + radius * 2 * Math.random() - radius;
var randomY = cy + radius * 2 * Math.random() - radius;
and finally hit-test:
if (ctx.isPointInPath(randomX, randomY)) {
// plot point, count etc.
}
FIDDLE
Update
An even more efficient way to generate random points in the arc shape (and spread them more even) is to draw directly to an off-screen canvas without using any bound checking and no cos/sin operations, which are expensive, and finally composite that on top of your arc shape (or use arc as clip).
// create off-screen canvas
var ocanvas = document.createElement('canvas');
var octx = ocanvas.getContext('2d');
var d;
d = ocanvas.width = ocanvas.height = 300;
octx.fillStyle = '#fff';
while(count) {
var randomX = d * Math.random();
var randomY = d * Math.random();
octx.fillRect(randomX - 1, randomY - 1, 2, 2);
count--;
}
// composite random points with main arc
ctx.globalCompositeOperation = 'source-atop';
ctx.drawImage(ocanvas, 0, 0);
ctx.globalCompositeOperation = 'source-over';
It can be optimized further by having the off-screen canvas represent only the bounds of the arc shape.
FIDDLE
Demo: http://jsfiddle.net/jv6nP/3/
it's not perfect that points are at border and thus their radius being bigger than zero makes them overlap onto other parts of pie. And this also results in them going over black border.
var can = $('#can')[0].getContext('2d'),
border=2,
x=100,
y=75,
r=60,
sRadius= 0,
leadAngle=null,
points= [],
dotRadius=2,
data = {
water:[30,'#5CC5FA'],
earth:[60,'#F0A71F'],
air:[10,'#26EDE3']
};
function reDraw(){
//making border...
can.beginPath();
can.arc(x,y,r+border,0,2*Math.PI);
can.fillStyle='black';
can.fill();
var newAngle=null;
for (var k in data) { //making piechart..
leadAngle = (2*Math.PI)*(data[k][0]/100);
newAngle = sRadius+leadAngle;
calPoints(sRadius,leadAngle,k);
can.beginPath();
can.arc(x,y,r,sRadius,newAngle);
can.lineTo(x,y);
can.fillStyle=data[k][1];
can.fill();
sRadius= newAngle;
}
//calculating points..
function calPoints(s,e,name) {
if (name!='water') return;
var py,px,rAngle,rRad;
for (var i=0; i<15; i++) {
rAngle=s+Math.random()*(e);
rRad = Math.random()*r;
px = (Math.cos(rAngle) * rRad)+x;
py = (Math.sin(rAngle) * rRad)+y;
points.push([px,py]);
}
}
//plotting dots from data...
points.forEach(function(v){
can.beginPath();
can.arc(v[0],v[1],dotRadius,0,2*Math.PI);
can.fillStyle='fff';
can.fill();
});
points=[];
requestAnimationFrame(reDraw);
}
reDraw();
Here is my question : I have an animation, that builds are circle. See : http://jsfiddle.net/2TUnE/
JavaScript:
var currentEndAngle = 0
var currentStartAngle = 0;
var currentColor = 'black';
setInterval(draw, 50);
function draw() { /***************/
var can = document.getElementById('canvas1'); // GET LE CANVAS
var canvas = document.getElementById("canvas1");
var context = canvas.getContext("2d");
var x = canvas.width / 2;
var y = canvas.height / 2;
var radius = 75;
var startAngle = currentStartAngle * Math.PI;
var endAngle = (currentEndAngle) * Math.PI;
currentEndAngle = currentEndAngle + 0.01;
var counterClockwise = false;
context.beginPath();
context.arc(x, y, radius, startAngle, endAngle, counterClockwise);
context.lineWidth = 15;
// line color
context.strokeStyle = currentColor;
context.stroke();
/************************************************/
}
When the circle is completely drawn, I would like it to start erasing, the same way it was created (so slowly removes the black). Once the whole circle is erased, i would create the black circle again, creating some kind of "waiting / loading" effect.
What i tried to do, is check if the currentEndAngle is 2 (so the circle is complete), and then move the startAngle, but it didn't work.
Any idea?
Thanks!
EDIT : Forgot to say, the animation is gonna be over an image, so it has to be "transparent" and not white
Look whats up in this JSFiddle: http://jsfiddle.net/fNTsA/
This method is basically your code, only we use a modulo to control state. Checking if the radius is 2 is only half-right, to toggle drawing white or drawing black you should do half the radius modulo 2. The first time around you have floor(0..2/2) % 2 == 0, the second you have floor(2..4/2) % 2 == 1, and so on.
Also because the line is antialiased, it helps to have the start angle overwrite what's been drawn already, otherwise you get extra white lines you probably don't want. For the same reason, when drawing the white circle, you should draw a slightly thicker line (smaller radius, thicker line). Otherwise the antialiasing leaves behind some schmutz -- a faint outline of the erased circle.
I put the radius and width into globals which you'd put at the top:
var lineRadius = 75;
var lineWidth = 15;
And likewise this is my modulo thing, pretty standard:
currentStartAngle = currentEndAngle - 0.01;
currentEndAngle = currentEndAngle + 0.01;
if (Math.floor(currentStartAngle / 2) % 2) {
currentColor = "white";
radius = lineRadius - 1;
width = lineWidth + 3;
} else {
currentColor = "black";
radius = lineRadius;
width = lineWidth;
}
Fun challenge! Try the following (updated fiddle here). I've tried to include plenty of comments to show my thinking.
// Moved these to global scope as you don't want to re-declare
// them in your draw method each time your animation loop runs
var canvas = document.getElementById("canvas1");
var context = canvas.getContext("2d");
var x = canvas.width / 2;
var y = canvas.height / 2;
var radius = 75;
// Use objects to hold our draw and erase props
var drawProps = {
startAngle: 0,
speed: 2,
color: 'black',
counterClockwise: false,
globalCompositeOperation: context.globalCompositeOperation,
lineWidth: 15
};
var eraseProps = {
startAngle: 360,
speed: -2,
color: 'white',
counterClockwise: true,
globalCompositeOperation: "destination-out",
lineWidth: 17 // artefacts appear unless we increase lineWidth for erase
};
// Let's work in degrees as they're easier for humans to understand
var degrees = 0;
var props = drawProps;
// start the animation loop
setInterval(draw, 50);
function draw() { /***************/
degrees += props.speed;
context.beginPath();
context.arc(
x,
y,
radius,
getRadians(props.startAngle),
getRadians(degrees),
props.counterClockwise
);
context.lineWidth = props.lineWidth;
context.strokeStyle = props.color;
context.stroke();
// Start erasing when we hit 360 degrees
if (degrees >= 360) {
context.closePath();
props = eraseProps;
context.globalCompositeOperation = props.globalCompositeOperation;
}
// Start drawing again when we get back to 0 degrees
if (degrees <= 0) {
canvas.width = canvas.width; // Clear the canvas for better performance (I think)
context.closePath();
props = drawProps;
context.globalCompositeOperation = props.globalCompositeOperation;
}
/************************************************/
}
// Helper method to convert degrees to radians
function getRadians(degrees) {
return degrees * (Math.PI / 180);
}