Sheep is the New Rubber Duck

People expect developers to have great personal sites. Most don't, and most don't care to. I decided to care for a bit — landed on monochrome and calm, the site you're looking at. But it needed one thing moving. Not a spinner, not a cursor trail. An animal walking across the page.

Why a sheep

Sheep are fluffy, a bit chubby, visually simple, and they look mildly silly and confused at times. They walk around freely, minding their own business, which fit the vibe. They're slightly unexpected but not forced, and definitely underrepresented in the pixel art ecosystem, unlike most animals you'd reach for. It is just perfect.

Getting the frames

A walking animation is a flipbook. The standard walk cycle has four key poses:

  • Contact A — one diagonal pair of legs forward, the other back, all touching ground
  • Passing A — legs crossing through vertical, lifted pair slightly off the ground
  • Contact B — the mirrored version of Contact A
  • Passing B — the mirrored version of Passing A

If you want this done in under 10 mins, use a generation tool. I used Quiver AI — free tier, generates SVGs from text prompts. The one constraint I gave in every prompt: keep it simple, no surprise details. Since the sheep is small on screen, any extra complexity just reads as noise. The prompts describe leg angles and ground contact for each pose; Contact B is the exact opposite diagonal of A, and the Passing frames have the non-planted legs angled forward about 10° and just barely off the ground.

The component

The core is a requestAnimationFrame loop. It runs two independent timers: one for position, one for the frame cycle.

const animate = (time: number) => {
  const s = state.current;
  if (s.lastTime) {
    const dt = (time - s.lastTime) / 1000;
    s.x += speed * s.dir * dt;

    const maxX = (containerRef?.current?.offsetWidth ?? window.innerWidth) - size;
    if (s.x >= maxX) { s.x = maxX; s.dir = -1; }
    else if (s.x <= 0) { s.x = 0; s.dir = 1; }

    if (time - s.lastFrameTime > frameMs) {
      s.frame = (s.frame + 1) % 4;
      s.lastFrameTime = time;
    }
    setPos({ x: s.x, dir: s.dir, frame: s.frame });
  }
  s.lastTime = time;
  s.raf = requestAnimationFrame(animate);
};

Position updates every frame using delta time, so speed stays consistent regardless of frame rate. The walk cycle advances independently every 180ms. When the sheep hits the container boundary it reverses. My sheep was originally right facing. To face left: scaleX(-1). No separate left-facing SVGs needed.

The wrapper is a div with position: relative and a border-bottom. The sheep walks along the line. Drop it anywhere. Scroll to top of page to see it in action!

Consider it your rubber duck. It's listening.

back to top ↑
LinkedInGitHubEmail