Drawing a 'bullseye' in HTML5 Canvas using javascript - javascript

I'm struggling a bit to get this to work. I have a 'Canvas' element on my web page, and I need to 'draw' filled circles within each other. I need to use a loop to draw the pattern, alternating between red and blue filled circles. It will use the initial band width value of 25. It will repeat the loop as long as the current radius is greater than 0. It will use a slider to control the band width. The slider has a minimum value of 5,
maximum value of 50 with step 5, and current value as 25. As the value of
the slider changes, it draws the pattern with the current bandwidth. I can make this work with gradients, but that does not do what I need it to do and it does not look right. Here is what I have so far:
var sliderModule = (function(win, doc) {
win.onload = init;
// canvas and context variables
var context;
// center of the pattern
var centerX, centerY;
function init() {
canvas = doc.getElementById("canvas");
context = canvas.getContext("2d");
centerX = canvas.width / 2;
centerY = canvas.height / 2;
// draw the initial pattern
//drawPattern();
}
// called whenever the slider value changes
function drawPattern() {
var canvas;
// clear the drawing area
context.clearRect(0, 0, canvas.width, canvas.height);
// get the current radius
var radius = doc.getElementById("radius").value;
// set fill color to red
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const colors = ['#F00', '#0F0', '#00F'];
const outerRadius = 100;
let bandSize = 10; // this would be where you put the value for your slider
for (let r = outerRadius, colorIndex = 0; r > 0; r -= bandSize, colorIndex = (colorIndex + 1) % colors.length) {
ctx.fillStyle = colors[colorIndex];
ctx.beginPath();
ctx.arc(canvas.width / 2, canvas.height / 2, r, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
}
}
return {
drawPattern: drawPattern
};
})
(window, document);
<!doctype html>
<html>
<head>
<title></title>
<meta charset="utf-8">
<script src="bullsEye.js"></script>
</head>
<body>
<canvas id="canvas" width="400" height="400"></canvas>
<label for="bandwidth">BandWidth:</label>
<input type="range" id="radius" min="5" max="50" step="5" value="25" oninput="sliderModule.drawPattern()" />
</body>
</html>
var sliderModule = (function(win, doc) {
win.onload = init;
// canvas and context variables
var canvas;
var context;
// center of the pattern
var centerX, centerY;
function init() {
canvas = doc.getElementById("testCanvas");
context = canvas.getContext("2d");
centerX = canvas.width / 2;
centerY = canvas.height / 2;
// draw the initial pattern
drawPattern();
}
// called whenever the slider value changes
function drawPattern() {
// clear the drawing area
context.clearRect(0, 0, canvas.width, canvas.height);
// get the current radius
var radius = doc.getElementById("radius").value;
// set fill color to red
context.fillStyle = '#FF0000';
// draw the pattern
context.beginPath();
context.arc(centerX, centerY, radius, 0, 2 * Math.PI);
context.fill();
context.closePath();
}
return {
drawPattern: drawPattern
};
})(window, document);

Your snippet doesn't quite work as provided, but given a value for your slider, you'll just reduce radius and loop while radius is greater than 0.
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 200;
canvas.height = 200;
const colors = ['#F00', '#0F0', '#00F'];
const outerRadius = 100;
let bandSize = 10; // this would be where you put the value for your slider
for (let r = outerRadius, colorIndex = 0; r > 0; r -= bandSize, colorIndex = (colorIndex + 1) % colors.length) {
ctx.fillStyle = colors[colorIndex];
ctx.beginPath();
ctx.arc(canvas.width / 2, canvas.height / 2, r, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
}
<canvas />
What you're missing is the loop which would change. To control the colors, I made an array, and in addition to changing my radius each for-loop iterator, I also change the colorIndex.
I use (colorIndex + 1) % colorIndex.length so it'll loop through each of them and not go beyond the index (it'll count 0, 1, 2, and back to 0). You can change or add colors to the array.

Related

HTML5 Canvas: how i can deal with inverted translate() after rotation?

I need to apply several matrix transformations before drawing a shape, however (if on somewhere) I use rotate() the coordinates are inverted and/or reversed and cannot continue without knowing if the matrix was previously rotated.
How can solve this problem?
Example:
<canvas width="300" height="300"></canvas>
<script>
let canvas = document.querySelector("canvas");
let ctx = canvas.getContext("2d");
ctx.fillStyle = "silver";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = "black";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, canvas.height/2);
ctx.lineTo(canvas.width, canvas.height/2);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(canvas.width/2, 0);
ctx.lineTo(canvas.width/2, canvas.height);
ctx.stroke();
ctx.translate(150, 150);
ctx.rotate(-90 * 0.017453292519943295);
ctx.translate(-150, -150);
// move the red rectangle 100px to the left (top-left)
// but instead is moved on y axis (right-bottom)
ctx.translate(-100, 0);
// more matrix transformations
// ....
// ....
// now finally draw the shape
ctx.fillStyle = "red";
ctx.fillRect(150, 150, 100, 50);
</script>
Can be this Translation after rotation the solution?
It looks like you aren't resetting the canvas matrix each time you make a new transformation.
The Canvas API has the save() and restore() methods. Canvas states are stored on a stack. Every time the save() method is called, the current drawing state is pushed onto the stack. A drawing state consists of transformations that have been applied along with the attributes of things like the fillStyle. When you call restore(), the previous settings are restored.
// ...
ctx.save(); // save the current canvas state
ctx.translate(150, 150);
ctx.rotate(-90 * 0.017453292519943295);
ctx.translate(-150, -150);
ctx.restore(); // restore the last saved state
// now the rectangle should move the correct direction
ctx.translate(-100, 0);
Check out this link for more information on the save and restore methods.
OK finally, i solved the problem by rotating the translation point before applying it. This function does the trick:
function helperRotatePoint(point, angle) {
let s = Math.sin(angle);
let c = Math.cos(angle);
return { x: point.x * c - point.y * s, y: point.x * s + point.y * c};
}
rotating the translation point using the inverted angle I obtain the corrected translation
helperRotatePoint(translation_point, -rotation_angle);
working code:
let canvas = document.querySelector("canvas");
// proper size on HiDPI displays
canvas.style.width = canvas.width;
canvas.style.height = canvas.height;
canvas.width = Math.floor(canvas.width * window.devicePixelRatio);
canvas.height = Math.floor(canvas.height * window.devicePixelRatio);
let ctx = canvas.getContext("2d");
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
ctx.fillStyle = "whitesmoke";
ctx.fillRect(0, 0, canvas.width, canvas.height);
class UIElement {
constructor(x, y, width, height, color) {
// PoC
this.draw_pos = {x, y};
this.draw_size = {width, height};
this.color = color;
this.rotate = 0;
this.scale = {x: 1, y: 1};
this.translate = {x: 0, y: 0};
this.skew = {x: 0, y: 0};
this.childs = [];
}
addChild(uielement) {
this.childs.push(uielement);
}
helperRotatePoint(point, angle) {
let s = Math.sin(angle);
let c = Math.cos(angle);
return {
x: point.x * c - point.y * s,
y: point.x * s + point.y * c
};
}
draw(cnvs_ctx, parent_x, parent_y) {
// backup current state
cnvs_ctx.save();
let elements_drawn = 1;// "this" UIElement
// step 1: calc absolute coordinates
let absolute_x = parent_x + this.draw_pos.x;
let absolute_y = parent_y + this.draw_pos.y;
// step 2: apply all transforms
if (this.rotate != 0) {
cnvs_ctx.translate(absolute_x, absolute_y)
cnvs_ctx.rotate(this.rotate);
cnvs_ctx.translate(-absolute_x, -absolute_y);
// rotate translate point before continue
let tmp = this.helperRotatePoint(this.translate, -this.rotate);
// apply rotated translate
cnvs_ctx.translate(tmp.x, tmp.y);
} else {
cnvs_ctx.translate(this.translate.x, this.translate.y);
}
cnvs_ctx.scale(this.scale.x, this.scale.y);
cnvs_ctx.transform(1, this.skew.y, this.skew.x, 1, 0, 0);
// step 3: self draw (aka parent element)
cnvs_ctx.fillStyle = this.color;
cnvs_ctx.fillRect(absolute_x, absolute_y, this.draw_size.width, this.draw_size.height);
// step 4: draw childs elements
for (let i = 0; i < this.childs.length ; i++) {
elements_drawn += this.childs[i].draw(
cnvs_ctx, absolute_x, absolute_y
);
}
// done, restore state
cnvs_ctx.restore();
return elements_drawn;
}
}
// spawn some ui elements
var ui_panel = new UIElement(120, 50, 240, 140, "#9b9a9e");
var ui_textlabel = new UIElement(10, 10, 130, 18, "#FFF");
var ui_image = new UIElement(165, 25, 90, 60, "#ea9e22");
var ui_textdesc = new UIElement(17, 46, 117, 56, "#ff2100");
var ui_icon = new UIElement(5, 5, 10, 10, "#800000");
ui_panel.addChild(ui_textlabel);
ui_panel.addChild(ui_image);
ui_panel.addChild(ui_textdesc);
ui_textdesc.addChild(ui_icon);
// add some matrix transformations
ui_textdesc.skew.x = -0.13;
ui_textdesc.translate.x = 13;
ui_image.rotate = -90 * 0.017453292519943295;
ui_image.translate.y = ui_image.draw_size.width;
ui_panel.rotate = 15 * 0.017453292519943295;
ui_panel.translate.x = -84;
ui_panel.translate.y = -50;
// all ui element elements
ui_panel.draw(ctx, 0, 0);
<canvas width="480" height="360"></canvas>

Rotate individual objects in canvas?

the rotate() function seems to rotate the whole drawing area. Is there a way to rotate paths individually? I want the center for the rotation to be the object, not the drawing area.
Using save() and restore() still makes rotate take into account the whole drawing area.
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.save();
context.fillStyle = 'red';
context.rotate(0.35);
context.fillRect(40,40, 100, 100);
context.restore();
context.save();
context.fillStyle = 'blue';
context.rotate(0.35);
context.fillRect(200, 40, 100, 100);
context.restore();
<canvas id="canvas" width="500" height="500"></canvas>
Use local space
Instead of drawing object at the position you want them draw everything around its own origin in its local space. The origin is at (0,0) and is the location that the object rotates around.
So if you have a rectangle that you draw with
function drawRect(){
context.fillRect(200, 40, 100, 100);
}
change it so that it is drawn at its origin
function drawRect(){
context.fillRect(-50,-50 , 100, 100);
}
Now you can easily draw it wherevery you want
Start with the setTransform function as that clears any existing tranforms and is a convenient way to set the location of the center of the object will be
ctx.setTransform(1,0,0,1,posX,posY); // clear transform and set center location
if you want to rotate it then add the rotation
ctx.rotate(ang);
and scale with
ctx.scale(scale,scale);
if you have two different scales you should scale before the rotate.
Now just call the draw function
drawRect();
and it is drawn with its center at posX,posY rotated and scaled.
You can combine it all into a function that has the x,y position, the width and the height, scale and rotation. You can include the scale in the setTransform
function drawRect(x,y,w,h,scale,rotation){
ctx.setTransform(scale,0,0,scale,x,y);
ctx.rotate(rotation);
ctx.strokeRect(-w/2,-h/2,w,h);
}
It also applies to an image as a sprite, and I will include a alpha
function drawImage(img,x,y,w,h,scale,rotation,alpha){
ctx.globalAlpha = alpha;
ctx.setTransform(scale,0,0,scale,x,y);
ctx.rotate(rotation);
ctx.drawImage(img,-img.width/2,-img.height/2,img.width,img.height);
}
On a 6 year old laptop that can draw 2000 sprites on firefox every 1/60th of a second, each rotated, scaled, positioned, and with a alpha fade.
No need to mess about with translating back and forward. Just keep all the objects you draw around there own origins and move that origin via the transform.
Update
Lost the demo so here it is to show how to do it in practice.
Just draws a lot of rotated, scaled translated, alphaed rectangles.
By using setTransform you save a lot of time by avoiding save and restore
// create canvas and add resize
var canvas,ctx;
function createCanvas(){
canvas = document.createElement("canvas");
canvas.style.position = "absolute";
canvas.style.left = "0px";
canvas.style.top = "0px";
canvas.style.zIndex = 1000;
document.body.appendChild(canvas);
}
function resizeCanvas(){
if(canvas === undefined){
createCanvas();
}
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
}
resizeCanvas();
window.addEventListener("resize",resizeCanvas);
// simple function to draw a rectangle
var drawRect = function(x,y,w,h,scale,rot,alpha,col){
ctx.setTransform(scale,0,0,scale,x,y);
ctx.rotate(rot);
ctx.globalAlpha = alpha;
ctx.strokeStyle = col;
ctx.strokeRect(-w/2,-h/2, w, h);
}
// create some rectangles in unit scale so that they can be scaled to fit
// what ever screen size this is in
var rects = [];
for(var i = 0; i < 200; i ++){
rects[i] = {
x : Math.random(),
y : Math.random(),
w : Math.random() * 0.1,
h : Math.random() * 0.1,
scale : 1,
rotate : 0,
dr : (Math.random() - 0.5)*0.1, // rotation rate
ds : Math.random()*0.01, // scale vary rate
da : Math.random()*0.01, // alpha vary rate
col : "hsl("+Math.floor(Math.random()*360)+",100%,50%)",
};
}
// draw everything once a frame
function update(time){
var w,h;
w = canvas.width; // get canvas size incase there has been a resize
h = canvas.height;
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.clearRect(0,0,w,h); // clear the canvas
// update and draw each rect
for(var i = 0; i < rects.length; i ++){
var rec = rects[i];
rec.rotate += rec.dr;
drawRect(rec.x * w, rec.y * h, rec.w * w,rec.h * h,rec.scale + Math.sin(time * rec.ds) * 0.4,rec.rotate,Math.sin(time * rec.da) *0.5 + 0.5,rec.col);
}
requestAnimationFrame(update); // do it all again
}
requestAnimationFrame(update);
All transformations in canvas are for the whole drawing area. If you want to rotate around a point you're going to have to translate that point to the origin, do your rotation and translate it back. Something like this is what you want.
Use a rotate function to rotate all of the shape's points around its center.
<!DOCTYPE html>
<html>
<head>
<style>
body
{
margin: 0px;
padding: 0px;
overflow: hidden;
}
canvas
{
position: absolute;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
var canvas;
var context;
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var degreesToRadians = function(degrees)
{
return degrees*Math.PI/180;
}
var rotate = function(x, y, cx, cy, degrees)
{
var radians = degreesToRadians(degrees);
var cos = Math.cos(radians);
var sin = Math.sin(radians);
var nx = (cos * (x - cx)) + (sin * (y - cy)) + cx;
var ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;
return new Vector2(nx, ny);
}
var Vector2 = function(x, y)
{
return {x:x,y:y};
}
var Shape = function(points, color)
{
this.color = color;
this.points = points;
};
Shape.prototype.rotate = function(degrees)
{
var center = this.getCenter();
for (var i = 0; i < this.points.length; i++)
{
this.points[i] = rotate(this.points[i].x,this.points[i].y,center.x,center.y,degrees);
}
context.beginPath();
context.arc(center.x,center.y,35,0,Math.PI*2);
context.closePath();
context.stroke();
}
Shape.prototype.draw = function()
{
context.fillStyle = this.color;
context.strokeStyle = "#000000";
context.beginPath();
context.moveTo(this.points[0].x, this.points[0].y);
for (var i = 0; i < this.points.length; i++)
{
context.lineTo(this.points[i].x, this.points[i].y);
//context.fillText(i+1, this.points[i].x, this.points[i].y);
}
context.closePath();
context.fill();
context.stroke();
}
Shape.prototype.getCenter = function()
{
var center = {x:0,y:0};
for (var i = 0; i < this.points.length; i++)
{
center.x += this.points[i].x;
center.y += this.points[i].y;
}
center.x /= this.points.length;
center.y /= this.points.length;
return center;
}
Shape.prototype.translate = function(x, y)
{
for (var i = 0; i < this.points.length; i++)
{
this.points[i].x += x;
this.points[i].y += y;
}
}
var Rect = function(x,y,w,h,c)
{
this.color = c;
this.points = [Vector2(x,y),Vector2(x+w,y),Vector2(x+w,y+h),Vector2(x,y+h)];
}
Rect.prototype = Shape.prototype;
var r = new Rect(50, 50, 200, 100, "#ff0000");
r.draw();
r.translate(300,0);
r.rotate(30);
r.draw();
</script>
</body>
</html>

Is clearing canvas [ 2D Context ] in HTML5 necessary for good performance?

I have a 2D canvas and drawing circle indefinitely one above the other.
Take this example : http://jsfiddle.net/umaar/fnMvf/
<html>
<head>
</head>
<body>
<canvas id="canvas1" width="500" height="500"></canvas>
</body>
</html>
JavaScript :
var currentEndAngle = 0
var currentStartAngle = 0;
var currentColor = 'black';
var lineRadius = 75;
var lineWidth = 15;
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;
var width;
var startAngle = currentStartAngle * Math.PI;
var endAngle = (currentEndAngle) * Math.PI;
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;
}
var counterClockwise = false;
context.beginPath();
context.arc(x, y, radius, startAngle, endAngle, counterClockwise);
context.lineWidth = width;
context.lineCap = "round";
// line color
context.strokeStyle = currentColor;
context.stroke();
}
Do I really need to clear canvas at some specific interval ?
How does canvas work in that case ? As it is '2D' context, does it still store previous data ? If yes, What should be approach to achieve smoothness for drawing circle keeping performance in mind ?
Canvas is a drawing surface. When you draw an element (e.g. call fill method), you are just changing the color of some pixels on the drawing surface. The canvas does not store any information about the element being drawn. In your example, there is no need to clear the canvas.

Canvas sinusoidal text scroller

I'm trying to create a sinusoidal text scrolling animation in HTML5 canvas, but I can't figure out how to animate each letter differently.
I know I can use .split('') to get an array that contains all the characters in the string. I tried using a for loop for (var i = 0; i < chars.length; i++) but that didn't do what I was expecting (all characters in the array were smooshed together). I was hoping somebody with the experience could help me out with the code and write comments in it, so that I can learn this.
What I already have is below. As you can see, it doesn't animate each letter. See this video for what I am trying to do.
// Canvas
var c = document.getElementById('c');
var ctx = c.getContext('2d');
var seconds = Date.now();
var offsetY = 220;
var offsetX = 490;
var chars = 'abc';
var amplitude = 50;
var textcolor ='#fff';
var backgroundcolor = '#000';
// Options
c.height = 500; // Canvas HEIGHT
c.width = 500; // Canvas WIDTH
function animate() {
var y = Math.floor((Date.now() - seconds) / 10) / 30;
var yPos = Math.sin((y)) * amplitude;
ctx.fillStyle = backgroundcolor;
ctx.fillRect(0, 0, c.width, c.height);
ctx.fillStyle = textcolor;
ctx.fillText(chars, offsetX--, offsetY + yPos);
if (offsetX == 0) {
offsetX = 490;
}
// Loop it
requestAnimationFrame(animate);
}
// Start animation
requestAnimationFrame(animate);
<!doctype html>
<html>
<head>
<title>Sinus Scroller</title>
</head>
<body>
<canvas id="c">
</canvas>
</body>
</html>
It's desirable to warp the letters to the sine wave because the distance from one character to the next grows as the slope of the wave increases. If you avoid warping and simply implement the wave with constant speed in x and with y = sin(x) for each letter, you'll see inter-character gaps growing on the steep portions of the sine wave and shrinking near the optima.
At any rate, here is the simple implementation:
var text = 'Savor the delightful flavor of Bubba-Cola',
canvasWidth = 620,
canvasHeight = 200,
rightEdgeBuffer = 50;
WebFont.load({ // Web Font Loader: https://github.com/typekit/webfontloader
google: {
families: ['Source Sans Pro']
},
active: function () { // Gets called when font loading is done.
var canvas = document.getElementsByTagName('canvas')[0],
context = canvas.getContext('2d'),
yZero = canvasHeight / 2, // Set axis position and amplitude
amplitude = canvasHeight / 4, // according to canvas dimensions.
textColor ='#fff',
backgroundColor = '#000';
canvas.width = canvasWidth;
canvas.height = canvasHeight;
context.font = "32px 'Source Sans Pro', monospace";
var pos = canvasWidth; // Split the text into characters.
var units = text.split('').map(function (char) {
var width = context.measureText(char).width,
unit = { char: char, width: width, pos: pos };
pos += width; // Calculate the pixel offset of each character.
return unit;
});
var running = true,
lapTime; // Set this before the first animation call.
function animate() {
var currentTime = Date.now(),
dp = (currentTime - lapTime) / 15; // Displacement in pixels.
lapTime = currentTime;
context.fillStyle = backgroundColor;
context.fillRect(0, 0, canvasWidth, canvasHeight);
units.forEach(function (unit) {
unit.pos -= dp; // Update char position.
if (unit.pos < -unit.width) { // Wrap around from left to right.
unit.pos += canvasWidth + rightEdgeBuffer;
}
var y = Math.sin(unit.pos / 45) * amplitude;
context.fillStyle = textColor;
context.fillText(unit.char, unit.pos, yZero + y);
});
if (running) {
requestAnimationFrame(animate);
}
}
document.getElementById('stopButton').onclick = function () {
running = false;
};
lapTime = Date.now();
requestAnimationFrame(animate);
}
});
<script
src="https://ajax.googleapis.com/ajax/libs/webfont/1.5.18/webfont.js"
></script>
<canvas></canvas>
<button id="stopButton"> stop </button>
Here is a more complete implementation with rectilinearly warped characters:
https://github.com/michaellaszlo/wavy-text

How to animate and erase an arc in HTML5

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);
}

Categories