
The loader that becomes a logo
Transitions, SVG, Text
NewFree
A rich, colourful page change for a painters and decorators’ site: on a click masking tape runs along the edges, the roller loads up with paint in the tray and paints the new page in lanes, down and up, with ridges at the edges of each pass, specks and drips; then it flies off, the fresh paint shines and dries, the tape peels away and the new headline arrives.
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 painter’s roller transition</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/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage" data-stage>
<!-- the first page: a painters and decorators studio, on warm cream walls -->
<section class="page page--a is-on" data-page="a" aria-label="Rooms">
<header class="bar">
<span class="brand"><svg class="brand__mark" viewBox="0 0 20 20" aria-hidden="true"><rect x="2" y="3" width="16" height="7" rx="2"/><path d="M18 6.5h1.2v5H10v3"/><rect x="8.6" y="14" width="2.8" height="5" rx="1"/></svg>Fresh Coat</span>
<nav class="nav" aria-label="Pages">
<a class="nav__link is-here" href="#rooms" aria-current="page">Rooms</a>
<a class="nav__link" href="#colours" data-go="b">Colours</a>
</nav>
</header>
<div class="body">
<p class="kicker" data-late>Painters & decorators</p>
<h1 class="title" data-title><span class="title__line"><span class="title__in">Rooms, painted</span></span><span class="title__line"><span class="title__in">by hand.</span></span></h1>
<p class="lede" data-late>Two coats, crisp edges, no drips left behind. We tape, we roll, we tidy up.</p>
<a class="go" href="#colours" data-go="b" data-autoclic data-late>
<span class="go__chip" aria-hidden="true"></span>
<span class="go__text">See this season’s colour</span>
<svg class="go__arrow" viewBox="0 0 26 12" aria-hidden="true"><path d="M1 6h22"/><path d="M18.5 1.5 23.5 6l-5 4.5"/></svg>
</a>
</div>
<aside class="chips" data-late aria-hidden="true">
<span class="chip" style="--c: #efe3cc"><b>Oat milk</b></span>
<span class="chip" style="--c: #d9b98f"><b>Biscuit</b></span>
<span class="chip" style="--c: #c9734f"><b>Clay pot</b></span>
</aside>
<footer class="foot" data-late><span>London & around</span><span>Free colour visits</span></footer>
</section>
<!-- the second page: the colour of the season, a deep lagoon green -->
<section class="page page--b" data-page="b" aria-label="Colours" aria-hidden="true">
<header class="bar">
<span class="brand"><svg class="brand__mark" viewBox="0 0 20 20" aria-hidden="true"><rect x="2" y="3" width="16" height="7" rx="2"/><path d="M18 6.5h1.2v5H10v3"/><rect x="8.6" y="14" width="2.8" height="5" rx="1"/></svg>Fresh Coat</span>
<nav class="nav" aria-label="Pages">
<a class="nav__link" href="#rooms" data-go="a">Rooms</a>
<a class="nav__link is-here" href="#colours" aria-current="page">Colours</a>
</nav>
</header>
<div class="body">
<p class="kicker" data-late>Colour of the season</p>
<h1 class="title" data-title><span class="title__line"><span class="title__in">Lagoon,</span></span><span class="title__line"><span class="title__in">deep and calm.</span></span></h1>
<p class="lede" data-late>A green with a drop of blue, matt on walls, satin on the woodwork.</p>
<a class="go" href="#rooms" data-go="a" data-autoclic data-late>
<span class="go__chip" aria-hidden="true"></span>
<span class="go__text">Back to the rooms</span>
<svg class="go__arrow" viewBox="0 0 26 12" aria-hidden="true"><path d="M1 6h22"/><path d="M18.5 1.5 23.5 6l-5 4.5"/></svg>
</a>
</div>
<aside class="chips" data-late aria-hidden="true">
<span class="chip" style="--c: #2c8a83"><b>Shallows</b></span>
<span class="chip" style="--c: #1f6f6a"><b>Lagoon</b></span>
<span class="chip" style="--c: #134a47"><b>Deep end</b></span>
</aside>
<footer class="foot" data-late><span>London & around</span><span>Free colour visits</span></footer>
</section>
<!-- the painting: masking tape, the tray, the roller, ridges, drips and specks -->
<svg class="paint" data-paint aria-hidden="true">
<defs>
<clipPath id="paintClip" clipPathUnits="userSpaceOnUse" data-clip></clipPath>
<pattern id="nap" width="5" height="7" patternUnits="userSpaceOnUse" data-nap>
<path d="M1 0v3.4M3.5 2.2v3.6M2.2 4.6v2.4" stroke="rgba(0,0,0,.28)" stroke-width=".9" stroke-linecap="round"/>
<path d="M4.4 .4v1.8M.4 4v1.6" stroke="rgba(255,255,255,.22)" stroke-width=".8" stroke-linecap="round"/>
</pattern>
<pattern id="streak" width="11" height="160" patternUnits="userSpaceOnUse" data-streak>
<path d="M2.5 0v70M6 88v60M8.5 22v34" stroke="rgba(0,0,0,.1)" stroke-width="1.4"/>
<path d="M1 50v44M4 110v40M9.5 0v18" stroke="rgba(255,255,255,.12)" stroke-width="1.2"/>
</pattern>
<linearGradient id="sleeveShade" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fff" stop-opacity=".34"/>
<stop offset=".28" stop-color="#fff" stop-opacity=".06"/>
<stop offset=".7" stop-color="#000" stop-opacity=".08"/>
<stop offset="1" stop-color="#000" stop-opacity=".34"/>
</linearGradient>
<linearGradient id="metal" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#e8ecef"/><stop offset=".45" stop-color="#9aa3aa"/><stop offset="1" stop-color="#6c757c"/>
</linearGradient>
<linearGradient id="wood" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stop-color="#a86b37"/><stop offset=".45" stop-color="#d49a5e"/><stop offset="1" stop-color="#9a5f2e"/>
</linearGradient>
<linearGradient id="trayBody" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#f4f1ec"/><stop offset="1" stop-color="#cfc8be"/>
</linearGradient>
<linearGradient id="sheen" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stop-color="#fff" stop-opacity="0"/>
<stop offset=".5" stop-color="#fff" stop-opacity=".42"/>
<stop offset="1" stop-color="#fff" stop-opacity="0"/>
</linearGradient>
</defs>
<!-- on the fresh paint, clipped to what is painted -->
<g clip-path="url(#paintClip)">
<rect class="streaks" data-streaks fill="url(#streak)"/>
<g data-ridges></g>
<g data-drips></g>
<rect class="sheen" data-sheen fill="url(#sheen)"/>
</g>
<g data-specks></g>
<!-- the tray, with its ribbed ramp and a pool of paint -->
<g class="tray" data-tray>
<path class="tray__shadow" data-tray-shadow/>
<path class="tray__body" data-tray-body fill="url(#trayBody)"/>
<path class="tray__pool" data-tray-pool/>
<path class="tray__poolshine" data-tray-shine/>
<g class="tray__ribs" data-tray-ribs></g>
<path class="tray__lip" data-tray-lip/>
</g>
<!-- the roller: wooden handle, wire frame, a fluffy sleeve full of paint -->
<g class="roller" data-roller>
<path class="roller__shadow" data-r-shadow/>
<path class="roller__wire" data-r-wire/>
<path class="roller__wireshine" data-r-wireshine/>
<rect class="roller__ferrule" data-r-ferrule fill="url(#metal)"/>
<path class="roller__handle" data-r-handle fill="url(#wood)"/>
<g class="roller__grain" data-r-grain></g>
<circle class="roller__hole" data-r-hole/>
<g class="roller__sleeve" data-r-sleeve>
<rect class="sleeve__base" data-s-base/>
<rect class="sleeve__paint" data-s-paint/>
<rect class="sleeve__nap" data-s-nap fill="url(#nap)"/>
<rect class="sleeve__shade" data-s-shade fill="url(#sleeveShade)"/>
<ellipse class="sleeve__cap" data-s-cap-l/>
<ellipse class="sleeve__cap" data-s-cap-r/>
</g>
</g>
</svg>
<!-- masking tape along the top and the bottom edge -->
<div class="tape tape--top" data-tape aria-hidden="true"><span class="tape__tear"></span></div>
<div class="tape tape--bottom" data-tape aria-hidden="true"><span class="tape__tear"></span></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>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.
49 values to change
With the Animorio connector, Claude Code takes it from here, guide included, and fits it into your project. Paste this request:
Take “The paint roller transition” from Animorio and fit it into my siteThe two pages sit one over the other. On a click the new page becomes visible above the old one but clipped by an SVG clipPath (clip-path: url(#paintClip) on the HTML element) holding one rectangle per lane, zero high: what the roller paints is the new page being uncovered, with its background colour. The new page’s words stay hidden until the paint is dry.
The roller is an SVG group with the centre of the sleeve at 0,0: GSAP moves it with x and y and turns it around that point (svgOrigin 0 0). The sleeve has four layers: the white base, the paint going from clear to full as it loads, the nap (a pattern of short strokes) and a light-and-shade gradient. As the roller moves, the nap’s patternTransform slides by the distance travelled: the sleeve seems to turn.
There are as many lanes as fit (four on a computer, three on a phone), as wide as the roller. For each one an object goes from 0 to 1 in LANE_TIME seconds with power1.inOut: on every frame the roller moves down (or up, in the odd lanes) and the lane’s clip rectangle stretches to just past the roller. Every press starts with a 0.08 second squash there and back and a few specks flying off the contact point.
Over the new page, inside the same clip, there are the nap lines (a pattern of vertical strokes), two ridges per lane (a darker and a lighter stroke that wobble slightly) and the drips: a rounded rectangle stretching with scaleY from its top end and a drop moving down with it. So they only appear where the paint has already been.
Once the roller is out, a gradient band of light crosses the page and ridges, lines, drips and specks fade in 0.9 seconds. The two tape strips (divs with a striped gradient and a torn edge made with clip-path) peel away from their right end, then the headline lines rise from under their mask going from weight 300 to 560.
With prefers-reduced-motion there is no tape, tray or roller: the new page fades in over 0.6 seconds, already set.
| Name | File | Default | What it changes |
|---|---|---|---|
LANES | script.js | 4 | The lanes of the pass on a computer: the roller is as wide as one lane. |
LANES_PHONE | script.js | 3 | The lanes on a phone. |
LANE_TIME | script.js | 0.62 | How many seconds the roller takes for one lane. |
DIPS | script.js | 2 | How many times the roller goes up and down the tray’s ramp. |
DRIPS | script.js | 2 | The drips for each lane. |
SPECKS | script.js | 7 | The specks at each press of the roller. |
SHEEN | script.js | 0.42 | How much the fresh paint shines when the light passes (0 none). |
TAPE | script.js | true | The masking tape on the edges. |
WORD_TIME | script.js | 0.8 | How many seconds a headline line takes to arrive. |
--cream | style.css | #f3ead8 | The cream of the first page’s walls (and of the paint on the way back). |
--lagoon | style.css | #1f6f6a | The lagoon green of the second page and of the paint. |
--accent | style.css | #e2582b | The terracotta of the button. |
--tape | style.css | #e9d8a6 | The colour of the masking tape. |
For the page change of a painters and decorators’ site, a paint shop, an interior design or renovation studio, or to move between two colours of a collection.
The same animations with three lanes instead of four, a smaller roller and tray, a smaller headline and colour chips. A tap changes page.
No tape, tray or roller: the new page fades in, already set.
GSAP 3.15.0, Fraunces (font variabile) 5.3.0, Instrument Sans (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 11 KB; the libraries: GSAP 28 KB.
Tested on 23 October 2026
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.
With your account’s email and password.
or with your email
You’re signed in as .
With your account, you can download the zip of every animation and every pack, for free.