More Ways to Draw Lines

In the last post, I discussed an alternate way of rendering lines, that could give a glitchy look, painterly look, or many other looks based on what parameters you used. In this post, I’ll discuss another way to draw lines.

The theme to these posts is that using moveTo / drawTo is fine but gives you clinical, antiseptic, perfect straight lines (or curves). And sometimes it’s nice to shake things up a bit. In fact, shaking things up is exactly what we’re going to do here today.

I’m going to start with something very similar to one of the earlier examples in the last post, but instead of drawing individual pixels along the length of the line, I’m going to draw short line segments.

const dx = x1 - x0;
const dy = y1 - y0;
const dist = Math.sqrt(dx * dx + dy * dy);
const res = 10;

context.beginPath();
context.moveTo(x0, y0);

for (let i = res; i < dist; i += res) {
  let t = i / dist;
  let x = x0 + dx * t;
  let y = y0 + dy * t;
  context.lineTo(x, y);
}
context.lineTo(x1, y1);
context.stroke()

Like before, I’m starting with two points defined by x0, y0 and x1, y1. And I’ve defined a res variable that dictates the length of those intermediate segments. Note that I moveTo the first point and set i equal to res to initialize the loop, and then lineTo the last point. This ensures that the start and end of the line remain constant (which will be important later). Otherwise, things should be familiar here if you read the previous post. Here’s what this gives us:

It’s exactly what you’d get if you just drew a line from the first to last point. So big deal. But now that we have these intermediate points, we can shake things up as promised.

I’ll just randomly shift each x, y point a bit before drawing to it. (Just showing the for loop here:

for (let i = res; i < dist; i += res) {
  let t = i / dist;
  let x = x0 + dx * t;
  let y = y0 + dy * t;
  x += Math.random() * 4 - 2;
  y += Math.random() * 4 - 2;
  context.lineTo(x, y);
}

This puts each x, y location somewhere from -2 to +2 on each axis. And gives us this:

I then put this all into a function to make it easily reusable:

function shakyLine(context, x0, y0, x1, y1, res, rand) {
  const dx = x1 - x0;
  const dy = y1 - y0;
  const dist = Math.sqrt(dx * dx + dy * dy);

  context.beginPath();
  context.moveTo(x0, y0);

  for (let i = res; i < dist; i += res) {
    let t = i / dist;
    let x = x0 + dx * t;
    let y = y0 + dy * t;
    x += Math.random() * rand - rand / 2;
    y += Math.random() * rand - rand / 2;
    context.lineTo(x, y);
  }
  context.lineTo(x1, y1);
  context.stroke();
}

Now I can just call it like so:

shakyLine(context, 100, 100, 700, 700, 10, 10);

And it creates a line like this:

Note that the random factor is 10 here, so it’s a lot more shaky than the earlier example. Note that the res and rand parameters both contribute in different ways to the shakiness of the line. So if I move res down to 5, but keep rand at 10, we get something more shaky, but in a tight way.

But here, I’ve bumped both res and rand up significantly:

shakyLine(context, 100, 100, 700, 700, 50, 40);

This gives us a line that varies a lot more, but because each segment is longer, it’s more of a chunky random shake.

So you can play these two parameters off each other to get various effects.

The next thing to do is create a shakyRect function. It looks like this:

function shakyRect(context, x, y, w, h, res, rand) {
  shakyLine(context, x, y, x + w, y, res, rand);
  shakyLine(context, x + w, y, x + w, y + h, res, rand);
  shakyLine(context, x + w, y + h, x, y + h, res, rand);
  shakyLine(context, x, y + h, x, y, res, rand);
}

As mentioned before, the fact that each line starts and ends on a non-shaky point means that the rectangle will be continuous. You can call it like this:

shakyRect(context, 100, 100, 600, 600, 10, 10);

And get a rectangle like this:

To show how res and rand relate, I made this for loop that draws a bunch of squares on the canvas. res increases from left to right and rand increases from top to bottom. It gives you a good idea of the different effects you can create.

for (let y = 20; y < 730; y += 70) {
  for (let x = 20; x < 730; x += 70) {
    shakyRect(context, x, y, 50, 50, x/730 * 10, y/730 * 10);
  }
}

Now, this rectangle function isn’t the best because it doesn’t support fills, but you can probably work up a better solution. You might also want to create functions for drawing other shaky shapes.

Or, maybe you just want a drop in library you can use. I created a whole JavaScript shaky library about 7 years ago. I can’t guarantee how well it has stood the test of time. There are undoubtedly some best practices in there that could be updated. But the core code should be good enough to work out things for yourself. It supports not only lines and rectangles, but circles, arcs, quadratic curves, bezier curves, and arcs. And as of this writing, it has 101 stars, which is kind of neat!

https://github.com/bit101/shaky

All the posts in the How to Draw a Line series:

1. How to Draw a Line

2. More Ways to Draw a Line

3. Yet Another Way to Draw Lines

2 thoughts on “More Ways to Draw Lines”

Leave a Reply

Your email address will not be published. Required fields are marked *