Technology · · 5 min read
A Headline That Evolves Itself
The homepage headline is a hill climber painting 78 triangles until they read Hi, I'm Ken.
A Headline That Evolves Itself
My website has a bunch of different optimisers running in blog posts and a photograph of my face on the front. I think the optimisers are the best thing on this site, and I figured, why not show it right there on the homepage? So the second slide of the hero now runs one too, and what it's optimising itself into drawing the headline "Hi, I'm Ken". Here's how it works, and what it took to make it cheap enough to leave running on a homepage. Part of the series on how this site is built.
Quick jargon guide
- Hill climbing: the simplest optimiser there is. Change your answer a bit, keep the change if it scored better, throw it away if it didn't, repeat forever.
- (1+1) evolution strategy: hill climbing in evolution-strategy notation. One parent, one child per generation, and the better of the two survives.
- Fitness function: the score. It decides what "better" means.
- Genome: the numbers describing one candidate answer. Here, a list of triangles.
- getImageData: the browser call that hands you the raw pixels of a canvas. It's slow, so how often you call it decides whether the page stays usable.
Roger Alsing's Mona Lisa came out in December 2008 and is one of the best demos of evolutionary computation. He took 50 semi-transparent polygons, changed one small thing at a time, kept the change only if the picture looked more like the painting than before, and after enough rounds of that he had something you'd recognise as da Vinci's.
Text, therefore, should be an easier target than a face. Letters are high contrast and built from straight-ish strokes, and people will read a word from a rough smear of ink in roughly the right place well before they notice it's made of shapes. So the slide has a canvas, and on the canvas are 78 translucent triangles being nudged toward the pixels of "Hi, I'm Ken."
The whole algorithm
There's no population, no crossover and no selection pressure to tune. It's one candidate and one mutation at a time:
for (var e = 0; e < EVALS_PER_FRAME; e++) {
var cand = genome.map(cloneShape);
mutate(cand, scale);
var s2 = score(cand);
if (s2 < best) {
best = s2;
genome = cand;
}
}
The fitness function draws the candidate and compares it with the target, pixel by pixel:
function score(g) {
paint(g, fit, FIT_W, FIT_H);
var d = fit.getImageData(0, 0, FIT_W, FIT_H).data;
var err = 0;
for (var i = 0, p = 0; i < d.length; i += 4, p++) {
var lum = (d[i] * 0.2126 + d[i + 1] * 0.7152 + d[i + 2] * 0.0722) / 255;
var diff = lum * (d[i + 3] / 255) - target[p];
err += diff * diff;
}
return err;
}
It's squared error against the brightness of the target, with the alpha folded in because the canvas sits transparent over a photograph, and lower is better. The target is the headline itself, drawn once into an offscreen canvas in the site's own font (Poppins).
Keeping it cheap
I score on a canvas of 176 by 48 pixels that is hidden from users. The canvas you see is up to 560 pixels wide on the page, which is 1,120 real pixels on a retina screen, and scoring at that size would mean reading back over 370,000 pixels per evaluation, 170 evaluations a frame. At 176 by 48 it's 8,448, and the letters are still readable at that size.
I didn't let colour evolve either. Each triangle carries one number that puts it somewhere between the two ends of the site's photo gradient, which keeps the result on palette and takes three dimensions out of the search.
Most mutations get rejected, and a rejected mutation changes nothing anyone can see, so the visible canvas is repainted at most once a frame and only if at least one of that frame's mutations was kept.
The hero is a carousel of five slides, so four times out of five this one isn't on screen, and while it's off screen the loop checks whether its slide is the active one, sees it isn't, and does nothing. Once 1,800 evaluations in a row have failed to improve anything the loop idles, and when the slide comes back round it starts again from a fresh set of triangles, so whoever's looking gets to watch the headline being built instead of arriving at a finished one. The carousel can be paused, too, to give it more time.
What a screen reader gets
I marked the canvas aria-hidden so screen readers skip it, and put the real heading next to it as visually hidden text:
<h2 class="sr-only">Hi, I'm Ken.</h2>
<canvas class="kr-hero-evolve" aria-hidden="true"></canvas>
The document outline doesn't change, a crawler sees a normal heading, and nobody has to sit through a description of an animation they can't see.
Was it worth it
It's one small file, and most of it is comments and setup. It swapped a static line of text for a working demo of evolutionary computation, the field my research is in, inside the first few seconds on the site. I'm normally against homepage animations that are only there to move, because they cost the reader something and give nothing back, but this one is a real optimiser running on your machine and you can read its fitness function further up this page.
If you want the version with sliders and a population and all the knobs, that's Evolution, Live.
