Vai al contenuto
animorio
EN
Tutti i componenti

Il modulo contatti del messaggio in bottiglia

Moduli, SVG, HoverGSAP, Solo CSSImpegnativaPeso compresso: Demo 15 KBGSAP 28 KBGratisScarica lo zip

Nello stile ricco, colorato e pieno di cose: il modulo contatti di una locanda sul mare, The Driftwood Inn. A sinistra un angolo di spiaggia al tramonto, col sole che scende, le onde, una barca a vela all’orizzonte e la riva che va e viene; sulla sabbia una bottiglia vuota, il suo tappo di sughero, un rotolo di spago e un foglio a righe. Mentre scrivi la lettera si riempie d’inchiostro: le parole del messaggio diventano righe di calligrafia col pennino che corre in fondo, l’email diventa il mittente col timbro del dollaro della sabbia, il nome la firma con lo svolazzo. Inviato, il foglio si arrotola, si lega con lo spago e scivola nella bottiglia, il tappo vola nel collo, la bottiglia si rovescia, un’onda risale la spiaggia, la prende e la porta al largo, verso il sole, finché diventa un riflesso.

Il codice

Una cartella che funziona da sola: index.html, style.css, script.js e le librerie da CDN. Nelle schede React e Vue la stessa demo è già un componente, che spegne le sue animazioni quando esce dalla pagina.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>The message in a bottle contact form</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/bricolage-grotesque@5.3.0/index.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/caveat@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the contact form of a seaside inn: beside it a corner of the beach at sunset, with an empty bottle standing in
         the sand, its cork and a sheet of paper. While you write, the letter fills with ink: the message line by line
         with a nib running at the end, the email as the return address with a sand dollar stamp, the name as the
         signature. Sent, the letter rolls up, is tied with twine and slides into the bottle, the cork flies in, the
         bottle tips over, rolls to the water and drifts away on the waves to the sun -->
    <section class="mare" aria-labelledby="mare-title" data-mare data-autoclic>
      <div class="mare__scene" aria-hidden="true">
        <svg class="scene" viewBox="0 0 300 360" data-svg>
          <defs>
            <linearGradient id="mare-sky" x1="0" y1="0" x2="0" y2="1">
              <stop offset="0" class="stop-sky-top"/>
              <stop offset="1" class="stop-sky"/>
            </linearGradient>
            <linearGradient id="mare-sea" x1="0" y1="0" x2="0" y2="1">
              <stop offset="0" class="stop-sea-far"/>
              <stop offset="1" class="stop-sea"/>
            </linearGradient>
            <linearGradient id="mare-sand" x1="0" y1="0" x2="0" y2="1">
              <stop offset="0" class="stop-sand-wet"/>
              <stop offset="0.16" class="stop-sand"/>
              <stop offset="1" class="stop-sand-low"/>
            </linearGradient>
            <linearGradient id="mare-glass" x1="0" y1="0" x2="1" y2="0">
              <stop offset="0" class="stop-glass-dark"/>
              <stop offset="0.3" class="stop-glass-light"/>
              <stop offset="0.55" class="stop-glass"/>
              <stop offset="1" class="stop-glass-dark"/>
            </linearGradient>
            <radialGradient id="mare-sun" cx="0.5" cy="0.5" r="0.5">
              <stop offset="0" class="stop-sun"/>
              <stop offset="0.45" class="stop-sun"/>
              <stop offset="1" class="stop-sun-glow"/>
            </radialGradient>
            <linearGradient id="mare-wood" x1="0" y1="0" x2="0" y2="1">
              <stop offset="0" class="stop-wood-light"/>
              <stop offset="1" class="stop-wood"/>
            </linearGradient>
            <clipPath id="mare-room">
              <rect x="0" y="0" width="300" height="360" rx="18"/>
            </clipPath>
            <clipPath id="mare-above">
              <rect x="0" y="0" width="300" height="170"/>
            </clipPath>
          </defs>

          <g clip-path="url(#mare-room)">
            <!-- the sky at sunset, two clouds drifting -->
            <rect x="0" y="0" width="300" height="172" fill="url(#mare-sky)"/>
            <g class="clouds" data-clouds>
              <g data-cloud="0" transform="translate(40 52)">
                <path class="cloud" d="M0 14 C 0 6, 9 2, 16 6 C 20 -2, 34 -3, 39 5 C 46 2, 56 7, 54 14 Z"/>
                <path class="cloud__light" d="M8 10 C 14 7, 20 7, 26 9"/>
              </g>
              <g data-cloud="1" transform="translate(196 34)">
                <path class="cloud" d="M0 11 C 1 5, 8 2, 13 5 C 17 -1, 28 -1, 31 5 C 37 3, 44 7, 42 11 Z"/>
              </g>
            </g>
            <!-- the sun going down behind the horizon -->
            <g clip-path="url(#mare-above)">
              <circle cx="220" cy="160" r="46" fill="url(#mare-sun)" class="sun__glow"/>
              <circle cx="220" cy="160" r="20" class="sun"/>
            </g>
            <!-- the sea: a band that darkens towards the beach, a sailing boat on the horizon, the glints under the sun -->
            <rect x="0" y="168" width="300" height="96" fill="url(#mare-sea)"/>
            <path d="M0 168.5 H300" class="horizon"/>
            <g class="boat" data-boat transform="translate(56 166)">
              <path d="M-7 0 H7 L5 2.6 H-5 Z" class="boat__hull"/>
              <path d="M0 -0.6 V-15" class="boat__mast"/>
              <path d="M0.8 -14 L8 -2 H0.8 Z" class="boat__sail"/>
              <path d="M-0.8 -11 L-6 -2 H-0.8 Z" class="boat__jib"/>
            </g>
            <g class="glints" data-glints></g>
            <g class="waves" data-waves></g>
            <!-- the bottle out at sea, drawn again small while it drifts -->
            <g data-floater opacity="0">
              <g data-floater-tilt>
                <path class="bottle__body" d="M-22 -9 C -22 -12, -19 -13, -16 -13 H6 C 10 -13, 12 -10, 14 -6 H22 V6 H14 C 12 10, 10 13, 6 13 H-16 C -19 13, -22 12, -22 9 Z"/>
                <rect x="22" y="-4.5" width="7" height="9" rx="2" class="cork"/>
                <rect x="-16" y="-4" width="26" height="8" rx="4" class="roll__paper"/>
                <path d="M-18 -9 H8" class="bottle__shine"/>
              </g>
              <path d="M-26 14 C -16 10, 16 10, 28 14" class="floater__ripple"/>
            </g>

            <!-- the beach: the water's edge that washes up and back, wet sand, then the dry sand -->
            <rect x="0" y="240" width="300" height="120" fill="url(#mare-sand)"/>
            <path class="wash" data-wash d="M0 248 C 60 252, 120 246, 180 250 C 230 253, 270 247, 300 250 V 236 H 0 Z"/>
            <path class="foam" data-foam d="M0 248 C 60 252, 120 246, 180 250 C 230 253, 270 247, 300 250"/>
            <g class="grains" data-grains></g>

            <!-- things on the sand: a piece of driftwood, two shells, pebbles, a coil of twine -->
            <g class="drift" transform="translate(196 342) rotate(-6)">
              <path d="M-70 -7 C -50 -12, 10 -11, 52 -8 C 60 -7, 64 -3, 62 2 C 40 6, -30 7, -66 5 C -74 4, -76 -4, -70 -7 Z" fill="url(#mare-wood)"/>
              <path d="M-60 -4 C -30 -6, 10 -6, 48 -4 M-50 1 C -20 0, 20 0, 44 2 M-8 -8 C -2 -6, 4 -6, 8 -8" class="drift__grain"/>
              <ellipse cx="22" cy="-2" rx="4" ry="2.2" class="drift__knot"/>
            </g>
            <g class="shell" transform="translate(26 318) rotate(-14)">
              <path d="M0 0 C -9 -2, -13 -11, -9 -17 C -5 -22, 5 -22, 9 -17 C 13 -11, 9 -2, 0 0 Z" class="shell__body"/>
              <path d="M0 -1 L-7 -16 M0 -1 L-3 -19 M0 -1 L1 -20 M0 -1 L5 -18 M0 -1 L8 -14" class="shell__rib"/>
              <path d="M-4 0 H4 L3 3 H-3 Z" class="shell__hinge"/>
            </g>
            <g class="shell shell--small" transform="translate(278 300) rotate(22) scale(0.7)">
              <path d="M0 0 C -9 -2, -13 -11, -9 -17 C -5 -22, 5 -22, 9 -17 C 13 -11, 9 -2, 0 0 Z" class="shell__body"/>
              <path d="M0 -1 L-7 -16 M0 -1 L-3 -19 M0 -1 L1 -20 M0 -1 L5 -18 M0 -1 L8 -14" class="shell__rib"/>
            </g>
            <ellipse cx="128" cy="344" rx="7" ry="4" class="pebble"/>
            <ellipse cx="139" cy="347" rx="4" ry="2.6" class="pebble pebble--light"/>
            <ellipse cx="56" cy="352" rx="5" ry="3" class="pebble pebble--light"/>

            <!-- the bottle on the sand: shadow, the glass behind, the letter when it goes in, the glass in front -->
            <g data-bottle-shadow>
              <ellipse cx="78" cy="309" rx="22" ry="5" class="bottle__shadow"/>
            </g>
            <g data-bottle>
              <g data-bottle-turn>
                <path class="bottle__back" d="M64 308 C 60 308, 58 305, 58 301 V 256 C 58 246, 66 240, 72 236 V 222 H 84 V 236 C 90 240, 98 246, 98 256 V 301 C 98 305, 96 308, 92 308 Z"/>
                <g data-inside></g>
                <path class="bottle__body" d="M64 308 C 60 308, 58 305, 58 301 V 256 C 58 246, 66 240, 72 236 V 222 H 84 V 236 C 90 240, 98 246, 98 256 V 301 C 98 305, 96 308, 92 308 Z"/>
                <path d="M71 222 H85 V 219 C 85 217.5, 84 217, 82.5 217 H 73.5 C 72 217, 71 217.5, 71 219 Z" class="bottle__lip"/>
                <path d="M63 258 C 63 250, 67 246, 72 242" class="bottle__shine"/>
                <path d="M63 266 V 298" class="bottle__shine"/>
                <path d="M93 270 V 296" class="bottle__shine bottle__shine--thin"/>
                <path d="M58 300 C 66 304, 90 304, 98 300" class="bottle__base"/>
                <!-- a scrap of an old label -->
                <path d="M64 270 L 92 268 L 91 284 L 66 285 Z" class="bottle__label"/>
                <path d="M68 275 H 86 M 69 279 H 82" class="bottle__label-line"/>
                <g data-cork-in opacity="0">
                  <path d="M72 223 C 72 213, 73 210, 74 209 H 82 C 83 210, 84 213, 84 223 Z" class="cork"/>
                  <path d="M74 213 H82 M73.5 217 H82.5" class="cork__dots"/>
                </g>
              </g>
            </g>

            <!-- the cork and the twine lying on the sand -->
            <g data-cork transform="translate(118 312) rotate(70)">
              <path d="M-5 -8 L 5 -8 L 6 8 L -6 8 Z" class="cork"/>
              <ellipse cx="0" cy="-8" rx="5" ry="1.8" class="cork__top"/>
              <circle cx="-1.5" cy="-2" r="0.7" class="cork__dot"/>
              <circle cx="2" cy="2" r="0.6" class="cork__dot"/>
              <circle cx="-2" cy="4.5" r="0.6" class="cork__dot"/>
            </g>
            <g class="twine" transform="translate(110 330)">
              <ellipse cx="0" cy="0" rx="9" ry="3.4" class="twine__coil"/>
              <ellipse cx="0" cy="-1.2" rx="6" ry="2.2" class="twine__coil"/>
              <path d="M8 1 C 14 3, 18 1, 22 4" class="twine__end"/>
            </g>

            <!-- the letter on a flat stone: ruled paper, the return address, the stamp, the ink lines, the signature -->
            <g data-letter-shadow>
              <path d="M146 268 L 282 262 L 286 342 L 150 348 Z" class="letter__shadow"/>
            </g>
            <g data-letter transform="translate(214 302) rotate(-3)">
              <g data-paper>
                <path d="M-66 -40 L 66 -40 L 66 40 C 40 42, -40 42, -66 40 Z" class="paper"/>
                <path d="M-66 -40 L 66 -40 L 66 -34 L -66 -34 Z" class="paper__top"/>
                <g class="paper__rules">
                  <path d="M-58 -16 H58 M-58 -6 H58 M-58 4 H58 M-58 14 H58 M-58 24 H58"/>
                </g>
                <path d="M-52 -40 V 40" class="paper__margin"/>
                <text x="-47" y="-25" class="paper__from" data-from></text>
                <g data-stamp transform="translate(52 -24)" opacity="0">
                  <circle r="9" class="stamp"/>
                  <g class="stamp__petals">
                    <ellipse cx="0" cy="-4" rx="1.4" ry="3"/>
                    <ellipse cx="0" cy="-4" rx="1.4" ry="3" transform="rotate(72)"/>
                    <ellipse cx="0" cy="-4" rx="1.4" ry="3" transform="rotate(144)"/>
                    <ellipse cx="0" cy="-4" rx="1.4" ry="3" transform="rotate(216)"/>
                    <ellipse cx="0" cy="-4" rx="1.4" ry="3" transform="rotate(288)"/>
                  </g>
                  <circle r="11.5" class="stamp__ring"/>
                </g>
                <g data-lines></g>
                <g data-blots></g>
                <text x="56" y="35" class="paper__sign" text-anchor="end" data-sign></text>
                <path d="M10 37 C 24 40, 40 38, 58 36" class="paper__flourish" data-flourish/>
                <g data-nib opacity="0">
                  <path d="M0 0 L 4 -11 L 7 -10 Z" class="nib"/>
                  <path d="M4 -11 L 14 -34" class="nib__quill"/>
                  <path d="M14 -34 C 8 -30, 6 -22, 5 -14 M14 -34 C 18 -28, 16 -20, 8 -12" class="nib__vane"/>
                </g>
              </g>
              <!-- the roll the letter becomes -->
              <g data-roll opacity="0">
                <rect x="-6" y="-40" width="12" height="80" rx="6" class="roll__paper"/>
                <ellipse cx="0" cy="-40" rx="6" ry="2.4" class="roll__end"/>
                <path d="M0 -40 m -3 0 a 3 1.2 0 1 0 6 0 a 1.6 0.6 0 1 0 -3 0" class="roll__spiral"/>
                <g data-tie opacity="0">
                  <path d="M-6.4 -3 H6.4 M-6.4 1 H6.4" class="tie"/>
                  <path d="M5 -1 C 12 -6, 14 2, 18 -2 M5 -1 C 11 4, 14 8, 19 6" class="tie"/>
                </g>
              </g>
            </g>

            <rect x="0.5" y="0.5" width="299" height="359" rx="17.5" class="scene__edge"/>
          </g>
        </svg>
      </div>

      <div class="mare__side">
        <form class="mare__form" novalidate data-form>
          <p class="mare__kicker">The Driftwood Inn</p>
          <h1 class="mare__title" id="mare-title">Write to us by sea</h1>

          <div class="field" data-field="name">
            <label class="field__label" for="mare-name">Your name</label>
            <input class="field__input" id="mare-name" name="name" type="text" autocomplete="name" aria-describedby="mare-err-name" data-input>
            <span class="field__err" id="mare-err-name" data-err></span>
          </div>

          <div class="field" data-field="email">
            <label class="field__label" for="mare-email">Email</label>
            <input class="field__input" id="mare-email" name="email" type="email" autocomplete="email" inputmode="email" aria-describedby="mare-err-email" data-input>
            <span class="field__err" id="mare-err-email" data-err></span>
          </div>

          <div class="field" data-field="message">
            <label class="field__label" for="mare-message">Message</label>
            <textarea class="field__input" id="mare-message" name="message" rows="2" aria-describedby="mare-err-message" data-input></textarea>
            <span class="field__err" id="mare-err-message" data-err></span>
          </div>

          <div class="mare__foot">
            <button class="send" type="submit" data-send>
              <svg class="send__wave" viewBox="0 0 22 14" aria-hidden="true">
                <path d="M1 9 C 4 5, 7 5, 9 8 C 11 11, 14 11, 16 8 C 18 5, 20 5, 21 6"/>
                <path d="M1 13 C 4 10, 7 10, 9 12 C 11 14, 14 14, 16 12" class="send__wave-low"/>
              </svg>
              <span class="send__word" data-word>Send by sea</span>
            </button>
            <span class="mare__note">We answer within a day.</span>
          </div>
        </form>

        <!-- what stays once the bottle is at sea -->
        <div class="done" data-done>
          <p class="done__title">Your bottle is at sea</p>
          <p class="done__text">It will reach the inn with the next tide. We write back to <span data-done-mail></span>.</p>
          <button class="again" type="button" data-again>Write another</button>
        </div>
      </div>
      <p class="sr" aria-live="polite" data-live></p>
    </section>
  </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

Cambia valori, colori e testi con le manopole e guarda l’animazione rispondere dal vivo. Oppure scrivi tu nel codice. Poi scarica la cartella con le tue modifiche.

37 valori da cambiare

Oppure chiedila a Claude Code

Col connettore di Animorio, Claude Code la prende da qui con tutta la sua guida e la adatta al tuo progetto. Incolla questa richiesta:

Prendi «Il modulo contatti del messaggio in bottiglia» da Animorio e adattala al mio sito

Cosa fa

Un modulo contatti vero, con nome, email e messaggio, e accanto una spiaggia che lo racconta. Ogni campo scrive sulla lettera sulla sabbia: il messaggio parola per parola in calligrafia, col pennino che corre in fondo e sparisce quando smetti; l’email in alto come mittente, e quando è giusta arriva il timbro del dollaro della sabbia; il nome in fondo come firma, con lo svolazzo che si disegna quando basta. Un campo lasciato sbagliato fa cadere una macchia d’inchiostro, scuote il campo e fa frusciare il foglio. Con «Send by sea» il foglio si arrotola da destra, viene legato con lo spago, vola sopra la bottiglia e ci scivola dentro; il tappo fa una capriola e si infila nel collo, la bottiglia si rovescia sulla sabbia, un’onda risale la spiaggia, la solleva e se la porta via, e la bottiglia va al largo dondolando, sempre più piccola, verso il sole.

Come funziona

La spiaggia in SVG, il mare ridisegnato

La spiaggia è un SVG di 300 per 360: cielo, sole, mare e sabbia sono fissi, mentre le onde, i riflessi del sole e la riva si ridisegnano a ogni fotogramma dal ticker di GSAP con qualche seno.

Ogni parola, una riga di calligrafia

Ogni parola del messaggio diventa un tratto di calligrafia: una curva a zig-zag con un ricciolo in su per le lettere alte (b, d, l, t…) e in giù per quelle basse (g, p, y…), che va a capo quando la riga è piena; il pennino si mette in fondo all’ultima parola.

I pezzi che si muovono scritti a mano

Le parti che si muovono (lettera, rotolo, bottiglia, tappo, bottiglia al largo) hanno un oggetto di numeri ciascuna: GSAP li anima e il ticker li scrive nell’attributo transform, così ruotano attorno al punto giusto anche dentro gruppi già spostati.

Il foglio che si arrotola e scende nella bottiglia

Per arrotolarsi il foglio si stringe verso il bordo sinistro (scaleX attorno a quel bordo) mentre il rotolo corre da destra a sinistra; poi un rotolo gemello, disegnato dietro il vetro, scende nella bottiglia.

L’onda che porta via la bottiglia

L’onda è la stessa riva che sale: un numero aggiunto all’altezza del bordo dell’acqua la fa risalire fino alla bottiglia e ritirarsi portandosela via.

Valori da cambiare

NomeFileDi partenzaCosa cambia
ROLL_TIMEscript.js0.9Quanti secondi ci mette la lettera ad arrotolarsi.
DRIFT_TIMEscript.js4.2Quanti secondi ci mette la bottiglia ad arrivare al sole.
WAVE_HEIGHTscript.js1Quanto sono alte le onde sul mare: 0 è la calma piatta.
SURFscript.js3Di quanti pixel la riva va e viene sulla sabbia.
INK_WIDTHscript.js2.5Quanto è larga una lettera della calligrafia sul foglio.
WAITscript.js0.4Quanti secondi dura l’invio finto prima che la lettera si arrotoli.
TYPE_SPEEDscript.js0.07Secondi per lettera quando l’anteprima scrive da sola.

Provali nel Playground

Quando usarla

La pagina Contatti di un albergo o di una casa vacanze sul mare, di uno stabilimento balneare, di una scuola di vela, di un bar sulla spiaggia: dove scrivere deve sembrare un piccolo gesto da vacanza.

Sul telefono

Sul telefono la spiaggia e il modulo restano affiancati, la spiaggia più stretta; gli errori vanno sotto il campo e la riga «We answer within a day» sparisce per fare posto.

Con il movimento ridotto

Col movimento ridotto il mare sta fermo, l’inchiostro, il timbro e la firma compaiono senza molle; inviato, la lettera e la bottiglia sulla sabbia si dissolvono e la bottiglia compare già al largo.

Trappole

  • Le trasformazioni di GSAP su gruppi SVG annidati girano attorno all’angolo del loro riquadro: la lettera e il tappo scivolavano via. Qui ogni pezzo ha i suoi numeri e il transform si scrive a mano, con la rotazione attorno al punto giusto (rotate(r 98 308) per il piede della bottiglia).
  • Il rotolo non può passare dietro al vetro se resta nel gruppo della lettera: c’è un rotolo gemello dentro la bottiglia, fra il vetro di dietro e quello davanti, che prende il posto dell’altro sopra il collo.
  • Gli stop delle sfumature con stop-color="var(--…)" come attributo restano neri: gli stop hanno una classe e il colore sta nel CSS.
  • L’anteprima clicca la scheda ogni pochi secondi: lo scritto finto parte solo se non ce n’è già uno in corso, se no ricomincerebbe a metà.

Librerie

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

Compressi con gzip, i tre file della demo pesano 15 KB; le librerie: GSAP 28 KB.

Collaudo

Provata il 23 ottobre 2026

  • Computer: Fatto
  • Telefono: Fatto
  • Movimento ridotto: Fatto
  • Safari su iPhone: Da fare

Licenza d’uso

Puoi usarla nei tuoi progetti e in quelli dei tuoi clienti. Non puoi rivenderla né ridistribuirla così com’è. GSAP resta sotto la sua licenza Standard. Licenza completa.

Tutti i componenti