Skip to content
animorio
IT
Back to the library

The diagonal gallery

An endless row tilted by eight degrees, with its pictures kept upright, that slides sideways as you scroll the page and can be dragged and thrown with inertia.

Images, ScrollGSAPMediumFree

Scroll inside the preview

The code

A folder that runs on its own: index.html, style.css, script.js and the libraries from a CDN.

<!doctype html>
<html lang="it">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>La galleria in diagonale</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/mona-sans@5.3.0/standard.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <svg class="disegni" aria-hidden="true" focusable="false" width="0" height="0">
    <defs>
      <symbol id="d-girasole" viewBox="0 0 200 200">
        <rect width="200" height="200" fill="#F3E9D2" />
        <g fill="#2F5DA8"><circle cx="0" cy="0" r="46" /><circle cx="200" cy="0" r="46" /><circle cx="0" cy="200" r="46" /><circle cx="200" cy="200" r="46" /></g>
        <g fill="#2F5DA8" transform="translate(100 100)">
          <ellipse rx="12" ry="40" cy="-38" /><ellipse rx="12" ry="40" cy="-38" transform="rotate(45)" /><ellipse rx="12" ry="40" cy="-38" transform="rotate(90)" /><ellipse rx="12" ry="40" cy="-38" transform="rotate(135)" />
          <ellipse rx="12" ry="40" cy="-38" transform="rotate(180)" /><ellipse rx="12" ry="40" cy="-38" transform="rotate(225)" /><ellipse rx="12" ry="40" cy="-38" transform="rotate(270)" /><ellipse rx="12" ry="40" cy="-38" transform="rotate(315)" />
        </g>
        <circle cx="100" cy="100" r="22" fill="#E2A93B" />
      </symbol>
      <symbol id="d-onda" viewBox="0 0 200 200">
        <rect width="200" height="200" fill="#E7EEF3" />
        <g fill="none" stroke-width="14" stroke-linecap="round">
          <path d="M-10 40 Q15 16 40 40 T90 40 T140 40 T190 40 T240 40" stroke="#1E4F8C" />
          <path d="M-10 80 Q15 56 40 80 T90 80 T140 80 T190 80 T240 80" stroke="#3D7CC2" />
          <path d="M-10 120 Q15 96 40 120 T90 120 T140 120 T190 120 T240 120" stroke="#1E4F8C" />
          <path d="M-10 160 Q15 136 40 160 T90 160 T140 160 T190 160 T240 160" stroke="#3D7CC2" />
        </g>
      </symbol>
      <symbol id="d-stella" viewBox="0 0 200 200">
        <rect width="200" height="200" fill="#F6E7D8" />
        <path d="M100 18 L122 78 L182 100 L122 122 L100 182 L78 122 L18 100 L78 78 Z" fill="#B5452B" />
        <path d="M100 42 L141 59 L158 100 L141 141 L100 158 L59 141 L42 100 L59 59 Z" fill="none" stroke="#B5452B" stroke-width="6" />
        <rect x="80" y="80" width="40" height="40" fill="#F2C14E" transform="rotate(45 100 100)" />
      </symbol>
      <symbol id="d-foglia" viewBox="0 0 200 200">
        <rect width="200" height="200" fill="#EAF1E3" />
        <path d="M100 190 C100 140 100 80 100 14" stroke="#2F5C3C" stroke-width="5" fill="none" />
        <g fill="#3F7A4E"><ellipse cx="72" cy="60" rx="30" ry="13" transform="rotate(-30 72 60)" /><ellipse cx="128" cy="96" rx="30" ry="13" transform="rotate(30 128 96)" /><ellipse cx="72" cy="132" rx="30" ry="13" transform="rotate(-30 72 132)" /></g>
        <g fill="#6FA36A"><ellipse cx="128" cy="40" rx="24" ry="10" transform="rotate(30 128 40)" /><ellipse cx="72" cy="96" rx="24" ry="10" transform="rotate(-30 72 96)" /><ellipse cx="128" cy="150" rx="24" ry="10" transform="rotate(30 128 150)" /></g>
      </symbol>
      <symbol id="d-rombo" viewBox="0 0 200 200">
        <rect width="200" height="200" fill="#F4EEE6" />
        <path d="M100 8 L192 100 L100 192 L8 100 Z" fill="#2A3F6B" />
        <path d="M100 38 L162 100 L100 162 L38 100 Z" fill="#E0A43B" />
        <path d="M100 68 L132 100 L100 132 L68 100 Z" fill="#B94A36" />
      </symbol>
      <symbol id="d-nodo" viewBox="0 0 200 200">
        <rect width="200" height="200" fill="#EDE3F0" />
        <g fill="none" stroke="#5B3A7A" stroke-width="10">
          <circle cx="76" cy="76" r="44" /><circle cx="124" cy="76" r="44" /><circle cx="76" cy="124" r="44" /><circle cx="124" cy="124" r="44" />
        </g>
      </symbol>
      <symbol id="freccia" viewBox="0 0 24 24"><path d="M4 12h15M13 6l6 6-6 6" /></symbol>
    </defs>
  </svg>

  <header class="apertura">
    <p class="apertura__sopra">Una bottega di maiolica</p>
    <h1 class="apertura__titolo">Sei decori,<br>in diagonale</h1>
    <p class="apertura__testo">Scorri la pagina: la fila sale di traverso. Puoi anche trascinarla, o usare le frecce.</p>
  </header>

  <!-- la fila: inclinata di -8 gradi, ogni quadro raddrizzato di +8; le copie prima e dopo la fanno infinita -->
  <section class="galleria" data-galleria aria-label="Decori">
    <div class="galleria__inclinato">
      <div class="galleria__binario" data-binario>
        <figure class="vagone">
          <div class="vagone__quadro"><svg viewBox="0 0 200 200" preserveAspectRatio="xMidYMid slice" aria-hidden="true"><use href="#d-girasole" /></svg></div>
          <figcaption><span class="vagone__nome">Girasole</span><span class="vagone__nota">Blu cobalto e giallo zafferano</span></figcaption>
        </figure>
        <figure class="vagone">
          <div class="vagone__quadro"><svg viewBox="0 0 200 200" preserveAspectRatio="xMidYMid slice" aria-hidden="true"><use href="#d-onda" /></svg></div>
          <figcaption><span class="vagone__nome">Onda</span><span class="vagone__nota">Due azzurri che si inseguono</span></figcaption>
        </figure>
        <figure class="vagone">
          <div class="vagone__quadro"><svg viewBox="0 0 200 200" preserveAspectRatio="xMidYMid slice" aria-hidden="true"><use href="#d-stella" /></svg></div>
          <figcaption><span class="vagone__nome">Stella</span><span class="vagone__nota">Rosso mattone su crema</span></figcaption>
        </figure>
        <figure class="vagone">
          <div class="vagone__quadro"><svg viewBox="0 0 200 200" preserveAspectRatio="xMidYMid slice" aria-hidden="true"><use href="#d-foglia" /></svg></div>
          <figcaption><span class="vagone__nome">Foglia</span><span class="vagone__nota">Verde salvia e verde bosco</span></figcaption>
        </figure>
        <figure class="vagone">
          <div class="vagone__quadro"><svg viewBox="0 0 200 200" preserveAspectRatio="xMidYMid slice" aria-hidden="true"><use href="#d-rombo" /></svg></div>
          <figcaption><span class="vagone__nome">Rombo</span><span class="vagone__nota">Blu notte, ocra e ruggine</span></figcaption>
        </figure>
        <figure class="vagone">
          <div class="vagone__quadro"><svg viewBox="0 0 200 200" preserveAspectRatio="xMidYMid slice" aria-hidden="true"><use href="#d-nodo" /></svg></div>
          <figcaption><span class="vagone__nome">Nodo</span><span class="vagone__nota">Viola prugna, a mano libera</span></figcaption>
        </figure>
      </div>
    </div>
    <div class="galleria__frecce">
      <button class="freccia freccia--indietro" type="button" data-indietro aria-label="Decoro precedente"><svg aria-hidden="true"><use href="#freccia" /></svg></button>
      <button class="freccia" type="button" data-avanti aria-label="Decoro successivo"><svg aria-hidden="true"><use href="#freccia" /></svg></button>
    </div>
  </section>

  <section class="chiusura">
    <p>Ogni decoro nasce dipinto a mano su una piastrella cruda, poi va in forno una seconda volta. Nessuno è uguale al vicino.</p>
  </section>

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

What it does

A row of large pictures crosses the screen diagonally, rising to the right, but each picture stays upright. As you scroll the page the row moves sideways, one pixel for each pixel of page; grab it with the mouse or a finger and it moves faster than your gesture, and when you let go it slides a little further and brakes. The arrows move it one picture at a time. The row never ends: after the last pattern the first one comes back.

How it works

Tilted, but upright

The row's container rotates by -8 degrees and each picture by +8: the track runs along the diagonal, the images stay standing. The offset is applied to the track inside the rotated container, so it moves along the diagonal and not horizontally.

Endless

The originals sit between two sets of copies. The position goes through gsap.utils.wrap(-loop/2, loop/2), where the loop is the width of one set: when it leaves on one side it comes back on the other, and with identical copies the jump cannot be seen.

One single position

Page scrolling, dragging, throwing, arrows and drift all change the same target; every frame the real position follows it with a frame-rate independent brake (0.08 while dragging, 0.12 after).

The throw

While dragging, the last five moves are kept; on release their average speed times 1200 becomes a momentum added to the target, losing 6% every sixtieth of a second.

Values to change

NameFileDefaultWhat it changes
ACCOPPIATAscript.js1Pixels sideways for each pixel of page scrolled. With 0 the row only moves when dragged.
TRASCINAscript.js2.5How far the row runs compared to the finger.
LANCIOscript.js1200Strength of the throw when you let go.
DERIVAscript.js18Pixels per second of movement on its own when nobody touches it. With 0 it stays still.
--inclinazionestyle.css-8degHow steeply the row rises; -4 degrees on phones.

When to use it

To show a collection of large images (work, products, rooms) in a section you cross while scrolling, without pinning the page.

On phones

The pictures become almost as wide as the screen and the tilt drops to four degrees. A vertical finger scrolls the page, a horizontal one drags the row.

With reduced motion

The row stays tilted but still: no drift, no link to scrolling and no inertia. It drags one to one and the arrows jump to the next picture.

Pitfalls

  • Apply the offset to the track inside the rotated container, not to the container: otherwise the row moves horizontally and not along the diagonal.
  • Decide the axis after a few pixels (touch-action: pan-y on the section): vertically the finger must stay with the page.
  • The copies must be identical and the loop must be the exact width of one set (picture plus gap, times the number of pictures), or the wrap jumps.
  • Hide the copies from screen readers (aria-hidden) or they will read every pattern three times.

Libraries

GSAP 3.15.0, Mona Sans (font variabile) 5.3.0.

Weight: 16 KB of our own files.

Testing

Tested on 27 September 2026

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

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