Lady Flamingo (2018)
Lady Flamingo was a small “toy project” idea: a calm, looping animation you can run locally in a browser. It’s the kind of thing you build on a weekend to learn (or re-learn) the fundamentals: coordinate systems, animation loops, and how small changes in code affect motion.
The point isn’t the flamingo. The point is the shape of a tiny, hackable project that:
- has no dependencies,
- uses stable browser APIs,
- and is safe to run because it’s just local HTML/CSS/JS.
What we’re building
A full-screen scene with a few flamingos drifting across the screen.
Under the hood, it’s just:
- A little bit of CSS for layout.
- A render loop using
requestAnimationFrame. - A few objects representing position, velocity, and rotation.
If you’ve never used requestAnimationFrame directly, MDN’s reference is the right starting point:
https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
Why a tiny animation project is useful
Most web development is building UIs and stitching systems together. A micro-project like this is the opposite: one file, one loop, and immediate feedback.
It teaches (or refreshes):
- how to keep state,
- how to update state over time,
- how to keep motion smooth,
- and how to avoid turning “simple” into “fragile”.
It’s also a good reminder that browser code is real software: performance and correctness still matter even when the output is playful.
Minimal version: one HTML file
Save this as lady-flamingo.html and open it in a browser.
For the flamingo, you can use an SVG you drew yourself, or a small PNG with transparency.
If you want to make your own flamingo art:
- Inkscape (SVG): https://inkscape.org/
- GIMP (PNG/WebP): https://www.gimp.org/
Here’s a minimal version using a simple inline SVG “flamingo-ish blob” so the file is self-contained:
<!doctype html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Lady Flamingo</title>
<style>
html, body { height: 100%; }
body {
margin: 0;
overflow: hidden;
background: radial-gradient(circle at 30% 20%, #1b3a57, #08101a 60%, #05070b);
}
.bird {
position: absolute;
width: 140px;
height: 140px;
opacity: 0.92;
transform-origin: 50% 50%;
will-change: transform;
pointer-events: none;
user-select: none;
}
</style>
<svg width="0" height="0" style="position:absolute">
<symbol id="flamingo" viewBox="0 0 200 200">
<path d="M112 40c-25 0-45 20-45 45 0 19 11 34 27 41-7 13-19 22-35 28l6 14c22-8 39-22 50-42 17-2 32-11 40-25 5-9 7-20 5-30-4-18-24-31-48-31z" fill="#ff6aa8"/>
<circle cx="128" cy="58" r="6" fill="#111"/>
<path d="M134 63c16 10 28 28 30 48" fill="none" stroke="#ff6aa8" stroke-width="10" stroke-linecap="round"/>
</symbol>
</svg>
<script>
const count = 7;
const birds = [];
function rand(min, max) { return Math.random() * (max - min) + min; }
function makeBird() {
const el = document.createElementNS("http://www.w3.org/2000/svg", "svg");
el.setAttribute("class", "bird");
el.setAttribute("viewBox", "0 0 200 200");
const use = document.createElementNS("http://www.w3.org/2000/svg", "use");
use.setAttributeNS("http://www.w3.org/1999/xlink", "href", "#flamingo");
el.appendChild(use);
document.body.appendChild(el);
return {
el,
x: rand(-200, window.innerWidth),
y: rand(0, window.innerHeight - 180),
vx: rand(18, 55),
wobble: rand(0.5, 1.6),
phase: rand(0, Math.PI * 2),
rot: rand(-18, 18)
};
}
for (let i = 0; i < count; i++) birds.push(makeBird());
let last = performance.now();
function tick(now) {
const dt = Math.min(0.05, (now - last) / 1000);
last = now;
for (const b of birds) {
b.phase += dt * b.wobble;
b.x += dt * b.vx;
const bob = Math.sin(b.phase) * 14;
if (b.x > window.innerWidth + 220) {
b.x = -220;
b.y = rand(0, window.innerHeight - 180);
b.vx = rand(18, 55);
}
b.el.style.transform = `translate(${b.x}px, ${b.y + bob}px) rotate(${b.rot + bob * 0.15}deg)`;
}
requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
</script>
Make it feel nicer (still tiny)
If you want to keep it small but more polished:
Add “exit” behaviour
You can listen for any keypress and close the tab/window if it was opened by script, or just show a small hint.
Keep the frame loop honest
A common bug is doing too much work per frame.
A practical habit: cap dt (as shown above) so that if the tab sleeps and resumes, objects don’t teleport across the screen.
MDN has good background on timing and animation behaviour: https://developer.mozilla.org/en-US/docs/Web/Performance
Separate state from rendering
Even in a single-file project, it’s helpful to separate:
- state updates (physics-ish),
- rendering (DOM writes).
That keeps the code readable and makes it easier to change art later.
Related
- Tools we use: /release1-2-tools.html
- Phishing basics: /security/phishing/
- More notes: /releases/
Lady Flamingo is intentionally small. If you can understand the whole file end-to-end, you’ve already won.