Skip to content
animorio
IT
Back to the library

The otter in the river

Hover, SVGGSAPAdvancedCompressed weight: Demo 27 KBGSAP 28 KBFreeDownload the zip

The header of a made-up river nature reserve, Holt, in the rich style and full of detail: a river at sunset seen from the side, above and below the water. A big otter with wet fur, whiskers and webbed feet floats on its back tapping a stone on a shell, rubs its face, dives, swims under the water through the eel grass and comes up with a fish that it eats on its back, then rolls over. Around it a willow, a boardwalk, a fallen log with a kingfisher that dives, a heron, fish, a crayfish, lily pads, reeds and dragonflies. A pointer or a finger near the water makes rings and the otter swims over to have a look.

The code

A folder that runs on its own: index.html, style.css, script.js and the libraries from a CDN. The React and Vue tabs hold the same demo as a ready component, which switches its animations off when it leaves the page.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>The otter in the river</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/full.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/bricolage-grotesque@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <!-- a made-up river reserve, Holt. A river seen from the side, above and below the water: hills in the haze, alders,
       birches and a heron on the far bank, a willow hanging over the water, a boardwalk, a fallen log with a kingfisher
       on its branch, banks with roots, stones and ferns, lily pads, eel grass, fish, a crayfish under a stone, light
       moving on the riverbed. A big otter floats on its back tapping a stone on a shell, grooms its face, rolls over,
       dives, swims under the water and comes up with a fish to eat. A pointer or a finger near the water makes rings,
       and the otter swims over to have a look. All drawn by the script -->
  <main class="riserva" data-riserva>
    <svg class="scena" data-scena viewBox="0 0 1600 900" preserveAspectRatio="xMidYMax slice" role="img" aria-labelledby="scena-nome">
      <title id="scena-nome">A river otter floating on its back by a boardwalk, grooming, diving and coming up with a fish, among lily pads, reeds, a willow and a kingfisher</title>
      <defs>
        <linearGradient id="g-cielo" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="var(--cielo-alto)"/>
          <stop offset=".7" stop-color="var(--cielo)"/>
          <stop offset="1" stop-color="var(--cielo-basso)"/>
        </linearGradient>
        <radialGradient id="g-sole" cx=".5" cy=".5" r=".5">
          <stop offset="0" stop-color="var(--sole)" stop-opacity=".95"/>
          <stop offset=".3" stop-color="var(--sole)" stop-opacity=".5"/>
          <stop offset="1" stop-color="var(--sole)" stop-opacity="0"/>
        </radialGradient>
        <linearGradient id="g-nuvola" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="#fffaf0"/>
          <stop offset=".6" stop-color="#fbe9cb"/>
          <stop offset="1" stop-color="#eec99a"/>
        </linearGradient>
        <linearGradient id="g-colline-1" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="var(--collina-lontana)"/>
          <stop offset="1" stop-color="var(--cielo-basso)"/>
        </linearGradient>
        <linearGradient id="g-colline-2" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="var(--collina-chiara)"/>
          <stop offset="1" stop-color="#b9c79a"/>
        </linearGradient>
        <linearGradient id="g-colline-3" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="var(--collina)"/>
          <stop offset="1" stop-color="#83a873"/>
        </linearGradient>
        <linearGradient id="g-foschia" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="var(--cielo-basso)" stop-opacity="0"/>
          <stop offset="1" stop-color="var(--cielo-basso)" stop-opacity=".55"/>
        </linearGradient>
        <linearGradient id="g-acqua" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="var(--acqua-chiara)"/>
          <stop offset=".12" stop-color="var(--acqua)"/>
          <stop offset=".5" stop-color="var(--acqua-media)"/>
          <stop offset="1" stop-color="var(--acqua-fonda)"/>
        </linearGradient>
        <linearGradient id="g-velo" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="var(--acqua)" stop-opacity=".3"/>
          <stop offset=".35" stop-color="var(--acqua-media)" stop-opacity=".34"/>
          <stop offset="1" stop-color="var(--acqua-fonda)" stop-opacity=".58"/>
        </linearGradient>
        <linearGradient id="g-raggio" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="#fff6d6" stop-opacity=".55"/>
          <stop offset=".6" stop-color="#e8fff0" stop-opacity=".12"/>
          <stop offset="1" stop-color="#e8fff0" stop-opacity="0"/>
        </linearGradient>
        <linearGradient id="g-sabbia" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="var(--sabbia)"/>
          <stop offset="1" stop-color="var(--sabbia-scura)"/>
        </linearGradient>
        <linearGradient id="g-riva" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="var(--terra)"/>
          <stop offset=".45" stop-color="var(--terra-media)"/>
          <stop offset="1" stop-color="var(--terra-scura)"/>
        </linearGradient>
        <linearGradient id="g-pelo-galla" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="var(--pelo-chiaro)"/>
          <stop offset=".28" stop-color="#b07c4e"/>
          <stop offset=".6" stop-color="var(--pelo)"/>
          <stop offset="1" stop-color="var(--pelo-scuro)"/>
        </linearGradient>
        <linearGradient id="g-pelo" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="var(--pelo-scuro)"/>
          <stop offset=".55" stop-color="var(--pelo)"/>
          <stop offset="1" stop-color="#a87a4f"/>
        </linearGradient>
        <linearGradient id="g-testa" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="var(--pelo-scuro)"/>
          <stop offset=".7" stop-color="var(--pelo)"/>
          <stop offset="1" stop-color="#94643d"/>
        </linearGradient>
        <linearGradient id="g-muso" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="#d9b48a"/>
          <stop offset="1" stop-color="var(--pelo-chiaro)"/>
        </linearGradient>
        <radialGradient id="g-sasso" cx=".35" cy=".3" r=".8">
          <stop offset="0" stop-color="#d3ccbd"/>
          <stop offset=".55" stop-color="#9a9384"/>
          <stop offset="1" stop-color="#5a554c"/>
        </radialGradient>
        <radialGradient id="g-sasso-scuro" cx=".35" cy=".3" r=".8">
          <stop offset="0" stop-color="#a39a86"/>
          <stop offset=".6" stop-color="#6c665a"/>
          <stop offset="1" stop-color="#3d3a33"/>
        </radialGradient>
        <radialGradient id="g-sasso-caldo" cx=".35" cy=".3" r=".8">
          <stop offset="0" stop-color="#d8bf98"/>
          <stop offset=".6" stop-color="#9c7f5a"/>
          <stop offset="1" stop-color="#5e4a33"/>
        </radialGradient>
        <radialGradient id="g-foglia" cx=".4" cy=".35" r=".75">
          <stop offset="0" stop-color="var(--ninfea-chiara)"/>
          <stop offset="1" stop-color="var(--ninfea)"/>
        </radialGradient>
        <linearGradient id="g-ala" x1="0" y1="0" x2="1" y2="0">
          <stop offset="0" stop-color="#e9fbff" stop-opacity=".7"/>
          <stop offset="1" stop-color="#e9fbff" stop-opacity=".25"/>
        </linearGradient>
        <linearGradient id="g-pesce" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="#5f6f43"/>
          <stop offset=".45" stop-color="#a9b382"/>
          <stop offset=".7" stop-color="#dfe3cf"/>
          <stop offset="1" stop-color="#f4f1e2"/>
        </linearGradient>
        <linearGradient id="g-pesce-2" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="#6b5a33"/>
          <stop offset=".45" stop-color="#c2a463"/>
          <stop offset=".75" stop-color="#ead9ad"/>
          <stop offset="1" stop-color="#f6eedb"/>
        </linearGradient>
        <pattern id="p-squame" width="7" height="6" patternUnits="userSpaceOnUse">
          <path d="M0 3A3.5 3.5 0 0 0 7 3M-3.5 6A3.5 3.5 0 0 0 3.5 6M3.5 6A3.5 3.5 0 0 0 10.5 6M-3.5 0A3.5 3.5 0 0 0 3.5 0M3.5 0A3.5 3.5 0 0 0 10.5 0" fill="none" stroke="#2c3220" stroke-width=".7" opacity=".5"/>
        </pattern>
        <linearGradient id="g-martin-dorso" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="#3fc0d6"/>
          <stop offset=".5" stop-color="#1886a8"/>
          <stop offset="1" stop-color="#0f5f7e"/>
        </linearGradient>
        <linearGradient id="g-martin-ventre" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="#f3a04a"/>
          <stop offset="1" stop-color="#d9702a"/>
        </linearGradient>
        <linearGradient id="g-tronco" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="#9b7a55"/>
          <stop offset=".5" stop-color="#6e5338"/>
          <stop offset="1" stop-color="#3e2d1d"/>
        </linearGradient>
        <linearGradient id="g-betulla" x1="0" y1="0" x2="1" y2="0">
          <stop offset="0" stop-color="#e9e2d4"/>
          <stop offset=".6" stop-color="#fbf7ee"/>
          <stop offset="1" stop-color="#cfc6b4"/>
        </linearGradient>
        <linearGradient id="g-salice" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="#7fa54e"/>
          <stop offset="1" stop-color="#a9c867"/>
        </linearGradient>
        <linearGradient id="g-gambero" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="#8a4a2a"/>
          <stop offset="1" stop-color="#c47a45"/>
        </linearGradient>
        <linearGradient id="g-riverbero" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="var(--sole)" stop-opacity=".6"/>
          <stop offset="1" stop-color="var(--sole)" stop-opacity="0"/>
        </linearGradient>
        <clipPath id="c-sotto"><path data-clip-sotto d="M0 470H1600V900H0Z"/></clipPath>
      </defs>

      <rect width="1600" height="520" fill="url(#g-cielo)"/>
      <circle cx="1230" cy="160" r="260" fill="url(#g-sole)"/>
      <circle cx="1230" cy="160" r="46" fill="var(--sole)" opacity=".75"/>
      <g data-nuvole></g>
      <g data-lontano></g>
      <g data-pontile></g>

      <!-- under the water -->
      <path data-acqua fill="url(#g-acqua)" d="M0 470H1600V900H0Z"/>
      <g clip-path="url(#c-sotto)">
        <use href="#lontano-forme" data-riflesso opacity=".22" transform="translate(0 940) scale(1 -1)"/>
        <g data-strisce></g>
        <g data-raggi></g>
        <g data-fondale></g>
        <g data-caustiche></g>
        <g data-alghe></g>
        <g data-gambi></g>
        <g data-gambero></g>
        <g data-pesci></g>
        <g data-particelle></g>
        <g data-sotto-superficie></g>
      </g>
      <g data-rive-sotto></g>

      <!-- the lily pads, the otter in front of them, the kingfisher, the bubbles, then the water veil that tints everything under the surface -->
      <g data-ninfee></g>
      <g data-lontra></g>
      <g data-martin></g>
      <g data-bolle></g>
      <path data-velo fill="url(#g-velo)" d="M0 470H1600V900H0Z"/>

      <!-- on the surface and above it -->
      <path data-luccichio fill="none" stroke="var(--schiuma)" stroke-width="2.2" stroke-linecap="round" opacity=".55"/>
      <g data-riverbero></g>
      <g data-cerchi></g>
      <g data-schizzi></g>
      <g data-salice></g>
      <g data-canne></g>
      <g data-erba></g>
      <g data-libellula></g>
    </svg>

    <header class="testata">
      <p class="marchio">
        <svg viewBox="0 0 32 32" aria-hidden="true"><path d="M3 20c4-4 8-4 13 0s9 4 13 0M3 26c4-4 8-4 13 0s9 4 13 0M10 13c2-5 9-7 13-3"/></svg>
        Holt
      </p>
      <p class="sotto">River otter reserve</p>
    </header>

    <div class="testo">
      <h1 class="titolo">Wild water, <em>close up.</em></h1>
      <p class="riga">Watch the otters fish, play and dive along a mile of quiet river. Open dawn to dusk.</p>
      <div class="azioni">
        <a class="pillola" href="#visit"><span>Plan a visit</span></a>
      </div>
    </div>

    <p class="aiuto" data-aiuto>
      <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2 15c2.5-2.5 5-2.5 7.5 0s5 2.5 7.5 0 3.5-2 5 -.5M6 9.5a6 6 0 0 1 12 0"/></svg>
      <span>Move near the water</span>
    </p>
  </main>

  <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
  <script src="script.js"></script>
</body>
</html>

Playground

Change values, colours and text with the controls and watch the animation respond live. Or write in the code yourself. Then download the folder with your changes.

45 values to change

Or ask Claude Code for it

With the Animorio connector, Claude Code takes it from here, guide included, and fits it into your project. Paste this request:

Take “The otter in the river” from Animorio and fit it into my site

What it does

A river scene drawn in SVG, in layers. Above: the ochre late-afternoon sky with clouds drifting, three rows of hills in the haze, alders, birches and poplars on the far bank and a heron standing in the shallows that now and then lowers its neck; a willow hanging over the water with swaying strands; a boardwalk of planks with grain, nails and weed on its posts; a fallen log half under the water with weed and the branch of a kingfisher, which now and then dives and comes back with a small fish; banks of layered earth with roots, mossy stones, grass hanging over the water, ferns, reeds and cattails. Below: water darker with depth, the surface seen from underneath, the far bank reflected and broken by the waves, the sun’s glints, rays and caustics of light moving on the bed, drifting specks, sand, gravel, stones, a river mussel, a crayfish peeking from under a stone, eel grass, the lily stems, three scaly fish that dart away. The otter is big and built from hinged parts: a broad head with a pale muzzle, a shiny nose, eyes with a catchlight, whiskers, two-tone fur with wet tufts and a sheen, short arms with fingers, webbed feet, a thick tail. It floats tapping its stone, rubs its face, dives, swims with silver air along its back, comes up dripping, every other time with a fish it eats in three bites, then rolls over.

How it works

The surface and the veil

superficie(x, t) adds two sine waves and the waves left by splashes, which spread and die out. That line closes three shapes: the water behind, the clip for what lies below (reflection, rays, bed) and a translucent veil in front of the otter. So whatever is below the line takes the colour of the river, even the half of the floating otter.

Parts that stay attached

The otter is a group of parts: head, legs, webbed feet and tail each rotate around their joint (translate to the joint, then rotate). Reeds and eel grass are blades that start from a fixed base and bend towards the tip; the cattail head sits on the curve at the right point and takes its tangent. The lily pads ride the surface and their stems always reach the bed.

Floating and diving

Two drawings of the otter: on its back and swimming. The dive is a timeline: the otter turns, dips its nose, the splash hides the swap between the two drawings, then a value p from 0 to 1 takes it under the water along an arc (a sine) and turns it with the slope; at the end another splash and the rise with back.out.

Splashes, rings and bubbles

A splash is a crown that grows and fades, drops with gravity that fall back and make rings, a wave along the surface and a push to the lily pads nearby. Bubbles rise with a wobble and pop at the surface.

The pointer

Near the water the pointer leaves rings now and then and bends the reeds nearby; the floating otter turns towards it, paddles over and looks, and the dive waits while it stays curious.

Lots of detail, little work per frame

Almost the whole scene is drawn once at the start (hills, trees, layered banks, mossy stones, boardwalk, riverbed). On every frame only what moves is worked out again: the surface, the caustics (six wobbling points per cell), the specks, the eel grass, the willow, the reeds, the fish, the crayfish, the birds and the otter. No filters: light and shade are gradients and strokes.

What the otter does

A small state machine with GSAP timelines: it floats and taps the stone, rubs its face with its paws (the stone waits on the shell), dives, swims, comes up dripping. Every other time it comes up with a fish in its mouth: it holds it in its paws and eats it in three bites, then rolls over (the belly-up drawing and the back-up drawing swap halfway round) and dives again.

Values to change

NameFileDefaultWhat it changes
FLOAT_TIMEscript.js2.6Seconds the otter floats and taps its stone before doing something else (rubbing its face, diving).
SWIM_TIMEscript.js2.8Seconds of swimming under the water.
DEPTHscript.js190How deep the dive goes, in scene units (1600 by 900).
WAVESscript.js1How high the little waves on the surface are (0 = still water).
SWAYscript.js1How much the reeds, the willow, the grass, the ferns and the eel grass sway.
RIPPLESscript.js1How big the rings are that the pointer leaves on the water.
CURIOUSscript.js1How fast the otter paddles over to the pointer (0 = it never comes).

Try them in the Playground

When to use it

As the opening of a nature reserve, a park, an aquarium or a zoo, a wildlife charity or a hiking guide; in general when the first screen should show a living place and make people want to touch it.

On phones

The same animations with the sizes adapted: the title goes across the top, the river fills the rest and the otter dives and comes up inside the part that can be seen, measured again on every resize. A finger near the water makes rings and calls the otter like the pointer.

With reduced motion

No waves, dives, splashes, bubbles or motion: the scene stands still, with the otter floating in its place; the words appear without moving.

Pitfalls

  • The water veil sits above the otter but below the lily pads and reeds: so the part under water takes the river colour and what is above stays clean. Reeds standing in the water have a separate stem, under the veil, down to the bed.
  • The dive swaps two drawings (on its back and swimming): the swap happens at the same moment as the splash, which hides it.
  • The visible part is measured again on resize (preserveAspectRatio slice crops the sides on phones): the two spots where the otter dives and comes up always stay inside.

Libraries

GSAP 3.15.0, Fraunces (font variabile) 5.3.0, Bricolage Grotesque (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 27 KB; the libraries: GSAP 28 KB.

Testing

Tested on 22 October 2026

  • Desktop: Done
  • Phone: Done
  • Reduced motion: Done
  • Safari on iPhone: To do

License

Use it in your own projects and in your clients’ projects. Do not resell it or redistribute it as is. GSAP stays under its own Standard license. Full license.

All animations