GODRICH
#css-animation#button#loader#transition#scroll#dashboard#ui#design-trends

Cursor Follow Animation: 9 Procedural Creatures

In a cursor follow animation, with no joint keyframed, how does a creature swim and walk after a target?

Give It One Target and the Whole Body Follows — Joints, Angles, Legs and Tentacles, Each Computed on Its Own

Auto-plays · click a tile to jump to its section · all nine in one zip

Left alone, the target travels a figure-eight through the middle of the stage: one swing across for every two swings up and down. The creatures loop around both rings, bending wide at the sides and cutting straight through the center. Once a pointer is over the stage, the target blends toward it bit by bit, and when the pointer leaves, the target drifts back onto the figure-eight.

That is a different unit of motion from the mouse follow effects, where dots or letters slide toward the pointer and each one only shrinks its own distance to it. Here the target pulls the head and nothing else; the rest of the body is dragged along under conditions such as a fixed gap, a bend limit or a bone length. The flocking simulation is a rule shared by many birds watching each other, while 08 is a rule by which one creature changes its own mood.

A portfolio hero and a 404 page

For a single companion on a portfolio hero, the chain snake in 01 has the simplest math: each frame it only has to place 26 joints again, and the moment a visitor moves the pointer it turns its head that way. A 404 page suits the lizard in 03, a lizard animation in which planted feet move one step at a time, which reads as a small guide walking beside a "let's start again" line.

As a helper in the corner of a store page, 08 fits best. While a shopper holds the pointer still to read a product, it wanders on its own; when the pointer moves, it comes closer; and when the pointer is flicked hard, it bolts and calms down 1.1 seconds later.

Event pages and a garden landing

An aquarium or ocean-experience event page can open on the octopus in 04 or the jellyfish in 06, and a fall event banner can carry the spider in 07, so the theme lands in one picture. On a landing page for an inn or a garden, the pond in 09 keeps more than half of the stage in motion and can take the place of a background video. The caterpillar in 05 works as a loading character crawling along a progress bar, and the koi in 02 as a calm backdrop for a cafe landing.

Cursor Follow Animation: 9 Creatures — Chain Snake, Angle-Capped Koi, Two-Bone Lizard, Reaching Tentacle, Crawling Caterpillar, Pulsing Jellyfish, Alternating Spider, a Pet with Moods and a Koi Pond

Each scene was captured on its 480×300 stage as 24 frames over 2 seconds, and the 23 differences between neighboring frames make up the table. The pond in 09 changes the most area at 51.4%, because the whole water surface moves, and the caterpillar in 05 changes the least at 9.6%, since only one body crawls across the floor. In every scene the picture changed in 23 of the 23 comparisons.

How much of the frame each scene changed

Scene Area changed Frames moved Mean intensity
01 Chain snake 14.5% 23/23 82.8
02 Angle-capped koi 17.8% 23/23 84.1
03 Lizard 12.7% 23/23 79.1
04 Octopus tentacles 24.9% 23/23 89.7
05 Caterpillar 9.6% 23/23 95.0
06 Two jellyfish 18.0% 23/23 74.0
07 Spider 19.9% 23/23 101.5
08 Pet creature 14.5% 23/23 102.7
09 Koi pond 51.4% 23/23 51.0

Scenes 01 to 08 hold one or two creatures on an open stage, so a small area figure is expected there. For those eight, what matters is how exactly the gap, the bend limit and the stepping rule hold, and each section below gives that number.

01A Chain Snake That Follows by Keeping Each Joint's Gap

The head closes in at five times its remaining distance per second, capped at 240px/s, and followChain below puts each of the 25 joints behind it 12px from the joint ahead, on the line toward where that joint sits now, which holds the body at 300px. Scene 01 passes Math.PI as maxBend so no bend limit applies, and the outline is one closed shape made by pushing each joint out sideways by its own radius: 12.94px at the head, 13.11px at the widest point and 2.5px at the tail. Over one 1,205px lap of the figure-eight, head and tail sit 205.23px apart on average and no pair of neighbors bends more than 0.29 radians.

A Chain Snake That Follows by Keeping Each Joint's Gap
Open in a new tab

01 · A Chain Snake That Follows by Keeping Each Joint's Gap — Measured area 14.523% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

SEGMENT_GAPBODY_WIDTHHEAD_SPEED
    function followChain(xs, ys, gap, maxBend, backAngle) {
        const n = xs.length;
        let prevAng = backAngle;
        for (let i = 1; i < n; i += 1) {
            let ang = Math.atan2(ys[i] - ys[i - 1], xs[i] - xs[i - 1]);
            if (maxBend < Math.PI && !isNaN(prevAng)) {
                const d = wrapAngle(ang - prevAng);
                if (d > maxBend) {
                    ang = prevAng + maxBend;
                }
                else if (d < -maxBend) {
                    ang = prevAng - maxBend;
                }
            }
            xs[i] = xs[i - 1] + Math.cos(ang) * gap;
            ys[i] = ys[i - 1] + Math.sin(ang) * gap;
            prevAng = ang;
        }
    }

02A Koi That Swims Without Folding Because Every Bend Is Capped

steerKoi turns the head by at most 3.2 radians per second, eases the speed toward 170px/s (slower inside 90px of the target), adds a 0.24-radian wiggle, and then calls followChain with MAX_BEND 0.35, about 20°, so no neighbor pair can bend further. The side fins open by 0.55 plus or minus 2.2 times the curve of joints 2 to 4, clamped to 0.15–1.45, so the outer side of a turn spreads wider, and the tail fin is a spring chasing the direction of the last segment. Over a 6.5-second lap some joint touched the limit in 40% of frames, and the tail fin trailed the tail segment by 133ms.

A Koi That Swims Without Folding Because Every Bend Is Capped
Open in a new tab

02 · A Koi That Swims Without Folding Because Every Bend Is Capped — Measured area 17.762% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

MAX_BENDFIN_GAINTAIL_STIFF
    function steerKoi(k, dt, tx, ty, maxBend) {
        const dx = tx - k.xs[0];
        const dy = ty - k.ys[0];
        const dist = Math.hypot(dx, dy);
        const turn = wrapAngle(Math.atan2(dy, dx) - k.heading);
        k.heading = wrapAngle(k.heading + clamp(turn, -TURN_RATE * dt, TURN_RATE * dt));
        const want = SWIM_SPEED * clamp(dist / SLOW_RADIUS, 0.3, 1);
        k.speed = lerp(k.speed, want, follow(3, dt));
        k.phase += k.speed * dt * WIGGLE_FREQ;
        const move = k.heading + Math.sin(k.phase) * WIGGLE_AMP;
        k.xs[0] += Math.cos(move) * k.speed * dt;
        k.ys[0] += Math.sin(move) * k.speed * dt;
        followChain(k.xs, k.ys, k.gap, maxBend, k.heading + Math.PI);
        // front-body curvature — sum of the bend at joints 2, 3 and 4. The sign is the bend direction
        const curve = bendAt(k.xs, k.ys, 2) + bendAt(k.xs, k.ys, 3) + bendAt(k.xs, k.ys, 4);
        k.curve = lerp(k.curve, curve, follow(10, dt));
        k.finL = lerp(k.finL, clamp(FIN_BASE + k.curve * FIN_GAIN, 0.15, 1.45), follow(8, dt));
        k.finR = lerp(k.finR, clamp(FIN_BASE - k.curve * FIN_GAIN, 0.15, 1.45), follow(8, dt));
        const n = KOI_JOINTS;
        const tailTarget = Math.atan2(k.ys[n - 1] - k.ys[n - 2], k.xs[n - 1] - k.xs[n - 2]);
        const acc = TAIL_STIFF * wrapAngle(tailTarget - k.tailAng) - TAIL_DAMP * k.tailVel;
        k.tailVel += acc * dt;
        k.tailAng = wrapAngle(k.tailAng + k.tailVel * dt);
    }

03A Lizard That Steps Only When a Foot Falls Too Far Behind

Each foot has a home spot 10px ahead of and 30px beside its spine joint, and when a planted foot drifts more than 38px from that home while the other diagonal pair is down, it arcs there in 0.16 seconds at a 7px lift and lands 19px past the spot. solveKnee below finds the knee from the 22px thigh, the 20px shin and the hip-to-foot distance, with the front elbows bending back and the hind knees bending forward. Across 18 seconds the lizard took 5.67 steps per second with an average stride of 54.28px, and no frame had both diagonal pairs in the air.

A Lizard That Steps Only When a Foot Falls Too Far Behind
Open in a new tab

03 · A Lizard That Steps Only When a Foot Falls Too Far Behind — Measured area 12.678% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

STEP_TRIGGERTHIGH_LENSTEP_LIFT
    function solveKnee(hx, hy, fx, fy, a, b, bend, guard, out) {
        const raw = Math.hypot(fx - hx, fy - hy);
        const d = guard ? clamp(raw, Math.abs(a - b) + 0.001, a + b - 0.001) : raw;
        const cosA = (a * a + d * d - b * b) / (2 * a * d);
        const ang = Math.atan2(fy - hy, fx - hx) + bend * Math.acos(cosA);
        const kx = hx + Math.cos(ang) * a;
        const ky = hy + Math.sin(ang) * a;
        const shin = Math.atan2(fy - ky, fx - kx);
        out[0] = kx;
        out[1] = ky;
        out[2] = kx + Math.cos(shin) * b;
        out[3] = ky + Math.sin(shin) * b;
        return cosA;
    }

04Octopus Tentacles That Reach Their Tips Toward the Target

Every frame, each of the 7 tentacles (12 joints, 10px apart, 110px long) runs the loop below 4 times: set the tip on its target and walk back to the root fixing each gap, then set the root on its anchor and walk out to the tip. Each tip aims at its own point in a fan around the target, swayed up to 30px by smooth noise, and a target farther away than the tentacle's length is handled before the loop by laying the chain straight toward it. During an 8-second lap an average of 4.69 of the 7 tips sat within 1px of their targets.

Octopus Tentacles That Reach Their Tips Toward the Target
Open in a new tab

04 · Octopus Tentacles That Reach Their Tips Toward the Target — Measured area 24.913% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

REACH_ITERTENTACLE_GAPSWAY_AMP
        for (let it = 0; it < iterations; it += 1) {
            xs[n - 1] = tx;
            ys[n - 1] = ty;
            for (let i = n - 2; i >= 0; i -= 1) {
                const d = Math.hypot(xs[i] - xs[i + 1], ys[i] - ys[i + 1]) || 1;
                xs[i] = xs[i + 1] + (xs[i] - xs[i + 1]) / d * gap;
                ys[i] = ys[i + 1] + (ys[i] - ys[i + 1]) / d * gap;
            }
            xs[0] = rx;
            ys[0] = ry;
            for (let i = 1; i < n; i += 1) {
                const d = Math.hypot(xs[i] - xs[i - 1], ys[i] - ys[i - 1]) || 1;
                xs[i] = xs[i - 1] + (xs[i] - xs[i - 1]) / d * gap;
                ys[i] = ys[i - 1] + (ys[i] - ys[i - 1]) / d * gap;
            }
        }

05A Caterpillar That Crawls by Bunching and Stretching

The 14 joints sit on the trail the head leaves behind, so each joint only tracks its distance along that trail and its speed, and targetGap shrinks the 13px gaps toward half that in a sine wave that runs from the tail toward the head and repeats every 0.57 seconds. crawl pulls each pair toward its gap with a spring, then multiplies a joint's speed by e^(−1.2·dt) when it moves forward and by e^(−28·dt) when it slides back. With the target pinned far away for 10 seconds that difference yields 81.26px/s of travel, while setting both frictions to 1.2 leaves −2.84px/s, which is no headway at all.

A Caterpillar That Crawls by Bunching and Stretching
Open in a new tab

05 · A Caterpillar That Crawls by Bunching and Stretching — Measured area 9.641% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

WAVE_DEPTHFRICTION_FWDFRICTION_BACK
    function targetGap(i) {
        const fromTail = CAT_JOINTS - 2 - i;
        return REST_GAP * (1 - WAVE_DEPTH * crawlDrive * Math.max(0, Math.sin(wavePhase - fromTail * WAVE_K)));
    }
    function crawl(dt) {
        wavePhase += WAVE_SPEED * dt;
        for (let i = 0; i < CAT_JOINTS; i += 1) {
            catF[i] = 0;
        }
        for (let i = 0; i < CAT_JOINTS - 1; i += 1) {
            const pull = SPRING_K * ((catS[i] - catS[i + 1]) - targetGap(i)) + SEG_DAMP * (catU[i] - catU[i + 1]);
            catF[i] -= pull;
            catF[i + 1] += pull;
        }
        for (let i = 0; i < CAT_JOINTS; i += 1) {
            catU[i] += catF[i] * dt;
            catU[i] *= Math.exp(-(catU[i] >= 0 ? FRICTION_FWD : frictionBack) * dt);
            catS[i] += catU[i] * dt;
        }
    }

06A Jellyfish That Lurches Forward Each Time Its Bell Squeezes

Each 1.2-second cycle squeezes the bell for 360ms and releases it over 840ms, and stepJelly adds 160px/s along the bell's heading in proportion to how much the squeeze grew, so speed jumps on the squeeze and the water drag e^(−1.6·dt) bleeds it off during the release. Five tentacles of 10 links hang from the rim, each keeping 92% of its last motion, sinking a little and getting its 11px gaps restored 3 times per step, while the small jellyfish swims the same figure-eight 3.4 seconds behind. Over two cycles the large jellyfish moved between 28.19 and 123.99px/s, 67.05px/s on average.

A Jellyfish That Lurches Forward Each Time Its Bell Squeezes
Open in a new tab

06 · A Jellyfish That Lurches Forward Each Time Its Bell Squeezes — Measured area 17.967% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

PULSE_IMPULSEWATER_DRAGLINK_PASSES
    function stepJelly(j, dt, gx, gy) {
        const want = Math.atan2(gy - j.y, gx - j.x);
        j.heading = wrapAngle(j.heading + clamp(wrapAngle(want - j.heading), -JELLY_TURN * dt, JELLY_TURN * dt));
        j.phase += dt / PULSE_PERIOD;
        const before = j.squeeze;
        j.squeeze = squeezeAt(j.phase);
        const push = Math.max(0, j.squeeze - before) * PULSE_IMPULSE;
        j.vx += Math.cos(j.heading) * push;
        j.vy += Math.sin(j.heading) * push;
        const drag = Math.exp(-WATER_DRAG * dt);
        j.vx *= drag;
        j.vy *= drag;
        j.x += j.vx * dt;
        j.y += j.vy * dt;
        // stage edge — inside JELLY_MARGIN (px), turn the velocity inward by the overshoot × 4 so the jellyfish stays on the stage
        const ox = j.x < JELLY_MARGIN ? JELLY_MARGIN - j.x : (j.x > W - JELLY_MARGIN ? W - JELLY_MARGIN - j.x : 0);
        const oy = j.y < JELLY_MARGIN ? JELLY_MARGIN - j.y : (j.y > H - JELLY_MARGIN ? H - JELLY_MARGIN - j.y : 0);
        j.vx += ox * 4 * dt;
        j.vy += oy * 4 * dt;
        for (let k = 0; k < TENTACLE_COUNT; k += 1) {
            const r = rimOf(j, k);
            stepTentacle(j.xs[k], j.ys[k], j.pxs[k], j.pys[k], r[0], r[1], dt, LINK_PASSES);
        }
    }

07A Spider Walking on Two Alternating Sets of Four Legs

The eight legs alternate between two sets so neighbors belong to different ones, and when the set whose turn it is has a foot more than 30px behind, all four of its legs lift together for 0.14 seconds, 14px high, and land ahead of their home spots by 1.6 times the distance the body covers during the step. The body height eases toward the average ground height under the planted feet plus 22px, and each knee is solved with bend 1, then solved again with bend −1 if its y is greater than the midpoint of the hip and foot y, which keeps the knees pointing up. In 9 seconds the sets swapped 49 times without the same set going twice in a row, at least four feet stayed on the ground, and the body rode between 18.56 and 25.39px.

A Spider Walking on Two Alternating Sets of Four Legs
Open in a new tab

07 · A Spider Walking on Two Alternating Sets of Four Legs — Measured area 19.912% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

GAIT_SETSSTEP_DISTBODY_CLEAR
            if (worst > STEP_DIST) {
                for (let k = 0; k < spiderLegs.length; k += 1) {
                    const g = spiderLegs[k];
                    if (g.set !== nextSet) {
                        continue;
                    }
                    const h = legHome(g);
                    g.sx0 = g.fx;
                    g.sy0 = g.fy;
                    // place it ahead by the distance the body travels during the step
                    g.ex = h[0] + bvx * STEP_TIME * 1.6;
                    g.ey = h[1] + bvy * STEP_TIME * 1.6;
                    g.t = 0;
                }
                movingSet = nextSet;
                nextSet = (nextSet + 1) % GAIT_SETS;
                setSwaps += 1;
            }

08A Pet Creature That Wanders, Tags Along and Startles Away

Once the target has stayed under 25px/s for 0.6 seconds, the pet wanders at 45px/s and turns by smooth noise, and while the target moves it follows at up to 170px/s, with its desired speed dropping once it is within 156px (46 + 110) and reaching zero at 46px. A target faster than 420px/s within 160px flips it into a 1.1-second dash the other way at 260px/s. In the demo run it switched from following to startled at 4.55 seconds (target speed 443.66, distance 93.71), back to following at 5.63 seconds and to wandering at 8.17 seconds.

A Pet Creature That Wanders, Tags Along and Startles Away
Open in a new tab

08 · A Pet Creature That Wanders, Tags Along and Startles Away — Measured area 14.476% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

STARTLE_SPEEDARRIVE_RADIUSWANDER_TURN
        else {
            pet.idle = targetSpeed > MOVE_SPEED ? 0 : pet.idle + dt;
            pet.state = pet.idle > IDLE_DELAY ? WANDER : FOLLOW;
            if (pet.state === FOLLOW) {
                const sp = MAX_SPEED * clamp((dist - STOP_DIST) / ARRIVE_RADIUS, 0, 1);
                wantX = dx / dist * sp;
                wantY = dy / dist * sp;
            }
            else {
                pet.wanderAng += smoothNoise(pet.clock * 0.7) * WANDER_TURN * dt;
                // near the stage edge, steer the heading back toward the center
                const edge = Math.min(pet.x, W - pet.x, pet.y, H - pet.y);
                if (edge < 70) {
                    const home = Math.atan2(H / 2 - pet.y, W / 2 - pet.x);
                    pet.wanderAng += wrapAngle(home - pet.wanderAng) * follow(3, dt);
                }
                wantX = Math.cos(pet.wanderAng) * WANDER_SPEED;
                wantY = Math.sin(pet.wanderAng) * WANDER_SPEED;
            }
        }

09A Koi Pond Scene Where Ripples Spread in the Fish's Wake

The water is a 240×150 grid of heights at 2px per cell, and waveStep sets each cell to half the sum of its four neighbors minus its height one step earlier, scaled by 0.985, sixty times a second, so a wave keeps 0.40 of its height after each second. Three koi steered by the same steerKoi as 02 stir the cells at their tails, and the pond floor is sampled at a spot shifted by the surface slope times 1.6 cells, which makes the pebbles waver. A single poke of height 100 spreads to a 28.64px radius after 15 steps, 50.99px after 30 and 94.53px after 60.

A Koi Pond Scene Where Ripples Spread in the Fish's Wake
Open in a new tab

09 · A Koi Pond Scene Where Ripples Spread in the Fish's Wake — Measured area 51.417% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

WAVE_DAMPWAKE_GAINREFRACT
    function waveStep() {
        const now = waveNow;
        const prev = wavePrev;
        for (let y = 1; y < GRID_H - 1; y += 1) {
            const row = y * GRID_W;
            for (let x = 1; x < GRID_W - 1; x += 1) {
                const i = row + x;
                prev[i] = ((now[i - 1] + now[i + 1] + now[i - GRID_W] + now[i + GRID_W]) * 0.5 - prev[i]) * WAVE_DAMP;
            }
        }
        waveNow = prev;
        wavePrev = now;
    }

What Makes Mouse Follow Animation Feel Alive Instead of Glued to the Pointer?

The body draws the path the head took

A pointer-glued sprite copies the pointer's position, so it stops the instant the pointer stops and turns the instant the pointer turns. In these scenes only the head is told where to go, and every joint behind it reads where the joint ahead of it went, so the body traces the curve the head drew a moment earlier. That is procedural animation JavaScript in its plainest form: the script stores no pose at all, only a rule that runs on every frame. followChain is called in 01, 02, 03, 08 and 09; 05 defines it but moves its joints along a trail of its own.

Fins and tentacles that run a beat late

A koi fish animation built from keyframes would likely loop a handful of tail poses. In 02 the tail fin is instead a spring that chases the tail segment's direction, and when the delay with the smallest error is measured, it trails by 133ms. The tentacles in 06 keep 92% of their previous motion, so they trail behind the bell; the same position-and-previous-position stepping drives the rope chains in JavaScript physics engines.

Distance Constraints for the Body, the Cosine Relation for Knees, a Threshold for Steps — the Math That Stands One Creature Up

A chain that only keeps distances

followChain asks one question per joint: in which direction is this joint from the one ahead right now? It then puts the joint back on that line at exactly the gap. Nothing is integrated, so the gap error over a whole lap of 01 stays at 3.2e-14px, which is floating-point noise.

Knees from the cosine relation

With the thigh a, the shin b and the hip-to-foot distance d forming a triangle, the angle at the hip satisfies cos A = (a² + d² − b²) / (2·a·d). The knee then sits a pixels from the hip, rotated from the hip-to-foot direction by bend times the arccosine of that value, and bend picks which side of the line the knee folds to.

Pulling from the tip and pulling back from the root

One pass of reachChain moves the tip to the target and fixes gaps back toward the root, then moves the root to its anchor and fixes gaps out to the tip. Starting from a tentacle hanging below a root at (0, 0) and aiming at (70, 40), the tip is 6.043px off after 1 pass, 2.698px after 2, 0.769px after 4, 0.086px after 8 and 0.001px after 16, while the gaps come out exact after every pass. Because 04 starts each frame from the previous frame's shape, REACH_ITER 4 is enough.

Friction that differs front and back turns a wriggle into a walk

The springs between caterpillar joints push and pull in equal pairs, so on their own they cannot move the body's center. Only the floor breaks that balance: a joint sliding back loses speed at a rate of 28 per second, one moving forward at 1.2, and the bunching wave becomes forward travel.

Where the Creature Breaks: a Body Folding When the Target Dives Inside It, Knees Popping at Out-of-Reach Distances, and Joints Stretching After a Tab Switch

When the target dives into the body

The 02 koi chain (14 joints, 13px apart) had its head driven into the spot of its own sixth joint at 200px/s for one second. With the bend limit switched off, neighbors folded back as far as 3.12 radians, close to 179°, up to 8 pairs of joints three or more apart overlapped, and the two closest joints came within 3.97px. The same test with MAX_BEND 0.35 gave 0 overlapping pairs, a closest distance of 37.42px and a largest bend of 0.35, with the body turning in an arc 102.32px from head to tail. Scene 01 shows no overlap on its figure-eight, but it moves the head straight at the target with no turning limit, so a visitor resting the pointer behind the snake's body would likely produce the same fold.

When the foot is out of reach

Math.acos returns NaN for any input outside −1 to 1. Feeding a leg with a 22px thigh and a 20px shin a distance of d = 45 gives cos A = 1.07, the knee becomes (NaN, NaN), and the shin direction and drawn foot that follow from it turn into NaN too, so the leg disappears from the canvas. The guard on the third line of solveKnee clamps d to 41.999, which keeps cos A at 1 and draws a straight leg with the knee at (22, 0.14) and the foot at (42, 0.02). In the 03 demo the hip-to-foot distance reaches 77.24px and passes 42px in 7% of moments, so that line is doing real work.

The first frame after a hidden tab

The 06 large jellyfish was put in one fixed state and advanced once with two different time steps. Passing a 2.4-second gap straight through, as a tab coming back after 2.4 seconds hidden would, stretched the tentacle links to 6.3 times their gap at most and 2.44 times on average, and the middle tentacle, 99px at rest, grew to 241.12px. Clamping dt to 0.05 seconds kept the links within 1.06 times and the tentacle at 100.77px. The only dt term in the new-position step of stepTentacle is the sink SINK * dt * dt, which pulls 172.8px down at 2.4 seconds and 0.075px at 0.05, and the gap fixing runs 3 passes whatever dt is, which cannot undo a pull that large.

    function frame(ts) {
        if (still || paused) {
            rafId = 0;
            return;
        }
        if (lastTs < 0) {
            lastTs = ts;
        }
        const dt = Math.min((ts - lastTs) / 1000, DT_MAX);
        lastTs = ts;
        if (dt > 0) {
            advance(dt);
        }
        draw();
        rafId = window.requestAnimationFrame(frame);
    }

All nine scenes guard against this twice. The frame above clips dt at DT_MAX, 0.05 seconds, and a visibilitychange listener stops the loop while the tab is hidden and, on return, resets lastTs to −1 so the clock starts fresh. To break a scene on purpose, open the download and change MAX_BEND, the true that 03 passes as guard, or DT_MAX; the password for the archive is hdkq4gtb.

Wider Joint Gaps, a Looser Bend Limit, a Farther Step Threshold — One Number That Changes a Creature's Character

Name Changed value Feel
Widen the joint gap 01 SEGMENT_GAP 12 → 20 The body grows from 300px to 500px, from 25% to 42% of the figure-eight lap, and the closest head-to-tail distance drops from 126.77px to 32.12px
Loosen the bend limit 02 MAX_BEND 0.35 → 0.7 The tightest turning radius shrinks from 37.33px to 18.96px, and frames touching the limit fall from 40% to 0%
Push the step threshold out 03 STEP_TRIGGER 38 → 60 Steps per second drop from 5.67 to 3.72 and the average stride grows from 54.28px to 79.12px

The body crosses itself instead of curving

At a gap of 20, the head-to-tail distance averages 162.44px instead of 205.23px, the largest neighbor bend rises from 0.29 to 1.18 radians, and overlapping joint pairs go from 0 to 3. The longer snake runs over its own body as it rounds a ring.

It does not fold, but it curls into a ball

At 0.7 the body can wrap through 481.28° instead of 240.64°, and on the figure-eight the largest bend is 0.4, so no frame reaches the limit. Run through the dive test from the previous section, the head-to-tail distance shrinks from 102.32px to 18.03px and the closest pair from 37.42px to 18.03px; no joints overlap (0 pairs in both), yet the koi coils into a tight ball.

It looks relaxed, but the legs drag

At 60 the lizard's hip-to-foot distance passes the 42px bone total in 31% of moments instead of 7%, peaking at 80.58px instead of 77.24px. It appears calmer, while the legs spend about four times as long stretched straight and dragging.

Resting Poses Under Reduced Motion — Accessibility with a Pause Button and a Scene Description

Scene descriptions and the pause button

In all nine scenes the canvas carries role="img" and an aria-label that puts the scene into words, so a screen reader announces something like "A pond where ripples spread behind three koi, the floor pattern wavers and lily pads bob." in place of the picture. The pill-shaped Pause button at the bottom right is the first Tab stop; Enter sets aria-pressed to true and freezes the scene, and pressing it again resumes. A 3px dark outline marks it when it has keyboard focus.

What reduced motion leaves on screen

With reduced motion switched on in the operating system, no loop runs, and each scene draws one frame of its creature lying in a gentle S-shaped resting pose. A change listener moves to that pose straight away if the setting flips mid-visit. Moving the pointer over the stage then redraws only the target and the eyes, so the creature holds still yet keeps looking at the visitor.

The status line in 08

Only 08 has a status line in the top left, reading "Wandering", "Following" or "Startled, running away". During the demo its aria-live is off, so a screen reader does not announce states that change by themselves, and it switches to polite after the visitor's first pointer or button input. That choice appears meant to keep an unattended demo from talking over everything else.

Questions to Answer Before Adding a Cursor-Following Creature to a Website

Does an animal following cursor effect like this also follow a finger on a phone?

Yes. The stage takes its target from pointermove and pointerdown in Pointer Events, so a touch counts the same as a mouse. When the finger lifts and pointerleave or pointercancel fires, the target eases back to the figure-eight.

Will a large hero background slow the page down?

The canvas keeps 480×300 internal coordinates and caps the device pixel ratio at 2, so stretching the stage wider does not add any simulation work. Even 09, the heaviest scene, runs at most 4 wave steps of its 240×150 grid per frame and draws the sand and its 48 pebbles once at start-up. For other scenes that compute every pixel on a canvas, see the canvas warp and droplet effects.

Where do I change a creature's color or thickness?

Colors are RGB arrays near the top of each script, such as CORAL, LAVENDER, BUTTER and SAGE; thickness is BODY_WIDTH 15 in 01 or KOI_SIZE 17 in 02. Every outline uses the single INK color, so on a darker background lighten that value as well or the creature sinks into it. For 3D objects that turn toward the pointer, the pointer-following 3D scenes cover that side.

Enter the archive password

The password is inside this article. You will find it as you read.