Generative Art Experiments
(As the title says)
Below is the actual canvas sketch which will change if you click below(or if you refresh the page). Full credit goes to Matt DesLauriers, the creator of the canvas-sketch library. I’m just playiing around with it.
Simple canvas

import canvasSketch from 'canvas-sketch';
const settings = {
dimensions: [ 2048, 2048 ],
parent: document.querySelector('#sketch-container')
};
const sketch = () => {
return ({ context, width, height }) => {
context.beginPath();
context.arc(width / 2, height / .4, width * 0.2, 0, Math.PI * 2, false);
context.fillStyle = 'turquoise';
context.fill();
context.lineWidth = width * 0.02;
context.strokeStyle = 'aqua';
context.stroke();
}
}
if (settings.parent) {
canvasSketch(sketch, settings);
} else {
console.error('Could not find sketch container element');
}
Grid for loops

// ...previous code
const sketch = () => {
return ({ context, width, height }) => {
const createGrid = () => {
const points = [];
const count = 5;
const offsetCount = count <= 1 ? 0.5 : count - 1;
for (let x = 0; x < count; x++) {
for (let y = 0; y < count; y++) {
const u = x / offsetCount;
const v = y / offsetCount;
points.push([u, v])
}
}
return points;
}
context.fillStyle = 'lemonchiffon';
context.fillRect(0, 0, width, height);
const grid = createGrid();
grid.forEach(([u, v]) => {
const x = u * width;
const y = v * height;
context.beginPath();
context.arc(x, y, width / 6, 0, Math.PI * 2, false);
context.strokeStyle = 'cornflowerblue';
context.lineWidth = 20;
context.stroke()
});
};
};
// ...next code
Line interpolation (lerp)
The lerp
function from canvas-sketch-utils
return a value between the min and max based on t
. Here it is used to move the origin point for each of the circles as the first starts at 0, 0
on the x, y
axis.
lerp(min, max, t);
lerp(0, 100, 0); // Returns 0
lerp(0, 100, 1); // Returns 100
lerp(0, 100, 0.5); // Returns 50
lerp(0, 100, 0.25); // Returns 25

// ...
import { lerp } from 'canvas-sketch-util/math';
// ...
const sketch = () => {
return ({ context, width, height }) => {
const count = 20;
const createGrid = () => {
const points = [];
const offsetCount = count - 1;
for (let x = 0; x < count; x++) {
for (let y = 0; y < count; y++) {
const u = count <= 1 ? 0.5 : x / offsetCount;
const v = count <= 1 ? 0.5 : y / offsetCount;
points.push([u, v])
}
}
return points;
}
context.fillStyle = 'blueviolet';
context.fillRect(0, 0, width, height);
const grid = createGrid();
const margin = count * 2.5;
grid.forEach(([u, v]) => {
const x = lerp(margin, width - margin, u);
const y = lerp(margin, width - margin, v);
const value = lerp(50, width - 50, u);
context.beginPath();
context.arc(x, y, width / (count * 2.5), 0, Math.PI * 2, false);
context.strokeStyle = 'ghostwhite';
context.lineWidth = count / 4;
context.stroke()
});
};
};
// ...
Randomness
With random.value()
instead of Math.random()
the dots stay in the same position when you refresh the page.

// ...
import random from 'canvas-sketch-util/random';
// ...
const sketch = () => {
return ({ context, width, height }) => {
const count = 30;
const x0 = width * 0.5; // Center x
const y0 = height * 0.5; // Center y
const r0 = 0; // Inner radius
const x1 = width * 0.5; // Outer center x (same as inner for circular)
const y1 = height * 0.5; // Outer center y (same as inner for circular)
const r1 = width * 0.6; // Outer radius
const createGrid = () => {
const points = [];
const offsetCount = count - 1;
for (let x = 0; x < count; x++) {
for (let y = 0; y < count; y++) {
const u = count <= 1 ? 0.5 : x / offsetCount;
const v = count <= 1 ? 0.5 : y / offsetCount;
points.push({
position: [u, v],
radius: random.value()
})
}
}
return points;
}
const gradient = context.createRadialGradient(x0, y0, r0, x1, y1, r1);
gradient.addColorStop(0, 'white');
gradient.addColorStop(1, 'black');
context.fillStyle = gradient;
context.fillRect(0, 0, width, height);
random.setSeed(234); // just needs to be unique
const grid = createGrid().filter(() => random.value() < 0.6);
const margin = count * 2.5;
grid.forEach(({position, radius}) => {
const [u, v] = position;
const x = lerp(margin, width - margin, u);
const y = lerp(margin, width - margin, v);
context.beginPath();
context.arc(x, y, radius * count, 0, Math.PI * 2, false);
context.fillStyle = 'ghostwhite';
context.fill()
});
};
};
// ...
Palettes

// ...
import palettes from 'nice-color-palettes';
// ...
const sketch = () => {
let palette = random.pick(palettes);
palette = random.shuffle(palette);
palette = palette.slice(0, random.rangeFloor(2, palette.length + 1));
const background = palette.shift();
const createGrid = () => {
const count = 30;
const points = [];
const offsetCount = count - 1;
for (let x = 0; x < count; x++) {
for (let y = 0; y < count; y++) {
const u = x / offsetCount;
const v = y / offsetCount;
points.push({
color: random.pick(palette),
position: [u, v],
radius: Math.abs(count * random.gaussian()),
})
}
}
return points;
}
return ({ context, width, height }) => {
context.fillStyle = background;
context.fillRect(0, 0, width, height);
let grid = createGrid().filter(() => random.value() < 0.5);
grid = random.shuffle(grid);
const margin = width * 0.05;
grid.forEach(({position, radius, color}) => {
const [u, v] = position;
const x = lerp(margin, width - margin, u);
const y = lerp(margin, height - margin, v);
context.beginPath();
context.arc(x, y, radius, 0, Math.PI * 2);
context.fillStyle = color;
context.fill();
});
};
};
Noise and characters


// ...
const sketch = () => {
const createGrid = () => {
const count = 70;
const points = [];
const offsetCount = count - 1;
for (let x = 0; x < count; x++) {
for (let y = 0; y < count; y++) {
const u = count <= 1 ? 0.5 : x / offsetCount;
const v = y / offsetCount;
const noise = random.noise2D(u, v);
const radius = Math.abs(noise) * 0.25;
points.push({
color: random.pick(palette),
position: [u, v],
radius,
rotation: noise
})
}
}
return points;
}
return ({ context, width, height }) => {
context.fillStyle = background;
context.fillRect(0, 0, width, height);
let grid = createGrid().filter(() => random.value() < 0.5);
grid = random.shuffle(grid);
const margin = width * 0.125;
grid.forEach(({position, radius, color, rotation}) => {
const [u, v] = position;
const x = lerp(margin, width - margin, u);
const y = lerp(margin, height - margin, v);
context.save();
context.fillStyle = hexToOklchCSS(color);
const textArr = ['^', '*', '=', '+', '¨', ':', '_', '.', '<', '>'];
context.textAlign = 'center';
context.textBaseline = 'middle';
context.font = `${Math.round(radius * width)}px monospace`;
context.translate(x, y);
context.rotate(rotation);
context.fillText(random.pick(textArr), 0, 0);
context.restore();
});
};
};
// ...
*Some cool stuff: If you press command/control S you can get a printable copy in your Downloads folder (just in case you make something that you want to hang on the wall).
command/control K will save an image and commit the code. The saved image will have the same sha as the code commit so you can always go back to it later.