String art

A charming fox

Writing SVG syntax "by hand", directly in a code editor, takes a lot of patience.

<svg viewBox="-50 -50 100 100">
  <!-- ... -->
</svg>

You have full control over the appearance of the graphic, but it's up to you to fill the canvas with elements and attributes, one line after the other.

Given enough time you may forgive the task, but if your intention is to show the result on the web, there are ways to ease the feat. With JavaScript specifically, you gain access to a host of methods, helping you compose the graphic and even create intriguing designs.

Consider a basic shape in the form of a <circle>.

<circle fill="none" stroke="currentColor" r="40" />

With a stroke, without a fill, the code leads to a predictable, round outline. If you wanted, you could add more elements inside the shape, but what if you needed to draw points, or even lines, precisely on the contour of the circle? You can almost picture the idea already. A cross-stitch work of art, a web of interconnected lines threading the surface in any direction.

With the option of JavaScript, you can achieve the feat with one method: getPointAtLength.

const element = document.querySelector("circle");
element.getPointAtLength(0); // SVGPoint {x: 40, y: 0}

The function takes a number, an offset, and returns an SVGPoint. Behind the fancy label, however, there’s just an object with two properties, x and y.

And the function is close to everything you need to complete the design. What is left is a process, a way to repeat the instruction a number of times and in the span of the circle, from start to end. Indeed, as the function takes an offset, you may want to limit yourself to the total length. And for this, JavaScript helps you once more with a fitting getter: getTotalLength.

const length = element.getTotalLength();

Armed with the number, set up a regular for loop.

const n = 5;
for (let i = 0; i < n; i++) {
  // ...
}

Iterating as many times as you want points, you are then able to find the coordinates at proper increments.

const offset = (length / n) * i;
const { x, y } = element.getPointAtLength(offset);

You can store the information in an array, and use the data to draw points with <circle> elements.

// const points = [] ...
points.push({ x, y });

To draw lines, however, you need to go further. And you need further loops.

One loop to consider the points, from first to penultimate.

for (let i = 0; i < points.length - 1; i++) {
  const { x: x1, y: y1 } = points[i];
  // ...
}

An additional, nested loop, from the point next to the current one to the very last.

// ...
for (let j = i + 1; j < points.length; j++) {
  const { x: x2, y: y2 } = points[j];
}

Looping “forwards” you consider lines in one direction only, but it’s more than enough to complete the set.

Again you can store the data in an array, and there are several ways to save the points. A <line> element requires four attributes, for the start and end coordinates.

<line x1="40" y1="0" x2="12.319074630737305" y2="38.055755615234375" />

In this instance keep the points in plain objects and four matching properties.

// const lines = [] ...
lines.push({ x1, y1, x2, y2 });

As an alternative, a <path> looks for instructions in the d attribute.

<path d="M 40 0 12.319074630737305 38.055755615234375" />

Following the M character the string works from one set of coordinates to the other, so that you can keep an array of directives, an array of strings instead.

lines.push(`M ${x1} ${y1} ${x2} ${y2}`);

And if you appreciate brevity, you might enjoy knowing that you don’t need an array at all. In the d attribute, the instructions can very well follow each other. String after string.

<path
  d="M 40 0 12.319074630737305 38.055755615234375M M 40 0 -32.3667755126953 23.503015518188477 ..."
/>

In light of this, add the sequence to a single string.

// const lines = '' ...
lines += `M ${x1} ${y1} ${x2} ${y2}`;

With <line>s, with <path>s, with one single <path>, you have ways to mark the document and fill the circle. You can entertain the idea with a small playground.

And you can definitely put the concept to good use and craft the quaint vector graphic of your dreams.

A quaint dream-catcher