Unerdwear

Developer tools · lightweight guides · security notes

Lady Flamingo (2018)

A pink flamingo by the water
Source: Unsplash.

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:

What we’re building

A full-screen scene with a few flamingos drifting across the screen.

Under the hood, it’s just:

  1. A little bit of CSS for layout.
  2. A render loop using requestAnimationFrame.
  3. 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):

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:

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:

That keeps the code readable and makes it easier to change art later.

Lady Flamingo is intentionally small. If you can understand the whole file end-to-end, you’ve already won.

Last updated: 2026-01-02