Skip to main content

Coding a Snowflake Generator

By Paul Hebert

Published on December 14th, 2022

After a nice tromp through the snow last Sunday, I started to wonder if I could procedurally generate random snowflakes. After a little while coding by the window with a hot mug of coffee, I was pretty pleased with the results:

In this article, I’ll walk you through hand-coding an SVG snowflake, let you customize your own snowflake in an interactive playground, and show how a dash of JavaScript can help you generate infinite variations.

One of the things that make snowflakes so beautiful is their symmetry. Each snowflake is composed of several symmetrical “trees” that are rotated around a circle. Knowing this, we can draw a snowflake in three steps:

  1. Draw half a tree.
  2. Copy the half tree to make a full tree.
  3. Copy and rotate the tree around a center point several times.

These three steps can be repeated to generate a nearly infinite number of unique snowflakes:

Before generating random snowflakes, we need to understand the code used to draw a snowflake. There are a few different ways to code graphics on the web. For our snowflakes, we’ll be using SVG since the syntax is similar to HTML, and they can be styled with CSS.

First, we need to set up a canvas for our drawing. In our case, this is an SVG wrapper element:

<svg 
  viewBox="0 0 100 100" 
  width="100" 
  height="100" 
  role="img"
>
  <title>A Snowflake</title>

  <g class="snowflake"
    <!-- 
      Our graphics code goes here 
    -->
  </g>
</svg>

This SVG will house all of our graphics. There are a few things to note:

  • Our viewBox describes the coordinate grid for our graphic. Everything we draw will be drawn on a 100-unit square grid.
  • role="img" tells browsers to treat the entire SVG as a single image instead of exposing each inner element to assistive technologies.
  • The <title> element describes the image to assistive technologies and search engines.
  • Our graphics will be housed in a <g> (group) element. (This isn’t necessary, but will make it easier to dynamically update our snowflakes later.)

While we’re at it, let’s give our SVG a background color with some CSS:

/* Give our SVG wrapper a blue background */
svg {
  background-color: hsl(200, 50%, 50%);
}

For our first step, we need to draw a handful of lines. Luckily SVG has a <line> element that does just what we need:

<line x1="50" y1="50" x2="50" y2="10" class="trunk" />

The <line> attribute draws a line between two points along an x/y grid. The first point is defined by the x1 and y1 attributes. The second point is defined by the x2 and y2 attributes.

The line above will work for the “trunk” of our tree. It goes from the center of the grid (50/50), straight up to 10 units below the top of our grid (50,10).

Note: SVG strokes are centered along their paths, so this stroke will be perfectly centered. The stroke will be painted between 49.5 and 50.5 on our horizontal grid axis.

Next, we need to add some “branches.” These will start touching our “trunk” and then branch up and to the left:

<!-- Start 10 above center. Move 5 left and 5 up -->
<line x1="50" y1="40" x2="45" y2="35" class="branch" />
<!-- Start 20 above center. Move 10 left and 10 up -->
<line x1="50" y1="30" x2="40" y2="20" class="branch" />
<!-- Start 35 above center. Move 8 left and 8 up -->
<line x1="50" y1="15" x2="42" y2="7" class="branch" />

You’ll notice that each of our branches starts touching our branch (x1="50") and then moves up and to the left.

We’ll also add a few CSS rules to style our lines:

line {
  /* Make all of our snowflake lines white */
  stroke: #fff;
  /* Round our lines' endpoints */
  stroke-linecap: round;
}

Alright, we’re getting somewhere! We’ve got our “half tree.” Try tweaking the sliders below to see how a different trunk length or branch settings affects our snowflake and our SVG code:

Now we’ve got half a tree, but we need to add the other half. There are a few ways we could do this:

  • We could add more lines moving the other direction.
  • We could copy and paste our lines and then use CSS or SVG transformations to flip them horizontally.

But, both of these options would lead to a lot of long and repetitive code, which could be hard to maintain. Luckily, there’s an SVG <use> element that allows us to clone and tweak chunks of SVG code. Here’s an example of how we can reuse our half-tree:

<g id="branches">
  <!-- 
    Move our branch `<line>` elements 
    inside of a group so we can 
    reference them together.
  -->
</g>

<use href="#branches" class="flipped-branches"/>

Note how the branches group and use element are linked by the branches ID. We’ve added a class to our <use> element so we can add styles to the copied branches. We’ll use the CSS scale property to flip our branches horizontally.

The scale property allows us to stretch and squish an element. scale takes two values: a horizontal scale value and a vertical scale value. One funny aspect of scale is that if you scale an element to a negative value, it will be flipped instead of squished. We can use -1 1 to flip our cloned branches:

.flipped-branches {
  scale: -1 1;
  transform-origin: center;
}

You may have noticed we also set a transform-origin property in addition to scale. This tells our branches to flip relative to the center of our SVG container.

Try using the “Branch Translation” slider below and see how it affects the flipped branches.

Now we’ve got one of our “trees” coded, but to get that fun snowflake effect, we’ll need to copy it several times and rotate each copy around a center point like the spokes on a bike wheel. Our friend <use> will help us streamline this:

<g id="tree"> 
  <g id="branches">
    <!-- Our branches go here -->
  </g>
  <use 
    href="#branches" 
    class="flipped-branches" 
  />
</g>

<!-- Copy our tree -->
<use href="#tree" style="--index: 1;" class="rotated-tree" />
<use href="#tree" style="--index: 2;" class="rotated-tree" />
<use href="#tree" style="--index: 3;" class="rotated-tree" />
<use href="#tree" style="--index: 4;" class="rotated-tree" />
<use href="#tree" style="--index: 5;" class="rotated-tree" />

You can see we’re using the same strategy we used to copy our branches, but this time we’re copying the entire tree five times. You may have also noticed that we’re giving each copy of the tree an --index custom property. We can use this custom property in our CSS to give each copy a different rotation around our center point:

.rotated-tree {
  rotate: calc(60deg * var(--index));
  transform-origin: center;
}

Since we have six trees (our original tree and five copies), we need to rotate each copy by 60 degrees (360 degrees divided by 6 trees) to space them evenly around our wheel.

If we had a different number of trees, we’d need to use a different number than 60 degrees. We can use another custom property and a little math to determine the degrees of rotation dynamically:

<g style="--tree-count: 6" class="snowflake">
  <!-- Our trees go here -->
</g>
rotate: calc(
  360deg / var(--tree-count) * var(--index)
);

Try changing the number of trees below and see how it affects the snowflake’s shape

In the demo above, we’ve got an array of input parameters that are processed and turned into an SVG string. That sounds an awful lot like a JavaScript function! Let’s turn our hand-written SVG into a function that accepts a settings object and spits out a snowflake.

This will allow us to generate random settings and procedurally generate snowflakes. (This logic is also what helped to power the demo above!)

// Our snowflake settings.
// We could randomly generate these, or pull them from a form.
const settings = {
  trunkLength: 40,
  branches: [
    { distance: 10, length: 5 },
    { distance: 20, length: 10 },
    { distance: 30, length: 8 },
  ],
  treeCount: 6
}

// Call our function and use it to 
// populate an SVG group
svgEl.innerHTML = buildSnowflake(settings);

// Our main function! 
// Returns the full snowflake SVG code
function buildSnowflake({trunkLength, branches, treeCount}) {
  return `
    <g class="snowflake" style="--tree-count: ${treeCount};">
      ${buildTree({trunkLength, branches})}
      ${buildTreeCopies(treeCount)}
    </g>
  `
}

// A helper function that returns an 
// SVG group containing a 
// single "tree"
function buildTree({trunkLength, branches}) {
  const trunk = `
    <line x1="50" y1="50" x2="50" y2="${50 - trunkLength}" />
  `;

  const branchStrings = branches.map(({distance, length}) => {
    const startY = 50 - distance;
    return `
      <line 
        x1="50" 
        y1="${startY}" 
        x2="${50 - length}" 
        y2="${startY - length}" 
      />
    `
  });

  return `<g id="tree">
    ${trunk}
    <g id="branches">${branchStrings.join(' ')}</g>
    <use href="#branches" class="flipped-branches" />
  </g>`;
}

// A helper function that returns a 
// number of `<use>` elements 
// copying our "tree"
function buildTreeCopies(treeCount) {
  let copies = '';
  for(let i = 0; i < treeCount; i++) {
    copies += `
      <use 
        href="#tree" 
        style="--index: ${i}" 
        class="rotated-tree"
      />` 
  }
  return copies;
}

Try editing the settings in the CodePen below to see how it changes the output of our function and the shape of our snowflake:

Now we’ve got a handy little function for generating snowflakes. In order to make this function “generative,” we’ll need to write some logic for generating a random settings object to pass into our buildSnowflake function.

We’ll use a small JS helper function called randomInt to generate random integers between two values. (I won’t dive deep into how that works here, but if you’re curious, you can view how it works in the upcoming CodePen.)

Here’s an example of how we could generate a random settings object:

function randomSettings() {
  // Create a random trunk length
  const trunkLength = randomInt(1, 50);
  
  // Create an array of branches
  // Each branch will have a random distance and length
  const branchCount = randomInt(1, 10);
  const branches = [];
  for(let i = 0; i < branchCount; i++) {
    branches.push({ 
      distance: randomInt(1, 40), 
      length: randomInt(1, 30) 
    });
  }
  
  // Determine how many trees/spokes 
  // to show
  const treeCount = randomInt(2, 30);
  
  return { 
    trunkLength, 
    branches, 
    treeCount 
  }
}

// Pass our random setting into our 
// snowflake function 
svgEl.innerHTML = drawSnowflake(
  randomSettings()
);

This makes some interesting shapes, but they don’t always feel likes snowflakes. Try clicking the “New Snowflake” button a few times below to see the generated shapes:

Sometimes the “trunk” lines are too short. Sometimes there aren’t enough “trees.” Sometimes the branches feel misaligned or extend outside our canvas. Sometimes it feels more like a doily…

Some of these patterns are really cool, but if we want to make snowflakes, we’ve got some more work to do…

First off, let’s ensure the “trunks” of our trees are a reasonable length. We’ll set a more reasonable minimum and maximum:

const trunkLength = randomInt(20, 40);

The next part is a bit trickier. There are a couple of issues with our current branches:

  • Sometimes they start past the end of our “trunk.”
  • Sometimes they extend outside of our canvas.

We can make a few tweaks to fix these:

// Instead of randomly generating our 
// branches, we'll start near our 
// center point and move outwards, 
// adding branches until we extend 
// past our trunk length.
for (
  let distance = randomInt(6, 10);
  distance < trunkLength;
  distance += randomInt(2, 10)
) {
  branches.push({
    distance,
    // When we get towards the end of 
    // our trunk, constrain the branch 
    // length so they don't extend too far
    length: randomInt(
      5, 
      Math.min(
        trunkLength - distance, 
        10
      )
    )
  });
}

We’ll also want to set a minimum and maximum number of “trees” that feels more like a snowflake:

const treeCount = randomInt(5, 12);

While we’re at it, let’s randomize one more piece of our snowflake. Let’s generate a random hsl color and use it to set our page’s background color:

const color = `hsl(
  ${random(190, 210)}, 
  ${random(30, 60)}%, 
  ${random(50, 80)}%
)`;

/* ... meanwhile, in our `drawSnowflake` function... */

document.body.style.backgroundColor = color;

Let’s see how this is working:

These feel a bit more snowflakey to me. We’ve turned our random pattern generator into a random snowflake generator!

There are lots of different snowflakes and a lot of different directions we could take it from here:

  • We could try changing our constraints to make different snowflake shapes.
  • We could animate how the snowflakes are drawn.
  • We could lay several snowflakes out in a pattern.
  • We could animate falling snowflakes.
  • We could use these same strategies to generate flowers, mandalas, or other geometric shapes.

I hope this taught you a little bit about SVGs, JavaScript, and generative art. I’d love to see what you make with these techniques.

If you remix the demos above or make brand-new generative art, post it in the comments!

Comments

CJ said:

I love what you've done here. My only comment is that real snowflakes, stellar dentrites, always have 6 'trees,' no more no less.

Leave a Comment

Please be kind, courteous and constructive. You may use simple HTML or Markdown in your comments. All fields are required.