Preloader
A loading screen in the manner of the studio sites of the awards circuit, in cream and ink.
Documentation
-
Copy to Webflow
Add Geist and Geist Mono to your site's fonts (Site settings, Fonts, Google Fonts) before pasting: Webflow removes any font your site has not installed. -
Add custom CSS
Custom CSS in WebflowThis CSS already ships inside the Webflow embed, so a pasted section needs nothing added. Paste it yourself, in the page or the site custom code, only if the effect runs on several pages. The tutorial explains where.
See the tutorialThe overlay covers the page from the first paint only if it comes first: keep it at the top of the Body, and if you move this code out of the Embed, put the CSS in the head of the page or of the site. -
Add custom JS
Custom JavaScript in WebflowThis script already ships inside the Webflow embed, settings block included. You paste it yourself, before the closing body tag of the page or the site custom code, only when the effect has to run on several pages, and the tutorial says how to choose.
See the tutorial -
Tune it
How to add a custom attribute in WebflowA custom attribute is a pair, a name and a value, typed in the Settings panel of the element you selected, under Custom attributes. The name is what the script looks for, the value is what it reads. The tutorial shows the panel.
See the tutorialCustom attributesSelect the section on the canvas and open the Settings panel. Under Custom attributes, add a pair: the name on the left, the value on the right.Custom attribute What it does data-preloader Marks the overlay, the block that covers the page while it loads. It takes no value: leave the value field empty. Keep it first in the Body, so it covers the page from the first paint. data-preloader-panels Marks the layer of panels, one box per panel, any number of them, that lift off the page one after the other. Without it the overlay leaves with no lift, only the counter going out. [ Optional ] data-preloader-counter Marks the box the three figures leave through, upwards. Without it the digit boxes are looked for in the whole overlay. [ Optional ] data-preloader-digit Marks one digit box, three of them: hundreds, tens, units. The engine builds the strip of figures inside each, and a missing box itself. [ Optional ] data-preloader-label Marks a short line that fades out with the counter, top left in the preview. [ Optional ] data-preloader-note Marks a second short line that fades out with the counter, top right in the preview. [ Optional ] data-preloader-line Marks the progress hairline, filled from the left as the page loads. Without it nothing shows the progress but the digits. [ Optional ] data-preloader-intro On any element of the page, outside the overlay: hidden when the loader starts, raised into place one after the other as the panels lift, in page order. Without it the page is simply there under the curtain. It is a mark, not a setting: the time of the rise is data-preloader-intro-duration, on the overlay. [ Optional ] SettingsEvery setting is a custom attribute too, added the same way and on the same section. An attribute always wins over the defaults written in the code.Attribute Default What it is data-preloader-min 2.2 The shortest time the counter takes to reach 100, in seconds, even on a page that is already there. data-preloader-max 8 The longest the loader waits for the page before leaving anyway, in seconds. data-preloader-ceiling 90 Where the counter waits while the page is still loading, 0 to 100. data-preloader-roll 0.55 The time the digits take to roll from one step to the next, in seconds. data-preloader-hold 0.35 The pause on 100 before the curtain lifts, in seconds. data-preloader-exit 0.5 How long the figures take to leave through their window, in seconds. data-preloader-open 0.9 How long one panel takes to lift off the page, in seconds. data-preloader-open-stagger 0.08 Delay between two panels starting to lift, in seconds. data-preloader-open-ease power4.inOut Shape of the lift, where it speeds up and where it settles. Options: any GSAP ease, power4.inOut and expo.inOut for instance. data-preloader-lift clamp(24px, 4vh, 48px) How far an intro element travels up while it fades in, any CSS unit. data-preloader-intro-duration 1 How long one intro element takes to settle, in seconds. data-preloader-intro-stagger 0.08 Delay between two intro elements, in seconds. data-preloader-fade 0.35 Under reduced motion, how long the overlay takes to fade out, in seconds. data-preloader-once false Whether the loader plays once per browser session, and is skipped on the next pages. Options: false (every page), true (the first page of the session only). data-preloader-remove true Whether the overlay is taken out of the DOM once done. Options: true, false (it stays, hidden, and can be played again). data-preloader-label-loading Loading What is read out to a screen reader while the page loads. Options: any short phrase. data-preloader-label-done Loaded What is read out once the page is in. Options: any short phrase.
Two files, no build step. It also needs GSAP 3 on the page, loaded with the tag in step 1.
It runs in the browser only. Load it with a script tag in plain HTML, in a
client-side script in Astro, never in the frontmatter. In React, call
Preloader.mount(ref.current) in useEffect and
destroy() in its cleanup.
-
Tune it, either way
Per element with an attribute, or once for the whole site by editingDEFAULTSat the top of the file.Attribute Default What it is Lower Higher data-preloader-min 2.2 The shortest time the counter takes to reach 100, in seconds, even on a page that is already there. The loader is a flash, the digits barely roll A long, deliberate count that holds the visitor data-preloader-max 8 The longest the loader waits for the page before leaving anyway, in seconds. A slow page is revealed half loaded The visitor can be kept on a stalled count for a long time data-preloader-ceiling 90 Where the counter waits while the page is still loading, 0 to 100. The count stalls early, and the page is felt to be slow The count runs almost to the end before it waits; at 100 it no longer waits at all data-preloader-roll 0.55 The time the digits take to roll from one step to the next, in seconds. The figures snap to each step Each step is a slow, heavy turn of the wheels data-preloader-hold 0.35 The pause on 100 before the curtain lifts, in seconds. The lift follows the count at once A beat on the full count data-preloader-exit 0.5 How long the figures take to leave through their window, in seconds. They snap out They drift out data-preloader-open 0.9 How long one panel takes to lift off the page, in seconds. The curtain snaps up A slow, heavy lift data-preloader-open-stagger 0.08 Delay between two panels starting to lift, in seconds. The panels lift as one wall, 0 being all at once A visible ripple across the screen, from the left data-preloader-open-ease power4.inOut Shape of the lift, where it speeds up and where it settles. Options: any GSAP ease, power4.inOut and expo.inOut for instance. data-preloader-lift clamp(24px, 4vh, 48px) How far an intro element travels up while it fades in, any CSS unit. It barely moves It slides in from further down data-preloader-intro-duration 1 How long one intro element takes to settle, in seconds. It is simply there It drifts into place data-preloader-intro-stagger 0.08 Delay between two intro elements, in seconds. They arrive together One after the other, down the page data-preloader-fade 0.35 Under reduced motion, how long the overlay takes to fade out, in seconds. It is gone at once A soft cross-fade to the page data-preloader-once false Whether the loader plays once per browser session, and is skipped on the next pages. Options: false (every page), true (the first page of the session only). data-preloader-remove true Whether the overlay is taken out of the DOM once done. Options: true, false (it stays, hidden, and can be played again). data-preloader-label-loading Loading What is read out to a screen reader while the page loads. Options: any short phrase. data-preloader-label-done Loaded What is read out once the page is in. Options: any short phrase. -
Drive it from JavaScript
When the page has something of its own to wait for, a first API call or a video that must be playable, or plays something of its own as the curtain lifts.const element = document.querySelector('[data-preloader]'); element.addEventListener('preloader:reveal', (e) => { console.log(e.detail); }); const loader = Preloader.mount(element); // the instance already playing, or null until GSAP is on the page loader.wait(fetch('/api/first')); // holds the count at the ceiling until it settles loader.replay(); // plays it again, the overlay put back // When the loader has to go for good: loader.destroy(); // gives the scroll back and the markup as it was written
-
Fit it to your page
- Put the overlay first in the body, or first in the section that is the page: it is fixed and covers the viewport from the first paint, before any script runs, so the page never flashes under it. The stylesheet must be on the page from the head for the same reason.
- One setting for every page, without opening the file: declare
window.PreloaderSettings = { min: 3 }in a script placed beforepreloader.js. The three levels, least specific first, areDEFAULTS, then that object, then adata-preloader-*attribute on the overlay. - GSAP 3 has to be on the page. The engine waits for
gsapinstead of assuming it, up tomaxseconds; past that it takes the overlay off the page and warns in the console, since a loader must never be the reason a page cannot be read. Without any script at all, the stylesheet lets the overlay go after twelve seconds. - The count follows the real load. Ready means the
loadevent has fired,document.fontsis ready and every promise handed towait()has settled. Until then the counter stops atceiling; once ready it runs to 100, never beforeminseconds, never aftermax. For something the browser does not know about, a first API call or a video that must be playable, hand the engine a promise:el.__preloader.wait(promise), orPreloader.mount(el).wait(promise). - The page comes in under the curtain. Mark what should rise into place with
data-preloader-intro, anywhere on the page, in the order it should arrive: the engine hides those elements when a play starts and raises them once the panels start to lift, one after the other. For anything else, listen topreloader:revealon the document: it fires at the same moment. Do not hide content yourself on load; on a session where the loader is skipped, nothing would bring it back. - Events, all bubbling from the overlay, with
{ preloader, value }asevent.detail:preloader:startat the start of a play,preloader:progresseach time the whole number changes,preloader:completewhen the counter reaches 100 and the page is in,preloader:revealwhen the panels start to lift,preloader:doneonce the overlay is out (withskipped: truewhen it never played), andpreloader:skipon a session that already saw it. - From JavaScript:
Preloader.mount(element, overrides)mounts and plays at once, and returnsnullwhile GSAP is not on the page. The api carriesplay()(a new play if the loader is idle, respectingonce),replay()(a new play whatever the session says, the overlay put back if it was removed),skip()(straight to done),wait(promise),state()(loading,complete,opening,done,skipped),progress()(the value shown),settings, anddestroy(), which gives the markup back as it was written.Preloader.auto(container)mounts every overlay found inside a container, readingwindow.PreloaderSettings. - Accessibility. The digits are hidden from screen readers; a live region built by the engine reads
labelLoadingwhen the play starts andlabelDonewhen the page is in. The body carriesaria-busy="true"while the overlay is up. The scroll is held onhtmlby an attribute the engine removes when done, and focus, if it sat in the overlay, is released. - **Under
prefers-reduced-motion: reduce** the counter reads 100 at once, nothing rolls, the intro elements are never hidden, and the overlay fades out infadeseconds as soon as the page is ready. Every event still fires. - The words of the markup are the preview's, and they ship as they are: put the site's own label, note and hero in their place. The demo page also puts a
preventDefaulton its menu link and adds a Replay control: remove both. - On Webflow, add the fonts first. Add Geist and Geist Mono to your site's fonts (Site settings, Fonts, Google Fonts) before pasting: the paste names them, and the Designer drops a font the site does not have.
-
Avoid the pitfalls
- **
onceneeds the engine early.** The skip is decided when the engine runs, so a page whose script tags sit at the end of a long body shows the overlay for the time the body takes to parse. Loadpreloader.jsright after the overlay, or in the head withdefer, on a site that plays once. - A page that scrolls on load. The browser restores the scroll position of a reload under the overlay. A site that wants every play to start at the top writes
history.scrollRestoration = 'manual'itself; the engine does not touch it. - **
ceilingat 100 stops waiting.** The count then reaches 100 on its pace alone,completefires, and the curtain lifts on a page that may still be loading. Keep it under 100 unless the page has nothing to wait for. - **Do not force
displayorvisibilityon the overlay in CSS**, noroverflowonhtml: the engine hides the overlay by its state attribute and holds the scroll bydata-preloader-lock, and takes both back. - The panels lift from their top edge:
transform-origin: topon them is a mechanic, not a look. A class of yours that sets another origin makes the panels shrink to their middle. - An intro element with a transform of its own (a rotated label, a translated box) loses it during the rise and gets it back at the end, since the engine writes and clears
transform. Wrap it, and mark the wrapper. - Two overlays on a page each play on their own; the session flag is shared, so the second one is skipped with the first.
- **
One click on Copy the AI prompt copies everything an assistant needs to build this effect: the complete code, the markup, the dependency, every setting and every pitfall.
What it does
Preloader is a page transition for Webflow and vanilla JavaScript, built on GSAP 3. Also called odometer counter preloader, preloader counter animation or preloader 0 to 100.
A loading screen in the manner of the studio sites of the awards circuit, in cream and ink. On arrival, a screen of ink covers the cream page: a terminal label top left, a note top right, a giant three figure counter bottom right in cream and a hairline across the foot that fills in cream. The counter does not count, it rolls: each figure is a strip of digits sliding through a one line window, an odometer. It shows steps, not every number: 000, 031, 067, 100, each reached with a roll of the figures and held long enough to be read, while the hairline follows the load continuously. The pace is that of a real download, runs and pauses, never a straight line. The page holds it back: while the document, its fonts and its images are still coming in, the counter waits at 90, and it never shows 100 before the page is really there, within a shortest and a longest time. At 100 the figures leave upwards through their window, then the four panels lift off the page one after the other from the left, each one rolling up to its top edge like a blind, and the page brings its own content in under them: anything marked data-preloader-intro rises into place, and an event fires for the rest. Once played, the overlay is taken out of the DOM, the scroll is given back, and it can be set to play once per session. Under reduced motion the counter reads 100 at once and the overlay fades out as soon as the page is in.
Updated
Questions
Paste it and keep the overlay, the element marked data-preloader, first in the Body so it covers the page from the first paint. Its settings are custom attributes on that overlay, for instance data-preloader-min="3", and data-preloader itself takes no value.
No. The counter needs the GSAP 3 core, no plugins, and the embed loads 3.12.5, but the engine waits for it only up to max seconds, 8 by default, then takes the overlay off the page and warns in the console. If no script runs at all, the stylesheet lets the overlay go after twelve seconds.
Yes. The counter waits at ceiling, 90 by default, until the load event has fired, document.fonts is ready and every promise handed to wait() has settled, then runs to 100. The digits do not show every number: they roll through 000, 031, 067 and 100 while a hairline follows the load continuously.
Change data-preloader-min, the shortest time the counter takes to reach 100, 2.2 seconds by default. Lower and the loader is a flash where the digits barely roll, higher and it is a long, deliberate count. data-preloader-max caps how long it waits for a slow page before leaving anyway.
Yes, with data-preloader-once="true": only the first page of the browser session then shows the loader, where the default, false, plays it on every page. Load preloader.js right after the overlay, or in the head with defer, because the skip is decided when the engine runs.