I built a gyroscope experiment on a Tuesday night with no brief, no deadline, no user. Just device orientation events, a canvas layer, and curiosity about what happened when you tilted a phone. Six months later, a version of that experiment was running in a production landing page.
That’s the whole argument for creative coding as a design engineer. Not that it makes you a better thinker (it might). Not that it’s good for creativity (probably). But that the things you build for no reason have a way of becoming the things you reach for when a problem actually needs them.
What you actually build when you explore canvas
The first canvas thing I shipped was bad. A particle system I’d copied from a tutorial, barely modified, dropped into a background because it looked cool. It looked cool. It also had a memory leak and no reduced-motion handling and ran at 20fps on a mid-range Android.
But writing it — actually understanding why requestAnimationFrame works the way it does, why you clear the canvas each frame, why particles need velocity vectors not positions — that was worth more than the bad output.
The second canvas project was better. The third, I knew what I was doing.
The thing about canvas is that it forces you to describe motion mathematically instead of declaratively. In CSS you say transition: transform 0.3s ease. In canvas you say: here is the current position, here is the velocity, here is the friction coefficient, update every frame. You stop thinking in keyframes and start thinking in physics. That changes how you read CSS animations — you start to understand what the easing function is actually modeling.
The progression that actually teaches you something — one dot following the mouse, then lag, then a trail:
const dots = Array.from({ length: 28 }, () => ({ x: W/2, y: H/2 }));
function tick() {
ctx.clearRect(0, 0, W, H);
// Head follows mouse with lag
dots[0].x += (mouse.x - dots[0].x) * 0.18;
dots[0].y += (mouse.y - dots[0].y) * 0.18;
// Each dot follows the one ahead
for (let i = 1; i < dots.length; i++) {
dots[i].x += (dots[i-1].x - dots[i].x) * 0.28;
dots[i].y += (dots[i-1].y - dots[i].y) * 0.28;
}
// Draw, fading toward the tail
for (let i = 0; i < dots.length; i++) {
const t = 1 - i / dots.length;
ctx.beginPath();
ctx.arc(dots[i].x, dots[i].y, 3 + t * 10, 0, Math.PI * 2);
ctx.globalAlpha = t * 0.9;
ctx.fill();
}
requestAnimationFrame(tick);
}
Each dot follows the one ahead with a lerp factor. Change 0.18 and 0.28 and the whole feel changes — tighter, snappier, or loose and liquid. This is the kind of thing that takes 20 minutes and teaches you more about motion than a week of CSS transitions.
The gyroscope project
The specific thing that ended up in production started because I was curious about DeviceOrientationEvent. It fires continuously on mobile as the device moves — alpha (compass), beta (tilt front/back), gamma (tilt left/right).
I spent a couple of evenings mapping those values to a parallax effect. Different layers of a composition moving at different rates as you tilted the phone. Nothing practical. Just: what does it feel like?
The answer was: surprisingly good. The depth illusion from even small amounts of parallax is visceral in a way that mouse-based parallax isn’t, because your proprioception is involved — your body knows it’s tilting, and the screen responding to that feels correct.
On desktop the same principle works with mouse position — layers at different depths responding at different rates:
element.addEventListener('mousemove', e => {
const cx = (e.clientX / window.innerWidth) - 0.5;
const cy = (e.clientY / window.innerHeight) - 0.5;
layers.forEach(layer => {
const depth = layer.dataset.depth;
layer.style.transform =
`translate(${cx * depth * 120}px, ${cy * depth * 120}px)`;
});
});
// On mobile: swap mousemove for DeviceOrientationEvent
// and cx/cy for event.gamma / event.beta
On mobile, swap mousemove for DeviceOrientationEvent and gamma/beta for cx/cy. Same code, different input source — which is exactly the kind of thing you only know to reach for if you’ve built it once before.
When a landing page project came up that needed something premium and distinctive on mobile, I had something ready to reach for. Not the exact experiment — cleaned up, reduced, actually performing well — but the mental model and most of the code were already there.
You can’t plan for that. You can only build things until you have a library of solved problems.
Easter eggs
Easter eggs are the most design-engineer thing you can do and I am absolutely biased about this.
An easter egg comes with its own constraints baked in: invisible until triggered, zero performance cost for people who never find it, actually worth the surprise when they do. Honestly a better brief than most tickets.
I’ve shipped a few. A konami code that changed the site’s color scheme. A long-press on a logo that triggered a particle burst. A specific scroll pattern that revealed a hidden message. None of them took more than an afternoon. All of them required thinking about interaction in a way that regular implementation doesn’t.
The hidden part is what keeps you honest. If someone hunts down a 5-keypress sequence and gets something mediocre, that’s worse than nothing. I’ve ended up pushing those animations further than I would on a “real” feature — probably because the stakes feel lower and there’s no ticket telling me when to stop.
Starting points that aren’t tutorials
DeviceOrientationEvent if you have a phone nearby. Map the values to anything — color, position, blur. See what feels good.
Canvas with requestAnimationFrame. One dot. Make it follow the mouse. Add lag. Add a trail. Each step teaches you something about the frame loop.
CSS @property with an animated custom number — type the property, animate it in a keyframe, apply it to hsl(). The browser interpolates through hue values you didn’t explicitly set:
@property --hue {
syntax: '<number>';
inherits: false;
initial-value: 265;
}
.orb {
background: radial-gradient(
circle at 38% 38%,
hsl(var(--hue), 75%, 62%),
hsl(var(--hue), 80%, 38%)
);
animation: hue-shift 4s linear infinite alternate;
}
@keyframes hue-shift {
to { --hue: 360; }
}
None of these are projects. They’re more like questions with moving answers. Start one, follow it until it stops being interesting, start another.
The things worth keeping will make themselves obvious.