Skip to content
animorio
IT
All components

The relief map card

Cards, Hover, 3DCanvas, GSAPAdvancedCompressed weight: Demo 15 KBGSAP 28 KBFreeDownload the zip

The card of a mountain loop whose map is made of paper sheets cut along the contour lines: as the pointer comes near, the sheets rise one after another on springs and the map becomes a relief model seen from where the pointer is, with cut edges and shadows. Preview route flies over the loop: the camera comes close and follows a marker that climbs the sheets step by step, while the distance and the ascent count up with it.

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 relief map card</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/geologica@5.3.0/full.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the card of a mountain loop. Its map is a stack of paper sheets cut along the contour lines: as the pointer
         comes near, the sheets rise one after another into a relief seen from where the pointer is. Preview route
         flies over the loop with a marker that climbs the sheets. The map is drawn by the script -->
    <article class="trail" aria-labelledby="trail-title" data-trail>
      <div class="trail__map" data-map-box>
        <canvas class="trail__canvas" role="img" aria-label="Relief map of the loop: from Larch Lake up the ridge to Cairn Peak, down the east valley and back to the lake" data-map></canvas>
      </div>
      <div class="trail__body">
        <h2 class="trail__title" id="trail-title" data-title>Cairn Ridge Loop</h2>
        <p class="trail__where">A day loop from Larch Lake</p>
        <dl class="trail__facts">
          <div>
            <dt>Distance</dt>
            <dd><span data-count>11.4</span><span class="trail__unit"> km</span></dd>
          </div>
          <div>
            <dt>Ascent</dt>
            <dd><span data-count>720</span><span class="trail__unit"> m</span></dd>
          </div>
        </dl>
        <button class="go" type="button" data-go>
          <span class="go__fill" aria-hidden="true" data-fill></span>
          <span class="go__icon" aria-hidden="true">
            <span class="go__glyph go__glyph--play" data-icon="play"><svg viewBox="0 0 24 24"><path d="M8.5 5.8v12.4a.8.8 0 0 0 1.2.7l9.6-6.2a.8.8 0 0 0 0-1.4L9.7 5.1a.8.8 0 0 0-1.2.7z"/></svg></span>
            <span class="go__glyph" data-icon="cancel"><svg viewBox="0 0 24 24"><path d="M7 7l10 10M17 7L7 17"/></svg></span>
            <span class="go__glyph" data-icon="again"><svg viewBox="0 0 24 24"><path d="M18.6 13.2a6.8 6.8 0 1 1-1.9-5.9"/><path d="M17.6 3.8v4.1h-4.1"/></svg></span>
          </span>
          <span class="go__label" data-label>Preview route</span>
        </button>
        <p class="sr" aria-live="polite" data-live></p>
      </div>
    </article>
  </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.

35 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 relief map card” from Animorio and fit it into my site

What it does

On a white background a white card with round corners. At the top the map of the loop, like a contour map: Larch Lake in light blue with water lines that follow the shore, green meadows, then bands of sand and hazel rising to the white summit, Cairn Peak 2,214 m with its triangle; every band has its brown edge and two faint contour lines. The trail is a red dashed line on a white thread: it starts from a red and white waymark on the shore, climbs in switchbacks, follows the ridge to the summit and comes back down the east valley. Under the map: Cairn Ridge Loop, A day loop from Larch Lake, Distance 11.4 km, Ascent 720 m and the dark green button Preview route. As it arrives the card rises and the sheets fall onto the map one after another, the lowest first. As the pointer comes near, the map becomes a paper relief model: the sheets rise one after another on springs that bounce a little, the high ones last; on the pointer’s side the thickness of their cut edges shows, and every sheet casts a soft shadow on the one below. The summit slides away from the pointer and grows a little, and moving the pointer goes round the relief; the further away the pointer, the less the map rises. Meanwhile the corners of the title’s letters grow sharper. With the pointer on the button, the button swells a little and the trail’s dashes start to walk round the loop. Pressed, the button becomes Stop preview and slowly fills with a lighter green: the camera comes close and follows a red arrow along the trail, which climbs the sheets step by step with a little hop and leaves a solid red line behind it, while Distance and Ascent count up from zero; the names on the map fade while the camera is close. At the end the camera goes back, the trail stays solid red, the facts go back to the totals and the button says Preview again. Pressed during the flight, the trail rubs itself out backwards and everything goes back to rest.

How it works

The ground

The relief is a height function: a ridge (a Gaussian along a segment), the summit, a shoulder and two hollows for the lakes, all bent by an octave noise that shifts the coordinates and adds detail. The script works it out on a grid, a point every 1.5 pixels, together with the steepness; then every pixel of the sheets takes its height and steepness by interpolation, once.

The sheets, cut pixel by pixel

For each sheet, (height − level) / steepness is the distance in pixels from the contour line: from it come the one-pixel soft edge, the brown line along the cut, two faint lines printed on the sheet and the paper grain. Every sheet becomes three images: the face, the edge (the same shape, darker) and the shadow (the shape blurred with shadowBlur, drawn off the canvas so only the shadow stays). The sheets are cut one per frame while they fall in at the start.

The perspective

The eye stands EYE pixels above a point E of the map: a sheet z high is drawn grown by EYE / (EYE − z) round E, so the high sheets grow and slide away from the eye. The edge is the dark shape repeated at the heights between the sheet below and its own, just enough to leave no gaps; the shadow falls on the sheet below, moved away from the light. E follows the pointer, stretched and moved a little south, so with the pointer in the middle the relief is already seen at a slant.

The springs

The pointer’s distance from the map, over REACH pixels, says how far to raise it. Every sheet has its own spring with SPRING, worked out in steps of 4 milliseconds, and the higher the sheet the softer its spring (LAG): the stack opens from the bottom and the summit arrives last. A sheet stands RISE pixels over the one below for its lift; at the start it falls from a little higher as it appears.

The trail on the sheets

The trail is a closed Catmull-Rom curve, measured a point per pixel; every point knows which sheet it lies on. The stretches are drawn right after their sheet, so the higher sheets cover them where they should, and one point more at each end draws the step. The white thread runs only along the sheet, the red also over the step. The dashes have a lineDashOffset equal to the length walked, so the pattern carries on from one sheet to the next; with the button pointed at, it slides forward.

The flight

A GSAP timeline takes the share of the loop from 0 to 1 in FLIGHT seconds. The camera comes close up to ZOOM and follows the arrow on springs, the eye stays behind it, the arrow hops onto each sheet on a spring of its own. Distance counts with the length walked, Ascent with the climb gathered along the height field, and the totals stay the words of the page. Stopped, the trail rubs itself out backwards and the facts go back to the totals.

Values to change

NameFileDefaultWhat it changes
SHEETSscript.js12How many sheets of paper the relief is cut into.
RISEscript.js3.8How high each sheet stands over the one under it, in pixels.
SPRINGscript.js[150, 0.42]The sheets’ spring: how stiff it is and how much it is held back (1 = no bounce).
LAGscript.js0.55How much later the top sheet answers than the lowest (0 = all together).
EYEscript.js460How high the eye is over the map, in pixels: lower gives a stronger relief.
REACHscript.js460How far from the map, in pixels, the pointer still raises it.
FLIGHTscript.js6.5How many seconds the flight over the route lasts.
ZOOMscript.js1.4How close the camera comes during the flight.
SUMMITscript.js['Cairn Peak', '2,214 m']The summit’s name and height on the map.
LAKEscript.js'Larch Lake'The lake’s name on the map.
WORDSscript.js['Stop preview', 'Preview again']The button’s words during the flight and after it.

Try them in the Playground

When to use it

For a route card in a hiking, running or cycling app; for a mountain hut, a park, a trail race, an island to visit: wherever a place has a climb to tell.

On phones

The card is 340 pixels wide, or the screen minus 32, and the map is drawn again at the real size. A finger resting on the map raises it and looks at it from the finger’s side (the map has touch-action: none, so over it the finger does not scroll the page), moving it goes round the relief and, let go, the sheets settle. Preview route is tapped like any button and the flight is the same.

With reduced motion

The relief stands already raised, seen from the south, and does not follow the pointer; no sheets falling in at the start and no walking dashes. Preview route does not fly: the trail turns solid red in a fade and the button moves to Preview again; the button’s words and icons change in a fade, without sliding.

Pitfalls

  • The white thread of a stretch, drawn over the step too, covered the red of the stretch before, and the line already walked looked broken at every contour: the white thread runs only along the sheet, the red over the step as well.
  • Cutting twelve sheets pixel by pixel at a density raised for the zoom took more than 300 milliseconds, too much for a phone: now the sheets have the screen’s density, the grain is a repeated tile, the steepness is kept as 1 / steepness (multiplications instead of divisions) and the sheets are cut one per frame while they fall in at the start, about 7 milliseconds each.
  • With the camera close, the window’s edge cut the names of the summit and of the lake in half: the labels fade as the camera comes close, and the summit’s name slides down when the relief pushes it against the top edge.
  • With lineDashOffset set to minus the start of each stretch, the dash pattern jumped from one sheet to the next: the right phase is plus the start, that is the length along the trail.
  • After a stopped flight the ascent stayed at 719 instead of 720: the count going back to the totals never reached its last step; at the end the exact total is written.
  • The previews’ autopilot takes the pointer all over the window, and with a reach of 360 pixels the map stayed flat half the time: with REACH at 460 it still rises half way at the sides, seen at a slant.

Libraries

GSAP 3.15.0, Geologica (font variabile) 5.3.0.

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

Testing

Tested on 6 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 components