Skip to content
animorio
IT
Back to the library

Squared paper under the photo

Hover, WebGL, ImagesWebGL, Canvas, GSAPAdvancedCompressed weight: Demo 8.9 KBGSAP 28 KBFreeDownload the zip

Three pictures in WebGL: where the pointer passes the photo opens in a hole with a trembling edge, underneath there is white squared paper, and around it the photo ripples like water.

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 squared sheet</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>
  <main class="pagina">
    <!-- tre quadri: ognuno ha la sua immagine, disegnata dallo script, e un piccolo WebGL che la mostra -->
    <div class="galleria">
      <div class="testa">
        <p class="nota" data-nota>Move the mouse over the pictures: wherever you pass, the photo opens, and underneath there’s graph paper.</p>
        <h1 class="titolo">Under every photo there’s graph paper.</h1>
      </div>
      <figure class="quadro quadro--campo" data-quadro="campo">
        <div class="quadro__tela" data-tela></div>
        <figcaption class="quadro__dida">The field, at the end of June</figcaption>
      </figure>
      <figure class="quadro quadro--finestra" data-quadro="finestra">
        <div class="quadro__tela" data-tela></div>
        <figcaption class="quadro__dida">The kitchen window</figcaption>
      </figure>
      <figure class="quadro quadro--brocca" data-quadro="brocca">
        <div class="quadro__tela" data-tela></div>
        <figcaption class="quadro__dida">The jug and two pears</figcaption>
      </figure>
    </div>
  </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.

18 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 “Squared paper under the photo” from Animorio and fit it into my site

What it does

A small gallery with three sepia pictures: a ploughed field at sunset, a kitchen window, a jug with two pears. They are drawn by the script on a canvas and shown by a small WebGL each. While nobody touches them they are just photos. Passing over them, under the pointer the photo opens in a hole with a trembling edge, and underneath there is white squared paper, with whole squares right up to the edges. The movement leaves a trail of holes that swell, merge and close up again in a second and a half; around each one the photo ripples, like water touched with a finger. With a finger, a tap opens a hole and dragging sideways draws.

How it works

A field of holes

The shader receives sixteen points: the first is the pointer, the others the trail, each with its position, the time it was born and its radius. In every pixel it adds r² over d² for all of them and cuts the sum at 1: where two holes come close they merge like drops. Trail holes swell in a quarter of a second and close with the square of the time left.

The trembling edge

Before measuring, the pixel’s point is warped with two turns of noise that flow over time, at different scales: so the hole’s edge is never a circle and its shape never repeats. The cut is narrow, between 0.96 and 1.04, so it reads as a cut and not as a fade.

Whole squares

The sheet counts how many 24-pixel squares fit in the frame, rounds, and divides the size by that number, for rows and for columns: the squares come out a hair off square, but none is cut. The two far edges are drawn too, and a thread of shadow just inside the cut puts the sheet under the photo.

Water on the photo

Each trail hole sends out a wave: a sine inside a bell that runs outward at 260 pixels per second and dies away over time. The wave moves the point where the photo is read along the radius, that is outward from the touch. The sheet does not bend: the water is on the photo.

Values to change

NameFileDefaultWhat it changes
CELLAscript.js24The side of a square in px; the sheet adjusts it to keep only whole squares.
TESTA, GOCCIAscript.js70, 84The radius of the hole under the pointer and of the trail holes.
VITAscript.js1.6How many seconds a trail hole takes to close.
TREMA, ONDAscript.js34, 16How many px the edge trembles and how many the water moves the photo.
DISEGNIscript.jscampo, finestra, broccaThe three pictures, drawn on the canvas; any photo can be loaded in their place.

Try them in the Playground

When to use it

For the photos of a portfolio, a menu or a gallery that must stay photos until someone plays with them: the sheet underneath says there is a plan behind every picture.

On phones

The photos stack. A tap opens a hole that closes by itself; dragging sideways draws, vertically the page scrolls as usual.

With reduced motion

No trail, no water and no trembling edge: under the pointer there is only the hole, which appears and disappears with a short fade.

Pitfalls

  • Sixteen holes, two turns of noise and a wave for every pixel are costly: when nobody touches the photo the shader does a single read and stops, and frames off screen are not drawn.
  • The canvas uploads into the texture upside down: without UNPACK_FLIP_Y the photo comes out upside down.
  • A non-power-of-two texture, in WebGL 1, needs CLAMP_TO_EDGE and no mipmaps, or it stays black.
  • The previews’ simulated pointer sends its events to the page: the frame under the pointer is found with closest() from a listener on the window, not with pointerenter.

Libraries

GSAP 3.15.0, Mona Sans (font variabile) 5.3.0.

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

Testing

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