Vai al contenuto
animorio
EN
Torna alla libreria

La transizione dalla serratura

Transizioni, SVGGSAP, Solo CSSImpegnativaPeso compresso: Demo 16 KBGSAP 28 KBGratisScarica lo zip

Una porta d'epoca in un corridoio di notte, con la serratura d'ottone e la chiave appesa al suo gancio. Al clic la chiave entra nella toppa e gira con due scatti, la luce della stanza passa dal buco e la vista ci si tuffa dentro: il buco della serratura si allarga fino a riempire lo schermo e mostra la stanza dietro. Il clic dopo esce dal buco di un'altra porta.

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, viewport-fit=cover">
  <title>Through the keyhole</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/full.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/instrument-sans@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage" data-stage>

    <!-- the corridor: the wall, the floor and a period door with its brass lock and the key on its hook.
         The three doors of the walk share this drawing: data-door picks the colours and the panels -->
    <div class="hall" data-hall>
      <svg class="door" viewBox="0 0 2400 800" preserveAspectRatio="xMidYMid slice" data-door="0" data-door-svg aria-hidden="true">
        <defs>
          <pattern id="paper" width="60" height="60" patternUnits="userSpaceOnUse">
            <rect class="wp-base" width="60" height="60"/>
            <rect class="wp-line" x="0" width="2" height="60"/>
            <path class="wp-motif" d="M30 22l6 8-6 8-6-8z"/>
          </pattern>
          <linearGradient id="brass" x1="0" y1="0" x2="1" y2="1">
            <stop offset="0" stop-color="#f3d38a"/>
            <stop offset="0.45" stop-color="#c79443"/>
            <stop offset="1" stop-color="#7e5622"/>
          </linearGradient>
          <radialGradient id="knob" cx="0.38" cy="0.34" r="0.7">
            <stop offset="0" stop-color="#fff0c2"/>
            <stop offset="0.35" stop-color="#d9a957"/>
            <stop offset="1" stop-color="#6e4a1c"/>
          </radialGradient>
          <radialGradient id="warm" cx="0.5" cy="0.5" r="0.5">
            <stop offset="0" class="warm-a"/>
            <stop offset="1" class="warm-b"/>
          </radialGradient>
          <radialGradient id="vignette" cx="0.5" cy="0.52" r="0.62">
            <stop offset="0.45" stop-color="#000" stop-opacity="0"/>
            <stop offset="1" stop-color="#000" stop-opacity="0.62"/>
          </radialGradient>
          <clipPath id="plaqueClip">
            <rect x="1142" y="173" width="116" height="25" rx="3"/>
          </clipPath>
          <linearGradient id="floorShade" x1="0" y1="0" x2="0" y2="1">
            <stop offset="0" stop-color="#000" stop-opacity="0.45"/>
            <stop offset="1" stop-color="#000" stop-opacity="0"/>
          </linearGradient>
        </defs>

        <rect class="wall" width="2400" height="700" fill="url(#paper)"/>

        <!-- the two wall lights of the corridor -->
        <g class="sconce">
          <ellipse cx="880" cy="300" rx="190" ry="190" fill="url(#warm)" class="sconce__glow"/>
          <ellipse cx="1520" cy="300" rx="190" ry="190" fill="url(#warm)" class="sconce__glow"/>
          <path class="sconce__arm" d="M880 352v-30M868 352h24M1520 352v-30M1508 352h24"/>
          <path class="sconce__shade" d="M858 322l8-46h28l8 46zM1498 322l8-46h28l8 46z"/>
        </g>

        <rect class="skirting" y="664" width="2400" height="36"/>
        <rect class="floor" y="700" width="2400" height="100"/>
        <rect y="700" width="2400" height="60" fill="url(#floorShade)"/>
        <path class="plank" d="M0 724H2400M0 752H2400M0 784H2400"/>

        <!-- the light of the room, under the door -->
        <ellipse cx="1200" cy="712" rx="190" ry="22" fill="url(#warm)" class="underglow"/>

        <!-- the frame round the door, and the crowns of the second and third doors -->
        <g class="v1">
          <path class="frame" d="M1036 140C1036 50 1364 50 1364 140z"/>
          <path class="fan" d="M1064 140C1064 76 1336 76 1336 140z"/>
          <path class="fan__bars" d="M1200 140V82M1200 140L1118 92M1200 140L1282 92M1200 140L1080 116M1200 140L1320 116"/>
        </g>
        <g class="v2">
          <path class="frame" d="M1012 136L1200 70 1388 136z"/>
          <path class="frame-lo" d="M1050 130L1200 84 1350 130z"/>
        </g>
        <rect class="door-shadow" x="1028" y="132" width="344" height="574"/>
        <rect class="frame" x="1036" y="136" width="328" height="564"/>
        <path class="frame-lo" d="M1048 700V148H1352V700"/>

        <!-- the door leaf -->
        <g data-leaf class="leaf-hit">
          <rect class="leaf" x="1070" y="170" width="260" height="530"/>
          <path class="leaf-edge" d="M1070 700V170H1330"/>

          <!-- four panels: the hotel -->
          <g class="v0">
            <rect class="panel" x="1106" y="204" width="80" height="200"/>
            <rect class="panel" x="1214" y="204" width="80" height="200"/>
            <rect class="panel" x="1106" y="476" width="80" height="190"/>
            <rect class="panel" x="1214" y="476" width="80" height="190"/>
            <rect class="panel-in" x="1118" y="216" width="56" height="176"/>
            <rect class="panel-in" x="1226" y="216" width="56" height="176"/>
            <rect class="panel-in" x="1118" y="488" width="56" height="166"/>
            <rect class="panel-in" x="1226" y="488" width="56" height="166"/>
          </g>
          <!-- two long panels with arched heads: the gallery -->
          <g class="v1">
            <path class="panel" d="M1106 404V262C1106 210 1294 210 1294 262V404z"/>
            <path class="panel-in" d="M1120 390V266C1120 226 1280 226 1280 266V390z"/>
            <rect class="panel" x="1106" y="476" width="188" height="190"/>
            <rect class="panel-in" x="1120" y="490" width="160" height="162"/>
          </g>
          <!-- six panels: the winter garden -->
          <g class="v2">
            <rect class="panel" x="1106" y="204" width="80" height="64"/>
            <rect class="panel" x="1214" y="204" width="80" height="64"/>
            <rect class="panel" x="1106" y="284" width="80" height="120"/>
            <rect class="panel" x="1214" y="284" width="80" height="120"/>
            <rect class="panel" x="1106" y="476" width="80" height="190"/>
            <rect class="panel" x="1214" y="476" width="80" height="190"/>
            <rect class="panel-in" x="1116" y="214" width="60" height="44"/>
            <rect class="panel-in" x="1224" y="214" width="60" height="44"/>
            <rect class="panel-in" x="1116" y="294" width="60" height="100"/>
            <rect class="panel-in" x="1224" y="294" width="60" height="100"/>
            <rect class="panel-in" x="1116" y="486" width="60" height="170"/>
            <rect class="panel-in" x="1224" y="486" width="60" height="170"/>
          </g>

          <!-- the brass plate with the room's name -->
          <g class="plaque" data-plaque>
            <rect x="1142" y="173" width="116" height="25" rx="3" fill="url(#brass)"/>
            <rect class="plaque__line" x="1145.5" y="176.5" width="109" height="18" rx="1.5"/>
            <text class="plaque__text" x="1200" y="189.6" text-anchor="middle" data-plaque-text>Blue Hour Suite</text>
            <g clip-path="url(#plaqueClip)"><path class="plaque__glint" d="M1130 173h10l-8 25h-10z" data-glint/></g>
          </g>

          <!-- the light of the room on the door, round the keyhole, once the lock gives -->
          <ellipse cx="1310" cy="470" rx="60" ry="60" fill="url(#warm)" class="spill" data-spill/>

          <!-- the little hook the key hangs from -->
          <g class="hook">
            <circle cx="1240" cy="440" r="4.6" fill="url(#knob)"/>
            <path d="M1240 441v8a5.2 5.2 0 0 0 10.4 0v-3"/>
            <circle cx="1245.2" cy="454" r="0.1" data-hook/>
          </g>

          <!-- the lock: the long backplate, the knob and the keyhole -->
          <g class="lock" data-lock>
            <rect x="1296" y="374" width="28" height="134" rx="14" fill="url(#brass)"/>
            <rect class="lock__line" x="1299.5" y="377.5" width="21" height="127" rx="10.5"/>
            <circle cx="1310" cy="396" r="14" fill="url(#knob)"/>
            <ellipse class="lock__shine" cx="1305" cy="391" rx="4.5" ry="3"/>
            <path class="keyhole" d="M1307.8 474.9A5.5 5.5 0 1 1 1312.2 474.9L1313.8 485H1306.2z"/>
            <circle cx="1310" cy="470" r="0.1" data-keyhole/>
            <path class="ticks" d="M1290 461l-9-4M1289 470h-10M1290 479l-9 4M1330 461l9-4M1331 470h10M1330 479l9 4" pathLength="1" data-ticks/>
          </g>
        </g>

        <rect width="2400" height="800" fill="url(#vignette)" pointer-events="none"/>
      </svg>

      <!-- the name of the place, top left: it changes with the door -->
      <p class="where" data-where>
        <span class="where__place" data-where-place>Hotel Marlowe Vane</span>
        <span class="where__floor" data-where-floor>Third floor, east corridor</span>
      </p>
    </div>

    <!-- the dark inside the lock, while the view goes through it -->
    <div class="veil" data-veil></div>

    <!-- the rooms behind the doors: only the keyhole's shape of them shows, until it opens to the whole screen -->
    <div class="room" data-room>
      <div class="room__inner" data-room-inner>

        <!-- the hotel: a suite at the blue hour -->
        <section class="scene" data-scene="0" aria-label="The Blue Hour Suite, Hotel Marlowe Vane">
          <svg class="scene__art" viewBox="0 0 1600 1000" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
            <defs>
              <linearGradient id="r0sky" x1="0" y1="0" x2="0" y2="1">
                <stop offset="0" stop-color="#18234a"/>
                <stop offset="0.55" stop-color="#3a4d8c"/>
                <stop offset="1" stop-color="#e79a5a"/>
              </linearGradient>
              <radialGradient id="r0lamp" cx="0.5" cy="0.5" r="0.5">
                <stop offset="0" stop-color="#ffd58c" stop-opacity="0.75"/>
                <stop offset="1" stop-color="#ffd58c" stop-opacity="0"/>
              </radialGradient>
              <pattern id="r0paper" width="44" height="44" patternUnits="userSpaceOnUse">
                <rect width="44" height="44" fill="#1e2b50"/>
                <rect width="3" height="44" fill="#24345e"/>
              </pattern>
            </defs>
            <rect width="1600" height="1000" fill="url(#r0paper)"/>
            <!-- the window on the harbour -->
            <path d="M610 520V280a190 190 0 0 1 380 0v240z" fill="url(#r0sky)"/>
            <g class="r-drift">
              <circle cx="872" cy="262" r="40" fill="#f8e8c2"/>
              <circle cx="886" cy="252" r="9" fill="#ead6a6"/>
              <circle cx="858" cy="278" r="6" fill="#ead6a6"/>
            </g>
            <path class="r-pop" d="M700 214l4 12 12 4-12 4-4 12-4-12-12-4 12-4zM760 300l3 8 8 3-8 3-3 8-3-8-8-3 8-3zM668 330l2.5 7 7 2.5-7 2.5-2.5 7-2.5-7-7-2.5 7-2.5z" fill="#f8e8c2"/>
            <path d="M610 520V452h40v-22h26v22h34v-40h18v-16h10v16h22v28h50v-34h30v-18h12v18h20v40h40v-26h34v18h44v62z" fill="#121a36"/>
            <path d="M686 470h8v10h-8zM760 456h8v10h-8zM846 466h8v10h-8zM922 472h8v10h-8z" fill="#f3c46b" class="r-glow"/>
            <path d="M610 520V280a190 190 0 0 1 380 0v240zM800 92v428M610 380h380" fill="none" stroke="#e8dcc2" stroke-width="12"/>
            <rect x="588" y="518" width="424" height="22" fill="#d9caa9"/>
            <!-- the curtains -->
            <path class="r-sway" data-o="560 110" d="M520 110h120c-8 120 18 250 4 360-10 80 10 120 30 150H520z" fill="#8a2e3b"/>
            <path class="r-sway" data-o="560 110" d="M560 110c4 140-10 300 0 510" fill="none" stroke="#6c2230" stroke-width="5"/>
            <path class="r-sway" data-o="1040 110" d="M1080 110H960c8 120-18 250-4 360 10 80-10 120-30 150h154z" fill="#8a2e3b"/>
            <path class="r-sway" data-o="1040 110" d="M1040 110c-4 140 10 300 0 510" fill="none" stroke="#6c2230" stroke-width="5"/>
            <rect x="500" y="96" width="600" height="16" rx="8" fill="#c9a24e"/>
            <!-- a little picture of a boat -->
            <g class="r-swing" data-o="1230 250">
              <path d="M1230 250l-52 46M1230 250l52 46" stroke="#c9a24e" stroke-width="2" fill="none"/>
              <rect x="1166" y="294" width="128" height="104" fill="#c9a24e"/>
              <rect x="1176" y="304" width="108" height="84" fill="#f0e3c6"/>
              <path d="M1196 362h68l-10 14h-48zM1230 314v46l26-4z" fill="#3a4d8c"/>
            </g>
            <!-- the floor and the rug -->
            <rect y="860" width="1600" height="140" fill="#1d1628"/>
            <rect y="846" width="1600" height="16" fill="#2c2340"/>
            <ellipse cx="800" cy="940" rx="420" ry="54" fill="#5b2c42"/>
            <ellipse cx="800" cy="940" rx="384" ry="40" fill="none" stroke="#c9a24e" stroke-width="3" stroke-dasharray="14 10"/>
            <!-- the armchair -->
            <path d="M404 860V690c0-40 30-60 76-60s76 20 76 60v170z" fill="#7b3342"/>
            <rect x="388" y="740" width="184" height="80" rx="26" fill="#93404f"/>
            <path d="M412 820v40M548 820v40" stroke="#2a1a14" stroke-width="10"/>
            <!-- the bed -->
            <path d="M640 720V600c0-50 320-50 320 0v120z" fill="#4b2a46"/>
            <path d="M680 604v100M740 590v114M800 586v118M860 590v114M920 604v100" stroke="#3c2038" stroke-width="3"/>
            <rect x="620" y="690" width="360" height="70" rx="12" fill="#efe6d6"/>
            <rect class="r-pop" x="650" y="650" width="130" height="54" rx="20" fill="#f7f1e6"/>
            <rect class="r-pop" x="820" y="650" width="130" height="54" rx="20" fill="#f7f1e6"/>
            <path d="M606 740h388l14 120H592z" fill="#c4964f"/>
            <path d="M606 740h388l4 30H602z" fill="#d8ad66"/>
            <path d="M640 800h320M630 830h340" stroke="#a87c3c" stroke-width="3"/>
            <!-- the bedside table and the lamp -->
            <rect x="1010" y="740" width="110" height="120" rx="6" fill="#3a2618"/>
            <rect x="1022" y="770" width="86" height="6" rx="3" fill="#c9a24e"/>
            <ellipse class="r-glow" cx="1065" cy="650" rx="250" ry="250" fill="url(#r0lamp)"/>
            <path d="M1050 740h30l-6-14h-18zM1063 726v-70" stroke="#c9a24e" stroke-width="5" fill="#c9a24e"/>
            <path class="r-glow" d="M1028 668l16-58h42l16 58z" fill="#f6cd84"/>
          </svg>
          <div class="scene__text">
            <p class="scene__place">Hotel Marlowe Vane</p>
            <h2 class="scene__title" data-split>The Blue Hour Suite</h2>
            <p class="scene__line">Third floor, a window on the old harbour</p>
          </div>
        </section>

        <!-- the exhibition: weathers under glass -->
        <section class="scene" data-scene="1" aria-label="The Cabinet of Small Weathers, The Ferrow Gallery">
          <svg class="scene__art" viewBox="0 0 1600 1000" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
            <defs>
              <linearGradient id="r1cone" x1="0" y1="0" x2="0" y2="1">
                <stop offset="0" stop-color="#fffaf0" stop-opacity="0.9"/>
                <stop offset="1" stop-color="#fffaf0" stop-opacity="0"/>
              </linearGradient>
              <pattern id="r1floor" width="80" height="40" patternUnits="userSpaceOnUse">
                <rect width="80" height="40" fill="#b27d4d"/>
                <path d="M0 40L40 0M40 40L80 0" stroke="#9c6a3e" stroke-width="3"/>
              </pattern>
            </defs>
            <rect width="1600" height="1000" fill="#e9e2d4"/>
            <rect width="1600" height="84" fill="#d8cfbd"/>
            <path d="M0 140H1600" stroke="#d3c9b5" stroke-width="4"/>
            <!-- the spotlights -->
            <path class="r-glow" d="M790 84h20l110 480H680zM1170 84h20l130 560H1040z" fill="url(#r1cone)"/>
            <path d="M792 84h16v18h-16zM1172 84h16v18h-16z" fill="#2c2924"/>
            <!-- the two framed weathers on the walls: a sun and a wind -->
            <g class="r-swing" data-o="420 290">
              <path d="M420 290l-90 38M420 290l90 38" stroke="#8b8172" stroke-width="2" fill="none"/>
              <rect x="300" y="326" width="240" height="300" fill="#2b2723"/>
              <rect x="316" y="342" width="208" height="268" fill="#f7f3ea"/>
              <circle cx="420" cy="460" r="46" fill="#e2a03f"/>
              <path class="r-draw" pathLength="1" d="M420 392v-26M420 528v26M352 460h-26M488 460h26M372 412l-18-18M468 508l18 18M468 412l18-18M372 508l-18 18" stroke="#e2a03f" stroke-width="6" stroke-linecap="round" fill="none"/>
            </g>
            <g class="r-swing" data-o="1180 140">
              <path d="M1180 140l-90 108M1180 140l90 108" stroke="#8b8172" stroke-width="2" fill="none"/>
              <rect x="1060" y="246" width="240" height="300" fill="#2b2723"/>
              <rect x="1076" y="262" width="208" height="268" fill="#f7f3ea"/>
              <path class="r-draw" pathLength="1" d="M1110 350c40-30 90 30 140 0M1110 400c50-30 100 30 150 0M1110 450c40-30 80 30 120 0" stroke="#4b6f8c" stroke-width="7" stroke-linecap="round" fill="none"/>
            </g>
            <path d="M370 652h100M370 666h70M1130 572h100M1130 586h70" stroke="#a79d8c" stroke-width="5"/>
            <!-- the floor -->
            <rect y="800" width="1600" height="22" fill="#d5cbb6"/>
            <rect y="822" width="1600" height="178" fill="url(#r1floor)"/>
            <!-- the plinth and the bell jar with its little cloud -->
            <ellipse cx="800" cy="826" rx="150" ry="16" fill="#000" opacity="0.18"/>
            <rect x="700" y="566" width="200" height="260" fill="#f9f7f2"/>
            <rect x="862" y="566" width="38" height="260" fill="#e4dfd3"/>
            <rect x="690" y="556" width="220" height="14" fill="#ffffff"/>
            <ellipse cx="800" cy="552" rx="96" ry="13" fill="#6b4a33"/>
            <g class="r-bob">
              <path class="r-draw" pathLength="1" d="M760 470l-8 30M790 476l-8 30M820 470l-8 30M846 478l-8 30" stroke="#5d8bb0" stroke-width="5" stroke-linecap="round" fill="none"/>
              <path class="r-pop" d="M742 458c-22 0-30-28-10-38-2-24 26-36 44-22 10-22 46-22 54 4 22-6 38 18 26 34 12 14 0 30-16 30z" fill="#ffffff" stroke="#dfe4ea" stroke-width="4"/>
            </g>
            <path d="M716 552V398a84 84 0 0 1 168 0v154" fill="#d9ecf2" fill-opacity="0.3" stroke="#ffffff" stroke-width="4"/>
            <path d="M738 520V404c0-30 12-52 34-64" fill="none" stroke="#ffffff" stroke-width="8" stroke-linecap="round" opacity="0.7"/>
            <circle cx="800" cy="308" r="9" fill="#6b4a33"/>
            <!-- the velvet rope -->
            <path d="M560 700v150M1040 700v150" stroke="#b88a3c" stroke-width="12"/>
            <ellipse cx="560" cy="852" rx="26" ry="8" fill="#b88a3c"/>
            <ellipse cx="1040" cy="852" rx="26" ry="8" fill="#b88a3c"/>
            <circle cx="560" cy="696" r="12" fill="#d8a94f"/>
            <circle cx="1040" cy="696" r="12" fill="#d8a94f"/>
            <path class="r-draw" pathLength="1" d="M560 706C700 800 900 800 1040 706" stroke="#8e1f2b" stroke-width="10" stroke-linecap="round" fill="none"/>
          </svg>
          <div class="scene__text">
            <p class="scene__place">The Ferrow Gallery</p>
            <h2 class="scene__title" data-split>The Cabinet of Small Weathers</h2>
            <p class="scene__line">Tiny climates under glass, until 14 March</p>
          </div>
        </section>

        <!-- the hotel's winter garden -->
        <section class="scene" data-scene="2" aria-label="The Winter Garden, Hotel Ostrander">
          <svg class="scene__art" viewBox="0 0 1600 1000" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
            <defs>
              <linearGradient id="r2glass" x1="0" y1="0" x2="0" y2="1">
                <stop offset="0" stop-color="#cfe2dd"/>
                <stop offset="1" stop-color="#f3efdb"/>
              </linearGradient>
              <pattern id="r2tiles" width="80" height="80" patternUnits="userSpaceOnUse">
                <rect width="80" height="80" fill="#ece3cd"/>
                <path d="M0 0h40v40H0zM40 40h40v40H40z" fill="#2f3d37"/>
              </pattern>
            </defs>
            <rect width="1600" height="800" fill="url(#r2glass)"/>
            <!-- the snowy hills beyond the glass -->
            <path d="M0 690c160-60 280-50 400-20 180-80 340-70 520-10 160-70 340-80 480-10 100-40 160-40 200-30v180H0z" fill="#ffffff" opacity="0.85"/>
            <path d="M0 740c200-50 400-40 600 0 200-40 420-50 600 0 160-30 300-30 400-10v70H0z" fill="#e6efec"/>
            <!-- the iron of the glasshouse -->
            <path d="M0 800V200M400 800V200M800 800V200M1200 800V200M1600 800V200M0 200a200 150 0 0 1 400 0M400 200a200 150 0 0 1 400 0M800 200a200 150 0 0 1 400 0M1200 200a200 150 0 0 1 400 0M0 440H1600M200 800V50M600 800V50M1000 800V50M1400 800V50" stroke="#2f3d37" stroke-width="10" fill="none"/>
            <rect y="800" width="1600" height="200" fill="url(#r2tiles)"/>
            <rect y="796" width="1600" height="10" fill="#2f3d37"/>
            <!-- a fern hanging from the roof -->
            <g class="r-swing" data-o="560 0">
              <path d="M560 0v210" stroke="#2f3d37" stroke-width="3"/>
              <path d="M520 210h80l-12 50h-56z" fill="#b5562f"/>
              <path d="M530 214c-40 30-60 80-62 130M546 214c-14 50-12 90 0 140M574 214c14 50 12 90 0 140M590 214c40 30 60 80 62 130" stroke="#3f8a5c" stroke-width="10" stroke-linecap="round" fill="none"/>
            </g>
            <!-- the palm in its pot -->
            <path d="M330 860h120l-14 90h-92z" fill="#b5562f"/>
            <rect x="322" y="848" width="136" height="22" rx="6" fill="#c76a40"/>
            <g class="r-swing" data-o="390 850">
              <path d="M390 850c-40-120-150-180-250-170 70 20 150 70 250 170z" fill="#2f6b4a"/>
              <path d="M390 850c-10-150 40-260 120-300-30 80-70 170-120 300z" fill="#3f8a5c"/>
              <path d="M390 850c40-110 140-160 220-150-70 30-140 80-220 150z" fill="#2f6b4a"/>
              <path d="M390 850c-60-90-80-200-50-280 10 90 30 180 50 280z" fill="#4c9a69"/>
            </g>
            <!-- the orange tree -->
            <path d="M1150 856h110l-12 94h-86z" fill="#b5562f"/>
            <path d="M1205 856V660" stroke="#5b4030" stroke-width="12"/>
            <g class="r-swing" data-o="1205 760">
              <circle cx="1205" cy="600" r="110" fill="#2f6b4a"/>
              <circle cx="1130" cy="650" r="70" fill="#3f8a5c"/>
              <circle cx="1280" cy="650" r="70" fill="#3f8a5c"/>
              <circle class="r-pop" cx="1160" cy="580" r="16" fill="#f08a24"/>
              <circle class="r-pop" cx="1240" cy="560" r="16" fill="#f08a24"/>
              <circle class="r-pop" cx="1210" cy="640" r="16" fill="#f08a24"/>
              <circle class="r-pop" cx="1120" cy="670" r="14" fill="#f08a24"/>
              <circle class="r-pop" cx="1290" cy="660" r="14" fill="#f08a24"/>
            </g>
            <!-- the table for tea, its chairs, the teapot and the cups -->
            <path d="M640 860V720c0-50 70-50 70 0v140M600 860l40-80M960 860V720c0-50-70-50-70 0v140M1000 860l-40-80" stroke="#2f3d37" stroke-width="6" fill="none"/>
            <path d="M800 732v130M760 870h80" stroke="#2f3d37" stroke-width="10"/>
            <ellipse cx="800" cy="728" rx="150" ry="26" fill="#f6f1e4"/>
            <path d="M650 728c0 30 300 30 300 0" fill="#e9e1cf"/>
            <g class="r-pop">
              <ellipse cx="780" cy="678" rx="46" ry="40" fill="#3b7a8c"/>
              <path d="M820 670c30-6 36-30 46-40" stroke="#3b7a8c" stroke-width="10" stroke-linecap="round" fill="none"/>
              <path d="M738 660c-30-6-30 40 0 36" stroke="#3b7a8c" stroke-width="8" fill="none"/>
              <path d="M760 642h40l-6-12h-28z" fill="#2c5f6d"/>
              <rect x="772" y="620" width="16" height="12" rx="6" fill="#2c5f6d"/>
            </g>
            <path class="r-draw" pathLength="1" d="M868 622c-12-20 12-30 0-52s12-30 0-50M884 626c-10-18 10-26 0-44" stroke="#ffffff" stroke-width="5" stroke-linecap="round" fill="none"/>
            <path class="r-pop" d="M886 700h36l-4 22h-28z" fill="#ffffff"/>
            <path class="r-pop" d="M696 702h32l-4 20h-24z" fill="#ffffff"/>
          </svg>
          <div class="scene__text">
            <p class="scene__place">Hotel Ostrander</p>
            <h2 class="scene__title" data-split>The Winter Garden</h2>
            <p class="scene__line">Tea under glass, from four in the afternoon</p>
          </div>
        </section>

        <!-- the light that comes through the keyhole, before the room is near enough to be seen -->
        <div class="room__light" data-room-light></div>
      </div>
    </div>

    <!-- the soft rim of light round the keyhole while it opens -->
    <svg class="rim" aria-hidden="true" data-rim>
      <path class="rim__wide" data-rim-wide/>
      <path class="rim__thin" data-rim-thin/>
    </svg>

    <!-- the key: hanging on its hook, then in the lock -->
    <div class="key" data-key aria-hidden="true">
      <div class="key__turn" data-key-turn>
      <svg class="key__art" viewBox="0 0 40 100">
        <defs>
          <linearGradient id="keyBrass" x1="0" y1="0" x2="1" y2="0">
            <stop offset="0" stop-color="#8a5f25"/>
            <stop offset="0.35" stop-color="#f0cf85"/>
            <stop offset="0.6" stop-color="#c99847"/>
            <stop offset="1" stop-color="#7a521d"/>
          </linearGradient>
          <path id="keyShape" fill-rule="evenodd" d="M20 1.5a8 8 0 0 1 7.6 10.6 8 8 0 0 1 1.4 15.4H23v2.5h3v4h-3.6V76h9.2v4h-3v4h3v6h-9.2v4.5h-4.8V34h-3.6v-4h3v-2.5H11a8 8 0 0 1 1.4-15.4A8 8 0 0 1 20 1.5zM20 6a3.6 3.6 0 1 0 0 7.2A3.6 3.6 0 0 0 20 6zM13 16.5a3.6 3.6 0 1 0 0 7.2 3.6 3.6 0 0 0 0-7.2zM27 16.5a3.6 3.6 0 1 0 0 7.2 3.6 3.6 0 0 0 0-7.2z"/>
          <clipPath id="keyCut" clipPathUnits="userSpaceOnUse">
            <rect x="-10" y="-10" width="60" height="120" data-key-cut/>
          </clipPath>
        </defs>
        <!-- the part of the key already inside the lock is cut away from below (the script sets the height) -->
        <g clip-path="url(#keyCut)">
          <use href="#keyShape" class="key__shadow" x="1.4" y="2.2"/>
          <use href="#keyShape" fill="url(#keyBrass)"/>
          <path d="M18.2 36v52" stroke="#fff3cf" stroke-width="0.9" opacity="0.7"/>
        </g>
      </svg>
      </div>
    </div>

    <!-- the one command: turn the key, then step back out -->
    <div class="dock" data-dock>
      <button class="act" type="button" data-act data-autoclic>
        <svg class="act__icon" viewBox="0 0 24 24" aria-hidden="true">
          <path d="M8.5 4.5a4 4 0 1 1 0 8 4 4 0 0 1 0-8zM12 9.8l8.5-.1M17 9.7v3.2M19.6 9.7v2.2"/>
        </svg>
        <span class="act__label"><span data-act-label>Turn the key</span></span>
      </button>
    </div>

    <p class="sr-only" aria-live="polite" data-live></p>
  </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.

52 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 «La transizione dalla serratura» da Animorio e adattala al mio sito

Cosa fa

Su una carta da parati verde petrolio a righe e losanghe, fra due applique accese, una porta rosso sangue di bue a quattro pannelli, con la cornice color crema, la targhetta d'ottone Blue Hour Suite, la piastra lunga col pomolo e il buco della serratura, da cui filtra una luce calda; sotto la porta, sul pavimento, la riga di luce della stanza. In alto a sinistra Hotel Marlowe Vane e Third floor, east corridor. All'arrivo le applique si accendono con un tremolio, un riflesso passa sulla targhetta e la chiave cade sul suo gancio e oscilla fino a fermarsi. Col puntatore sulla porta la chiave ondeggia un poco e intorno alla serratura si accende un alone. Premendo Turn the key (o la porta) la chiave si stacca dal gancio, vola alla serratura inclinandosi, entra nella toppa (la sua parte bassa sparisce nel buco) e gira due volte su sé stessa: a ogni scatto la piastra dà un colpetto e tre lineette d'ottone lampeggiano ai suoi lati. Poi la luce della stanza esce dal buco e lo contorna; la chiave vola via verso chi guarda e la vista si tuffa nella serratura: la porta cresce intorno al buco e scivola al centro, si fa buia come l'interno della serratura, e nel nero resta il buco, con dentro la stanza, che si allarga fino a riempire lo schermo. Dentro, la Blue Hour Suite: la finestra ad arco sul porto all'ora blu con la luna e i tetti, le tende che si assestano, il quadretto con la barca che oscilla sul chiodo, il letto, la poltrona e la lampada che si accende con un guizzo; il titolo sale lettera per lettera. Step back out torna indietro attraverso il buco, ma di un'altra porta: la porta verde della Ferrow Gallery con la lunetta di vetro sopra, che apre The Cabinet of Small Weathers (un sole e un vento in cornice, una nuvola sotto una campana di vetro che piove, il cordone di velluto), e poi la porta blu a sei pannelli dell'Hotel Ostrander, che apre The Winter Garden (la serra di ferro e vetro con la neve fuori, la felce appesa, la palma, l'arancio, il tè che fuma). Poi si ricomincia dalla prima.

Come funziona

Un numero solo per il tuffo

Tutto il tuffo dipende da st.p, da 0 a 1, che una timeline di GSAP fa correre in ZOOM secondi; render() ne ricava il resto. Lo zoom è esponenziale, z = zFull elevato a p, così la velocità sembra costante dal primo all'ultimo istante (raddoppiare è sempre lo stesso passo), e zFull è lo zoom a cui il cerchio del buco copre anche gli angoli dello schermo: metà della diagonale diviso il raggio del buco in pixel, più un margine.

La porta cresce intorno al buco

La porta, un SVG che riempie lo schermo con preserveAspectRatio slice, cresce di z con il transform-origin sul buco della serratura (la sua posizione sullo schermo viene da getScreenCTM) e intanto scivola verso il centro, tutta entro z = 6. Fra z = 2,2 e z = 8 un velo nero sale da 0 a 1: è il buio dentro la serratura; poi la porta si nasconde, perché ingrandita cento volte non serve più a nessuno.

La maschera a forma di buco

La stanza sta sopra la porta, ritagliata con un clip-path path() a forma di buco: un arco di cerchio largo più di mezzo giro (A con large-arc 1) e la fessura a trapezio sotto. render() lo riscrive a ogni fotogramma nello stesso punto e alla stessa scala del buco disegnato sulla porta, così combaciano; alla fine il clip-path diventa none. Intorno, lo stesso tracciato in un SVG a parte fa l'orlo di luce, che si spegne mentre il buco si allarga.

La chiave entra e gira

La chiave è un div con un SVG dentro, sopra la stanza. Vola dal gancio con due serie di keyframes (su e giù, inclinata e dritta), poi scende nella toppa: un clipPath dell'SVG, un rettangolo che si accorcia, taglia da sotto la parte già entrata, in sincronia con la discesa. Gli scatti fanno girare la chiave su sé stessa di TURN_ANGLE gradi con back.out: la larghezza segue il coseno dell'angolo e uno skewY leggero dà la profondità; per ogni scatto la piastra si sposta di un pixel e tre lineette si disegnano con stroke-dashoffset.

La profondità e la stanza che arriva

Nella stanza, la scena interna parte da DEPTH volte più grande e si assesta a 1 mentre il buco si apre, e davanti c'è una luce calda che si dissolve fra z = 3 e z = 26: da lontano nel buco si vede solo luce, poi la stanza. A metà del tuffo parte l'entrata della stanza: il titolo diviso in lettere sale con back.out, quadri e piante oscillano dal loro punto d'attacco con svgOrigin ed elastic, luci con un tremolio a keyframes, linee disegnate con pathLength 1.

Valori da cambiare

NomeFileDi partenzaCosa cambia
TURNSscript.js2Gli scatti della chiave nella serratura prima che la porta ceda.
TURN_ANGLEscript.js70Di quanti gradi gira la chiave a ogni scatto.
ZOOMscript.js1.5I secondi del tuffo nel buco della serratura (l'uscita ne dura l'85 per cento).
DEPTHscript.js1.4Quanto è lontana la stanza all'inizio del tuffo (1 = nessuna profondità).
SWINGscript.js1Quanto oscilla la chiave sul gancio (0 = resta ferma).
LIGHTscript.js0.85Quanto è forte la luce della stanza che passa dal buco.
KEY_SIZEscript.js1.15La grandezza della chiave rispetto alla porta.

Provali nel Playground

Quando usarla

Per il sito di un hotel che apre le sue stanze una alla volta, per la mostra che fa sbirciare le sale prima di entrare, per un calendario dell'avvento, per la pagina segreta di un evento o di un lancio, per il passaggio fra la copertina e il contenuto di un racconto.

Sul telefono

La porta è disegnata in un SVG molto largo con preserveAspectRatio slice, così sul telefono in verticale resta intera al centro e si perdono solo i lati del corridoio; il tuffo è lo stesso, con lo zoom calcolato dalla diagonale dello schermo. Le stanze tengono le cose importanti al centro. L'ondeggiare della chiave sotto il puntatore c'è solo col mouse.

Con il movimento ridotto

Niente si sposta: la chiave sparisce dal gancio e ricompare nella serratura già girata, la luce del buco si accende, e la stanza, già tutta al suo posto, compare con una dissolvenza; all'uscita la stanza si dissolve sulla porta dopo, con la chiave che ricompare sul gancio.

Trappole

  • La vignettatura del corridoio, un rettangolo grande quanto tutto il disegno, stava sopra la porta e si prendeva i clic e il passaggio del puntatore: la porta non si apriva col clic e la chiave non ondeggiava. Ha pointer-events none.
  • La chiave girava con rotationY e una prospettiva sul suo contenitore, che però viene spostato sullo schermo con x e y: la prospettiva restava al punto di partenza e la chiave si allungava di traverso. Con la prospettiva sul disegno interno il Chrome delle prove si bloccava a metà giro; ora il giro è piatto: la larghezza segue il coseno dell'angolo (oltre i 90 gradi si vede l'altro lato) e un piccolo skewY dà la profondità.
  • Il pulsante restava vuoto per un attimo al clic: il fromTo che fa risalire la scritta nuova, dentro una timeline costruita subito, si disegnava subito e nascondeva la scritta vecchia. Ci vuole immediateRender: false.
  • Il secondo clic veniva ignorato finché la stanza non aveva finito la sua entrata (quasi cinque secondi), e il pilota delle anteprime clicca ogni 2,8: il ritorno si libera appena la stanza riempie lo schermo, mentre i quadri oscillano ancora; l'entrata della stanza si ferma se la si rivede.
  • Il titolo della serra, verde scuro, finiva sopra i ferri scuri della serra e non si leggeva, e quello della galleria sopra i fili dei quadri: dietro ai titoli c'è un velo del colore della stanza, che sfuma dall'angolo in alto a sinistra.
  • Con la chiave nella toppa il suo anello copriva il pomolo: il pomolo è salito sulla piastra, che si è allungata.

Librerie

GSAP 3.15.0, Fraunces (font variabile) 5.3.0, Instrument Sans (font variabile) 5.3.0.

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

Collaudo

Provata il 11 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.

Tutte le animazioni