Skip to content
animorio
IT
Back to the library

The images that open like an accordion

Hover, ImagesGSAP, CSS onlyMediumCompressed weight: Demo 6.6 KBGSAP 28 KBFreeDownload the zip

A minimal-style hover gallery for an invented studio’s prints: six narrow panels between hairlines; the one under the pointer opens on a spring while the others make room, the print inside slides slower than the edge and the upright name lies down into a title that gains width and weight.

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>Sallow, landscape prints</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/archivo@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage" data-stage>
    <header class="top" data-enter>
      <span class="top__name">Sallow</span>
      <span class="top__note">Landscape prints, autumn edition</span>
    </header>

    <!-- the gallery: narrow panels side by side between hairlines; the panel under the pointer opens wide on a spring,
         the others make room, the print inside slides slower than the edge and the name turns from upright to level -->
    <section class="gallery" data-gallery aria-label="Prints">
      <button class="panel" type="button" data-panel style="--paper: #d8d1c2">
        <span class="panel__art" data-art>
          <svg viewBox="0 140 600 660" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
            <rect width="600" height="800" class="paper" />
            <circle class="accent" cx="380" cy="300" r="46" />
            <path d="M0 420H600" class="ink" />
            <path d="M0 470H600M0 515H600M0 556H600M0 592H600M0 624H600M0 652H600M0 676H600M0 697H600M0 715H600" class="fine" />
            <path d="M0 420C120 404 210 402 300 412S470 426 600 404V420H0Z" class="fill" />
          </svg>
        </span>
        <span class="panel__upright" data-upright>Tidewater</span>
        <span class="panel__title" data-title>Tidewater</span>
        <span class="panel__line" data-line>Low sun on a flat sea, nine lines of water.</span>
      </button>

      <button class="panel" type="button" data-panel style="--paper: #cdd1c9">
        <span class="panel__art" data-art>
          <svg viewBox="0 140 600 660" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
            <rect width="600" height="800" class="paper" />
            <path d="M0 800V560H120V500H230V430H340V360H450V300H600V800Z" class="fill" />
            <path d="M0 560H120V500H230V430H340V360H450V300H600" class="ink" />
            <path d="M60 560V800M175 500V800M285 430V800M395 360V800M525 300V800" class="fine" />
            <rect class="accent" x="438" y="262" width="24" height="38" />
          </svg>
        </span>
        <span class="panel__upright" data-upright>Quarry</span>
        <span class="panel__title" data-title>Quarry</span>
        <span class="panel__line" data-line>Steps of cut stone climbing to one red marker.</span>
      </button>

      <button class="panel" type="button" data-panel style="--paper: #ddd3c1">
        <span class="panel__art" data-art>
          <svg viewBox="0 140 600 660" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
            <rect width="600" height="800" class="paper" />
            <path d="M0 470H600M0 560H600M0 650H600M0 740H600" class="fine" />
            <g class="fill">
              <circle cx="60" cy="440" r="26" /><circle cx="160" cy="440" r="26" /><circle cx="260" cy="440" r="26" /><circle cx="360" cy="440" r="26" /><circle cx="460" cy="440" r="26" /><circle cx="560" cy="440" r="26" />
              <circle cx="110" cy="528" r="30" /><circle cx="210" cy="528" r="30" /><circle cx="310" cy="528" r="30" /><circle cx="410" cy="528" r="30" /><circle cx="510" cy="528" r="30" />
              <circle cx="60" cy="616" r="34" /><circle cx="160" cy="616" r="34" /><circle cx="360" cy="616" r="34" /><circle cx="460" cy="616" r="34" /><circle cx="560" cy="616" r="34" />
            </g>
            <circle class="accent" cx="260" cy="616" r="34" />
          </svg>
        </span>
        <span class="panel__upright" data-upright>Orchard</span>
        <span class="panel__title" data-title>Orchard</span>
        <span class="panel__line" data-line>Rows of apple trees, and the one that ripened first.</span>
      </button>

      <button class="panel" type="button" data-panel style="--paper: #d4cdd3">
        <span class="panel__art" data-art>
          <svg viewBox="0 140 600 660" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
            <rect width="600" height="800" class="paper" />
            <path d="M0 520C140 440 260 470 360 430S520 360 600 380V800H0Z" class="fill" />
            <path d="M0 600C120 560 250 590 380 540S540 500 600 510" class="ink" />
            <path d="M0 660C150 630 280 650 400 610S560 580 600 590M0 720C130 700 260 712 420 680S560 660 600 664" class="fine" />
            <circle class="accent" cx="150" cy="250" r="22" />
          </svg>
        </span>
        <span class="panel__upright" data-upright>Dunes</span>
        <span class="panel__title" data-title>Dunes</span>
        <span class="panel__line" data-line>Wind-drawn ridges under a small evening moon.</span>
      </button>

      <button class="panel" type="button" data-panel style="--paper: #d9cebd">
        <span class="panel__art" data-art>
          <svg viewBox="0 140 600 660" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
            <rect width="600" height="800" class="paper" />
            <path d="M170 800V420A130 130 0 0 1 430 420V800Z" class="fill" />
            <path d="M170 800V420A130 130 0 0 1 430 420V800" class="ink" />
            <path d="M230 800V430A70 70 0 0 1 370 430V800" class="fine" />
            <path d="M0 800H600" class="ink" />
            <rect class="accent" x="284" y="560" width="32" height="240" />
          </svg>
        </span>
        <span class="panel__upright" data-upright>Kiln</span>
        <span class="panel__title" data-title>Kiln</span>
        <span class="panel__line" data-line>A brick arch with the fire still glowing inside.</span>
      </button>

      <button class="panel" type="button" data-panel style="--paper: #c9d1ce">
        <span class="panel__art" data-art>
          <svg viewBox="0 140 600 660" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
            <rect width="600" height="800" class="paper" />
            <path d="M120 800V460L300 330L480 460V800" class="ink" />
            <path d="M180 800V417M240 800V374M300 800V330M360 800V374M420 800V417M120 540H480M120 620H480M120 700H480" class="fine" />
            <path d="M120 460L300 330L480 460" class="ink" />
            <circle class="accent" cx="300" cy="470" r="20" />
          </svg>
        </span>
        <span class="panel__upright" data-upright>Glasshouse</span>
        <span class="panel__title" data-title>Glasshouse</span>
        <span class="panel__line" data-line>Iron ribs, thin panes, one bloom under the roof.</span>
      </button>
    </section>

    <footer class="bottom" data-enter>
      <span>Six prints on cotton paper</span>
      <span class="bottom__hint" data-hint>Pass over a print</span>
    </footer>
  </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 images that open like an accordion” from Animorio and fit it into my site

What it does

A warm white page with an invented print studio’s name at the top, Sallow, and below it a row of six narrow panels divided by hairlines: six landscape prints drawn in lines and soft fills (the sea with a low sun, the stepped quarry, the orchard, the dunes with the moon, the arched kiln, the glasshouse), each with its name written upright at the bottom. On opening the panels rise one after another like blinds. With the pointer over a panel, it widens to take more than half the row and the others narrow to make room: every panel has its own spring, which arrives a hair too far and settles, and the neighbours start a moment later, the farthest last. As the panel widens the print inside slides slower than the edge, so it is revealed slowly; the upright name fades and in its place a large title rises, widening and gaining weight, on a veil the colour of the paper, with a line about the print below; the red detail of the print (the sun, the marker, the ripe apple) lights up only when the panel is open. Moving to another panel the row redistributes in a wave. When nobody moves the pointer, an invisible pointer opens the prints one after another.

How it works

Weights instead of widths

Every panel has a weight: 1 when closed and, when open, the one that makes it take OPEN_RATIO of the row. On every frame the row is divided in proportion to the weights and the panels take left and width in pixels (top and height on a phone): the sum is always the whole row, even while the springs swing.

One spring per panel, in a wave

When the open panel changes, each one receives its new weight with a delay of STAGGER per place of distance: neighbours start first. The weight follows its target with a spring integrated in 1/240 second steps on GSAP’s ticker (stiffness and damping in SPRING), so it overshoots a hair and settles.

The print slower than the edge

The print is an SVG as large as the open panel, so it never distorts: the panel shows a window onto it. Its position is (panel width minus print width) times PARALLAX: with 0.5 it stays centred, with less it moves slower than the edge and is revealed little by little.

From upright name to title

The panel’s openness (0 to 1) drives: the upright name, which fades early; the title, which appears halfway rising 14 pixels and goes from the starting TITLE_WIDTH and TITLE_WEIGHT to the final ones with Archivo’s axes; the line below, towards the end; the paper-coloured veil (a CSS variable --o) and the print’s red detail.

The ghost pointer and the tap

A repeating GSAP timeline carries a number from 0 to 1 in STEP seconds per print: from it comes the panel to open, always in the same order, so the round comes back the same. The real pointer rules for 2.4 seconds after its last move; on a phone a tap opens the panel for 6 seconds and another tap closes it. With reduced motion the weights jump straight to their target and the title appears without spring or parallax.

Values to change

NameFileDefaultWhat it changes
OPEN_RATIOscript.js0.56How much of the row the open panel takes (0.3 to 0.8).
SPRINGscript.js[170, 17]The panels’ spring: how stiff and how damped it is (less damping, more bounce).
STAGGERscript.js0.045The delay between a panel and its neighbour when the row changes, in seconds.
PARALLAXscript.js0.36How much the print follows the widening edge: 0.5 keeps it centred, less makes it slide slower.
TITLE_WIDTHscript.js[72, 112]The title’s width as it arrives and with the panel open (62 to 125).
TITLE_WEIGHTscript.js[300, 640]The title’s weight as it arrives and with the panel open (100 to 900).
STEPscript.js1.6How many seconds the ghost pointer stays on each print.
WANDERscript.jstrueThe ghost pointer that opens the prints one after another when nobody touches them.

Try them in the Playground

When to use it

For a photographer’s or illustrator’s gallery, an art shop’s prints, a brand’s collections, a hotel’s rooms, the sections of a project: a few strong images revealed one at a time without leaving the page.

On phones

The same animations, with the panels stacked: the tapped one opens, growing in height with the same spring and wave, and the print slides vertically. Another tap on the same panel closes it; after 6 seconds the automatic round comes back.

With reduced motion

No springs, no parallax and no automatic round: one panel is already open, and moving over another the row changes at once while the title fades in.

Pitfalls

  • With reduced motion the row stayed all equal: the starting open panel was already written in the state, and the function that opens it discarded it as ‘already open’. Now the state starts empty and the panel really opens.
  • The large title sat on top of the print and could not be read: underneath there is a veil the colour of the panel’s paper, in an ::after that appears with the opening; title, line and name sit above it with z-index, because the pseudo-element is painted after the children.
  • A GSAP scale on the print at opening was wiped out on every frame by the parallax transform: the print has one owner, the loop that draws it.
  • The prints in a 600x800 viewBox left a big gap at the top of the open, almost square panel: the viewBox starts at 140, so the composition sits in the middle.

Libraries

GSAP 3.15.0, Archivo (font variabile) 5.3.0.

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

Testing

Tested on 21 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