Believe it or not, the beautiful fractal star below was not carefully designed by an artist, nor was it generated by a complex mathematical formula. It was generated by a game of chance, following simple rules, over and over.
Chaos Game is a simple method used for creating a fractal using a polygon and an initial random point inside it, then following a set of rules to iteratively creatie a sequence of points.
In this article, we will first explore the magic of Chaos Game and how it is generated. Then we will build a simple version from scratch using HTML Canvas. Finally I will introduce you to chaos-game
, an open-source library that I built to generate a wide variety of fractals at lightning speed with high performance using web workers and OffscreenCanvas
.
What is the Chaos Game?
To create a fractal using the Chaos Game algorithm, you generally follow this simple set of rules:
- Start with N points as the vertices of a polygon (like a triangle).
- Pick a starting point by choosing any random point inside the polygon.
- Randomly select one of the vertices.
- Move a fraction of the distance (for example 0.5) from your current point toward the chosen vertex.
- Draw a dot at this new location.
- Use this new point as your current location and go back to step 3.
By following these simple rules for a triangle with a jump distance of 0.5 thousands of time, you will see that the triangle does not get filled with random noise. Instead, it will magically form the famous Sierpiński triangle. You can see the process in action in the GIF below.
A Simple Implementation from Scratch
Now that we understand the rules, let's bring it to life. We will build a minimal version of the Chaos Game for a triangle. All we need is a simple HTML file.
Step 1: The Playground
First, we will set up a basic index.html with a <canvas>
element as our sheet of paper, and a <script>
to hold the logic.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Chaos Game</title>
<style>
body {
background-color: #111;
display: grid;
place-items: center;
height: 100vh;
margin: 0;
}
</style>
</head>
<body>
<canvas id="chaos-canvas" width="800" height="800"></canvas>
<script>
// Our JavaScript will go here!
</script>
</body>
</html>
We set the width
and height
of the canvas and give it an id
to easily find it with JavaScript.
The simple CSS inside the <style>
tag centers the canvas on a dark background.
Step 2: The Logic
Inside the <script>
tag, we will bring the Chaos Game to life.
Setting Things Up
First, we get a reference to the canvas and its context, and prepare the canvas for drawing.
const canvas = document.getElementById('chaos-canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'white'; // the color of the drawn dots
const width = canvas.width;
const height = canvas.height;
Rule 1: Define the polygon
We will define the three corners of a triangle using their {x, y} coordinates. We add 50px padding so they are not at the edge of the canvas.
const vertices = [
{x: width / 2, y: 50}, // Top
{x: 50, y: height - 50}, // Bottom-left
{x: width - 50, y: height - 50}, // Bottom-right
];
Rule 2: Pick a starting point
For simplicity, we start at the center of the canvas, but any random point inside the canvas will work.
let currentPoint = {x: width / 2, y: height / 2};
Rule 3: Pick a random vertex
Rules 3 to 6 will repeat many times, so we define a gameloop()
function that we can repeatedly call. Inside it, we will select one of the vertices at random.
function gameLoop() {
const randomIndex = Math.floor(Math.random() * vertices.length);
const targetVertex = vertices[randomIndex];
}
Rule 4: Move halfway to the target
Now inside our gameloop
function, we calculate the midpoint between our current point and the chosen vertex using the (a + b) / 2
formula
function gameLoop() {
// ...
currentPoint.x = (currentPoint.x + targetVertex.x) / 2;
currentPoint.y = (currentPoint.y + targetVertex.y) / 2;
}
Rule 5: Draw a dot at the new location
At the new location we calculated before, we draw a dot as a 1 pixel rectangle.
function gameLoop() {
// ...
ctx.fillRect(currentPoint.x, currentPoint.y, 1, 1);
}
Rule 6: Go Back to Step 3 and Repeat
Now that we draw a dot, we can go back to step 3 and repeat the whole process. Steps 3-6 are contained inside our gameLoop
function, so we only need to schedule this function for the next frame using requestAnimationFrame
.
function gameLoop() {
// ...
requestAnimationFrame(gameLoop);
}
// To initially start the game!
gameLoop();
Putting It All Together
Here is the complete code. You can save this as an index.html
file and open it to see the Chaos Game in action.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Simple Chaos Game</title>
<style>
body {
background-color: #111;
display: grid;
place-items: center;
height: 100vh;
margin: 0;
}
</style>
</head>
<body>
<canvas id="chaosCanvas" width="800" height="800"></canvas>
<script>
const canvas = document.getElementById('chaosCanvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'white';
const width = canvas.width;
const height = canvas.height;
// 1. Define the vertices of a triangle
const vertices = [
{x: width / 2, y: 50}, // Top
{x: 50, y: height - 50}, // Bottom-left
{x: width - 50, y: height - 50}, // Bottom-right
];
// 2. Pick a starting point
let currentPoint = {x: width / 2, y: height / 2};
function gameLoop() {
// 3. Roll the dice: pick a random vertex
const randomIndex = Math.floor(Math.random() * vertices.length);
const targetVertex = vertices[randomIndex];
// 4. Take a leap: move halfway
currentPoint.x = (currentPoint.x + targetVertex.x) / 2;
currentPoint.y = (currentPoint.y + targetVertex.y) / 2;
// 5. Mark your spot: draw a 1x1 pixel
ctx.fillRect(currentPoint.x, currentPoint.y, 1, 1);
// 6. Repeat
requestAnimationFrame(gameLoop);
}
// Start the game!
gameLoop();
</script>
</body>
</html>
When you open this you will see the famous Sierpiński triangle slowly emerge from the chaos and noise. As you watch it form, you might notice a few limitations which will bring us to the next section.
The Limitations and Challenges
If you run the code above, you will notice a few things:
- It is slow. To get a high-density, crisp image, you need millions or even billions of points. Our simple loop will take forever to achieve that.
- We can put steps 3-5 inside a loop to repeat 1000 or 10000 times before waiting for the next browser redraw. This partially counters the slowness, but it will block the main thread and freeze the UI for that duration.
- What if you want to use a pentagon, change the default 0.5 jump, change the colors, or add extra vertices? What if you want to restrict the selection of the vertices in a certain way, for example not to select a vertex twice in a row? Our simple script will quickly become too complex.
Supercharging It With chaos-game
Library
The limitations above drove me to write an open-source, high-performance library for generating high quality fractals very fast, without blocking the main thread. You can play with it at https://m-sarabi.ir/chaos-game/
- Using a Web Worker to handle the heavy lifting of all the calculations will ensure that the main thread stays smooth and responsive.
- Iterating the whole process in a tight loop in the worker can make it lightning fast without being throttled by
requestAnimationFrame
. - With a wide range of settings, like sides, size, colors, restrictions and more, it is possible to create many different forms of fractals.
- Points drawn on top of each other will increase the intensity of that pixel controlled by gamma correction, which will add depth to the final fractal. You can still get the classic one by setting gammaExponent to 0.
Getting started with the library
Using the chaos-game
library as an npm package can be as easy as creating a few buttons and a canvas and wiring them up to the ChaosGame
.
On the Chaos Game GitHub page you can see all the settings, the API methods, how to set it up, and maybe drop a ⭐️ on it.
Here you can see a basic usage example:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Chaos Game</title>
<script src="https://cdn.jsdelivr.net/npm/chaos-game@1.2.0"></script>
<style>
body {
background-color: #111;
color: #eee;
text-align: center;
}
.controls {
margin: 1rem;
}
canvas {
border: 1px solid #555;
}
</style>
</head>
<body>
<h1>Chaos Game JS</h1>
<div class="controls">
<button id="playBtn">Play</button>
<button id="stopBtn">Stop</button>
<button id="resetBtn">Reset</button>
<button id="downloadBtn">Download PNG</button>
Sides: <input type="number" id="sidesInput" value="5" min="3"/>
</div>
<canvas id="chaosCanvas" width="800" height="800"></canvas>
<script>
const canvas = document.getElementById('chaosCanvas');
// Initialize the game
const chaosGame = new ChaosGame(canvas,
{
canvasSize: 1000,
sides: 5,
jumpDistance: 0.618, // The golden ratio
centerVertex: true,
restriction: 'no-repeat',
fgColor: '#FFD700',
bgColor: '#1A1A1A',
solidBg: true,
symmetrical: true,
});
// Wire up the controls
document.getElementById('playBtn').onclick = () => chaosGame.play();
document.getElementById('stopBtn').onclick = () => chaosGame.stop();
document.getElementById('resetBtn').onclick = () => chaosGame.reset();
document.getElementById('downloadBtn').onclick = () => chaosGame.download();
document.getElementById('sidesInput').onchange = (e) => {
chaosGame.updateSetting('sides', parseInt(e.target.value));
};
</script>
</body>
</html>
Conclusion
We started with a simple set of rules for the Chaos Game and saw how beautiful patterns can emerge from chaos with these rules. We built a basic implementation of the Chaos Game in JavaScript, and saw its limitations. Then we saw how a Web Worker-based library like chaos-game
can unlock its full potential.
The real fun of the Chaos Game is exploration. You can experiment with the live demo at https://m-sarabi.ir/chaos-game/ right now. Try it with a hexagon, a jump of 0.5858, and center vertex. Or maybe try it with a square, midpoints, and a jump of 2/3.
- You can find the
chaos-game
library on GitHub, npm, and jsDelivr. - Give it a star if you found this interesting!
- I'd love to see what you create! Share your fractals and findings in the comments below.
- Feel free to open an issue on GitHub if you find any bugs or have a suggestion.
Thanks for reading.
Top comments (0)