user1477388 - 7 months ago 17
Javascript Question

# Handdrawn circle simulation in HTML 5 canvas

The following code creates a circle in HTML 5 Canvas using jQuery:

Code:

``````//get a reference to the canvas
var ctx = \$('#canvas')[0].getContext("2d");

DrawCircle(75, 75, 20);

//draw a circle
{
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI*2, true);
ctx.fillStyle = 'transparent';
ctx.lineWidth = 2;
ctx.strokeStyle = '#003300';
ctx.stroke();
ctx.closePath();
ctx.fill();
}
``````

I am trying to simulate any of the following types of circles:

I have researched and found this article but was unable to apply it.

I would like for the circle to be drawn rather than just appear.

Is there a better way to do this? I'm sensing there's going to be a lot of math involved :)

P.S. I like the simplicity of PaperJs, maybe this would be the easiest approach using it's simplified paths?

There are already good solutions presented here. I wanted to add a variations of what is already presented - there are not many options beyond some trigonometry if one want to simulate hand drawn circles.

I would first recommend to actually record a real hand drawn circle. You can record the points as well as the `timeStamp` and reproduce the exact drawing at any time later. You could combine this with a line smoothing algorithm.

This here solution produces circles such as these:

You can change color, thickness etc. by setting the `strokeStyle`, `lineWidth` etc. as usual.

To draw a circle just call:

``````handDrawCircle(context, x, y, radius [, rounds] [, callback]);
``````

(`callback` is provided as the animation makes the function asynchronous).

The code is separated into two segments:

1. Generate the points
2. Animate the points

Initialization:

``````function handDrawCircle(ctx, cx, cy, r, rounds, callback) {

/// rounds is optional, defaults to 3 rounds
rounds = rounds ? rounds : 3;

var x, y,                                      /// the calced point
tol = Math.random() * (r * 0.03) + (r * 0.025), ///tolerance / fluctation
dx = Math.random() * tol * 0.75,           /// "bouncer" values
dy = Math.random() * tol * 0.75,
ix = (Math.random() - 1) * (r * 0.0044),   /// speed /incremental
iy = (Math.random() - 1) * (r * 0.0033),
rx = r + Math.random() * tol,              /// radius X
ry = (r + Math.random() * tol) * 0.8,      /// radius Y
a = 0,                                     /// angle
ad = 3,                                    /// angle delta (resolution)
i = 0,                                     /// counter
start = Math.random() + 50,                /// random delta start
tot = 360 * rounds + Math.random() * 50 - 100,  /// end angle
points = [],                               /// the points array
``````

In the main loop we don't bounce around randomly but increment with a random value and then increment linearly with that value, reverse it if we are at bounds (tolerance).

``````for (; i < tot; i += ad) {
dx += ix;
dy += iy;

if (dx < -tol || dx > tol) ix = -ix;
if (dy < -tol || dy > tol) iy = -iy;

x = cx + (rx + dx * 2) * Math.cos(i * deg2rad + start);
y = cy + (ry + dy * 2) * Math.sin(i * deg2rad + start);

points.push(x, y);
}
``````

And in the last segment we just render what we have of points.

The speed is determined by `da` (delta angle) in the previous step:

``````    i = 2;

/// start line
ctx.beginPath();
ctx.moveTo(points[0], points[1]);

/// call loop
draw();

function draw() {

ctx.lineTo(points[i], points[i + 1]);
ctx.stroke();

ctx.beginPath();
ctx.moveTo(points[i], points[i + 1]);

i += 2;

if (i < points.length) {
requestAnimationFrame(draw);
} else {
if (typeof callback === 'function')
callback();
}
}
}
``````

(see comments below in update section)

Tip: To get a more realistic stroke you can reduce `globalAlpha` to for example `0.7`.

However, for this to work properly you need to draw solid to an off-screen canvas first and then blit that off-screen canvas to main canvas (which has the `globalAlpha` set) for each frame or else the strokes will overlap between each point (which does not look good).

For squares you can use the same approach as with the circle but instead of using radius and angle you apply the variations to a line. Offset the deltas to make the line non-straight.

I tweaked the values a little but feel free to tweak them more to get a better result.

To make the circle "tilt" a little you can first rotate the canvas a little:

``````rotate = Math.random() * 0.5;

ctx.save();
ctx.translate(cx, cy);
ctx.rotate(-rotate);
ctx.translate(-cx, -cy);
``````

and when the loop finishes:

``````if (i < points.length) {
requestAnimationFrame(draw);
} else {
ctx.restore();
}
``````

(included in the demo linked above).

The circle will look more like this:

Update

To deal with the issues mentioned (comment fields too small :-) ): it's actually a bit more complicated to do animated lines, especially in a case like this where you a circular movement as well as a random boundary.

Ref. comments point 1: the tolerance is closely related to radius as it defined max fluctuation. We can modify the code to adopt a tolerance (and `ix/iy` as they defines how "fast" it will variate) based on radius. This is what I mean by tweaking, to find that value/sweet-spot that works well with all sizes. The smaller the circle the smaller the variations. Optionally specify these values as arguments to the function.

Point 2: since we're animating the circle the function becomes asynchronous. If we draw two circles right after each other they will mess up the canvas as seen as new points are added to the path from both circles which then gets stroked criss-crossed.

We can get around this by providing a callback mechanism:

``````handDrawCircle(context, x, y, radius [, rounds] [, callback]);
``````

and then when the animation has finished:

``````if (i < points.length) {
requestAnimationFrame(draw);

} else {
ctx.restore();
if (typeof callback === 'function')
callback();  /// call next function
}
``````

Another issues one will run into with the code as-is (remember that the code is meant as an example not a full solution :-) ) is with thick lines:

When we draw segment by segment separately canvas does not know how to calculate the butt angle of the line in relation to previous segment. This is part of the path-concept. When you stroke a path with several segments canvas know at what angle the butt (end of the line) will be at. So here we to either draw the line from start to current point and do a clear in between or only small `lineWidth` values.

When we use `clearRect` (which will make the line smooth and not "jaggy" as when we don't use a clear in between but just draw on top) we would need to consider implementing a top canvas to do the animation with and when animation finishes we draw the result to main canvas.

Now we start to see part of the "complexity" involved. This is of course because canvas is "low-level" in the sense that we need to provide all logic for everything. We are basically building systems each time we do something more with canvas than just draw simple shapes and images (but this also gives the great flexibility).

I address a couple of issues in this updated fiddle but you will still need to tweak then a little bit (updated with top canvas, callback, dynamic tolerance).

Source (Stackoverflow)