Triangular Mesh

This post is from the talented maxwellito, if you're interested in posting, you can open up a proposal, just like he did!

This triangular meshing effect is often shown off in libraries with SVG. Today we’re going to build it with canvas! It’s a great example of how a coordinate system and a little displacement can give clean beautiful effects.

As usual we begin with a little setup code, a square canvas. We will also set the size of the canvas and adjust it based on the user’s device pixel ratio, or pixel density. This ensures that the final result is crisp on all monitors.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var canvas = document.querySelector('canvas');
var context = canvas.getContext('2d');

var size = window.innerWidth;
var dpr = window.devicePixelRatio;
canvas.width = size * dpr;
canvas.height = size * dpr;
context.scale(dpr, dpr);
context.lineJoin = 'bevel';

Now let’s make a grid of dots. The standard way, regular lines and columns. For every dot coordinate we will draw it on the canvas, but also store the coordinate in an array for future use.

Every coordinate will be represented by an object with 2 properties: x and y.

The space between lines and columns is defined by the variable gap, we’ll draw these circles so we can see how our grid is placed out on the canvas.

11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var line,
    lines = [],
    gap = size / 7;

for(var y = gap / 2; y <= size; y+= gap) {
  line = [];
  for (var x = gap / 2; x <= size; x+= gap) {
    line.push({x: x, y: y});
    context.beginPath();
    context.arc(x, y, 1, 0, 2 * Math.PI, true);
    context.fill();
  }
  lines.push(line);
}
  

Now, we’re going to displace every other line on the x axis. We do this by alternating the variable called odd between true and false.

We can see that the new pattern is shaping up to be a mesh of regular triangles.

11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var line, dot,
    odd = false, 
    lines = [],
    gap = size / 8;

for(var y = gap / 2; y <= size; y+= gap) {
  odd = !odd;
  line = [];
  for(var x = gap / 4; x <= size; x+= gap) {
    dot = {x: x + (odd ? gap/2 : 0), y: y};
    line.push(dot);
    context.beginPath();
    context.arc(dot.x, dot.y, 1, 0, 2 * Math.PI, true);
    context.fill();
  }
  lines.push(line);
}

The next step will be using the dots to draw the triangles.

To make our life easier let’s make a function that take the 3 coordinates of a triangle and draw them together.

29
30
31
32
33
34
35
36
37
38
function drawTriangle(pointA, pointB, pointC) {
  context.beginPath();
  context.moveTo(pointA.x, pointA.y);
  context.lineTo(pointB.x, pointB.y);
  context.lineTo(pointC.x, pointC.y);
  context.lineTo(pointA.x, pointA.y);
  context.closePath();
  context.stroke();
}

Now we’ll string up our drawTriangle function, and use the dots we generated earlier to draw all the triangles.

This part might be a bit complex to understand. The script is going to go through all the lines and combining the two dots of the sibling line, forming triangles. The concatenation of two lines, let’s say line a and line b, and merge the dots into one array to make it look like a zig-zag: a1, b1, a2, b2, a3 etc.

This will give us an array, containing each triangles specific coordinates. Looking something like [a1, b1, a2], [b1, a2, b2], [a2, b2, a3]…

39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
var dotLine;
odd = true;

for(var y = 0; y < lines.length - 1; y++) {
  odd = !odd;
  dotLine = [];
  for(var i = 0; i < lines[y].length; i++) {
    dotLine.push(odd ? lines[y][i]   : lines[y+1][i]);
    dotLine.push(odd ? lines[y+1][i] : lines[y][i]);
  }
  for(var i = 0; i < dotLine.length - 2; i++) {
    drawTriangle(dotLine[i], dotLine[i+1], dotLine[i+2]);
  }
}

Now that we have a regular triangle mesh, we are one detail away from getting the magic to happen.

Every dot is a gap away from the surrounding dots. So a dot can be moved in this area without overlapping with other dots. Let’s use a bit of Math.random() to get a random position in this area.

21
22
23
24
25
    line.push({
      x: x + (Math.random()*.8 - .4) * gap  + (odd ? gap/2 : 0),
      y: y + (Math.random()*.8 - .4) * gap
    });

And for a little extra generative fun, let’s add in some grays! Only 16 shades. No more.

37
38
39
40
  var gray = Math.floor(Math.random()*16).toString(16);
  context.fillStyle = '#' + gray + gray + gray; 
  context.fill();

If you’re interested in exploring more detailed implementations of this effect, you can check out my library: triangulr

↪ You can edit the code above, and have it run by pressing the arrow between the code and demo, but if you like, you can also play around a little with this code