I decided to do a page just on this since arcTo() is so confusing to everyone. In fact, it's so confusing that several of the browsers got their implementation wrong. As of this writing some implementations are still buggy, but most of them will draw arcTo() correctly now. I was just looking at this in the SeaMonkey browser and it has some bugs that are already fixed in the Mozilla trunk that make the arcs go crazy. I run Ubuntu, and their package for SeaMonkey is way behind Mozilla. The Firefox browser, on the other hand, works just fine. Opera, as of this writing, also has problems with arcTo. So, if this doesn't work right on your browser, I apologize, but you'll just have to upgrade to a newer version, switch browsers, or wait until they fix your browser. N.B. All browsers are fixed now.

context.arcTo(x1, y1, x2, y2, radius)

Although you pass two points to arcTo(), there're actually three points involved in the whole thing. There's (x0,y0), which is whatever the current point in the path is before you call arcTo. Additionally, there are (x1,y1) and (x2,y2) the two control points you pass to arcTo(). Finally, there is a radius that you pass in that will be the radius of an imaginary circle we'll talk about in just a second.

It works like this. Draw an imaginary line through (x0,y0) and (x1,y1). Draw another imaginary line through (x1,y1) and (x2,y2).

Take an imaginary circle of radius r, and slide it up between the two lines until it just touches both lines. The two points at which the circle touches the lines are called the tangent points.

- Draw a line from the current point (x0,y0) to the first tangent point on the line from (x0,y0) to (x1,y1)
- Draw an arc from that tangent point to the other tangent point on the line from (x1,y1) to (x2,y2) along the circumference of the circle
- Add the tangent point where the arc ends up, on the line from (x1,y1) to (x2,y2) to the path as the new current point on the path.

The above three diagrams are all drawn on canvas dynamically. The arcTo really uses arcTo() with reduced alpha, and the rest of the diagram is calculated in javascript and drawn with lineTo and arc.

It's really quite easy to make a rounded rectangle.

/* roundedRect(ctx,x,y,width,height,radius,fill,stroke)
Arguments: ctx - the context to be used to draw with.
x,y - the top left corner
width - how wide the rectangle
height - how high the rectangle
radius - the radius of the corner
fill - true if the rectangle should be filled
stroke - true if the rectangle should be stroked */
var roundedRect=function(ctx,x,y,width,height,radius,fill,stroke)
{
ctx.save(); // save the context so we don't mess up others
ctx.beginPath();
// draw top and top right corner
ctx.moveTo(x+radius,y);
ctx.arcTo(x+width,y,x+width,y+radius,radius);
// draw right side and bottom right corner
ctx.arcTo(x+width,y+height,x+width-radius,y+height,radius);
// draw bottom and bottom left corner
ctx.arcTo(x,y+height,x,y+height-radius,radius);
// draw left and top left corner
ctx.arcTo(x,y,x+radius,y,radius);
if(fill){
ctx.fill();
}
if(stroke){
ctx.stroke();
}
ctx.restore(); // restore context to what it was on entry
}
var c4=document.getElementById("c4");
var ctx4=c4.getContext('2d');
ctx4.strokeStyle='rgb(150,0,0)';
ctx4.fillStyle='rgb(0,150,0)';
ctx4.lineWidth=7;
roundedRect(ctx4,15,15,160,120,20,true,true);
ctx4.strokeStyle='rgb(150,0,150)';
ctx4.fillStyle='rgba(0,0,150,0.6)';
ctx4.lineWidth=7;
roundedRect(ctx4,95,95,160,150,40,true,false);

At this point this should be pretty self evident. We define a function to do the rounded rectangles for us. It has to know where rectangle starts, what size it is, how rounded we want the corners, and whether we want it filled or stroked or both. Then it does it for us using the currently set strokeStyle and fillStyle.

Once we've defined it, it's an easy matter to call it twice to draw two rectangles with rounded corners.

The first one is both filled and stroked. Its corners have a radius of 20, it's filled in green, and it's stroked in red.

The second rectangle is only filled. Its corners have a radius of 40, and it's filled in blue. For fun, I made the blue transparent by using rgba(0,0,150,0.6) for the fillStyle. The last argument, 0.6 , can range in value from 0.0, completely transparent, to 1.0, completely opaque. When we use rgb() with only 3 arguments, the colors are always opaque.

I'd like to thank Eric Fredricksen who pointed out that I didn't need any lineTo calls in the roundedRect() routine when using arcTo() and made my tutorial and my code more elegant.

var c5=document.getElementById("c5");
var ctx5=c5.getContext('2d');
ctx5.strokeStyle='rgb(50,50,0)';
ctx5.fillStyle='rgb(50,50,0)';
ctx5.lineWidth=2;
// draw the line for the shaft
ctx5.moveTo(10,30);
ctx5.lineTo(290,30);
// draw the top of the arrow head
ctx5.lineTo(285,28);
// draw the curve of the back
ctx5.arcTo(289,30, 285,32,8);
// draw the bottom of the arrow head
ctx5.lineTo(290,30);
// and make it draw
ctx5.stroke();
ctx5.fill();

First we draw the line of the shaft, then a line back to the top back corner of the arrow head. That will be the first control point. Then we pass the tip of the arrow head and the bottom back corner of the arrow head as the other two control points and ask for a radius of eight. As soon as we stroke and fill it, we get a beautiful arrow head. If you want to know more about drawing arrows and arcs, I have a whole tutorial on it, Drawing lines and arcs with arrow heads on HTML5 Canvas.

I hope this helped to clear up any confusion, and I hope you keep learning. It's my favorite thing to do.