Coding a Snowflake Generator
At 4/19/2024
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.
The Beauty of Symmetry
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:
- Draw half a tree.
- Copy the half tree to make a full tree.
- 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:
Hand-coding a snowflake
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.
Setting up our canvas
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>
Code language: HTML, XML (xml)
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%);
}
Code language: CSS (css)
Drawing half a “tree”
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" />
Code language: HTML, XML (xml)
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" />
Code language: HTML, XML (xml)
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;
}
Code language: CSS (css)
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:
Completing our “tree”
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"/>
Code language: HTML, XML (xml)
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;
}
Code language: CSS (css)
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.
Copying and rotating our “tree”
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" />
Code language: HTML, XML (xml)
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;
}
Code language: CSS (css)
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>
Code language: HTML, XML (xml)
rotate: calc(
360deg / var(--tree-count) * var(--index)
);
Code language: CSS (css)
Try changing the number of trees below and see how it affects the snowflake’s shape
Making your code configurable (a.k.a the snowflake building machine)
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;
}
Code language: JavaScript (javascript)
Try editing the settings in the CodePen below to see how it changes the output of our function and the shape of our snowflake:
Introducing randomness with JavaScript
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()
);
Code language: JavaScript (javascript)
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…
Introducing Constraints (a.k.a making the snowflakes more snowflakey)
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);
Code language: JavaScript (javascript)
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
)
)
});
}
Code language: JavaScript (javascript)
We’ll also want to set a minimum and maximum number of “trees” that feels more like a snowflake:
const treeCount = randomInt(5, 12);
Code language: JavaScript (javascript)
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;
Code language: JavaScript (javascript)
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!
Make it your own
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!