Greeting loader
A loading screen that says hello. A panel of ink covers the page, and greetings run through its middle, Hello, Bonjour, Hola, Ciao, Hallo, Olá, each one rising out of a one line window letter by letter, leaning and settling on an expo out, then leaving upwards on a power3 in while the next one is already rising under it: the two overlap, never a hard swap.
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-greeting-loader Marks the overlay, an empty block that covers the page while the greetings run. It takes no value: leave the value field empty. Keep it first in the Body, so it covers the page from the first paint. The script builds the panel, the greetings and the dot inside it. data-greeting-loader-target On the headline of the page, an h1 or any element that holds text: where the last word lands. Its own text closes the run, in its own face, and the word that flies becomes it. Keep it in the first screen. Without it the last greeting leaves with the panel. [ Optional ] data-greeting-loader-state Written by the engine on the overlay: waiting, greeting, handover, then done; fading under reduced motion, skipped on a later page of the session, off once destroyed. Never written by hand. [ 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-greeting-loader-color #15100c Colour of the panel that covers the page. Options: any CSS colour. data-greeting-loader-ink #f4ece0 Colour of the greetings on the panel, and of the last word for as long as the panel is behind it. Options: any CSS colour that reads on color. data-greeting-loader-dot #ffab1a Colour of the dot before each greeting, the accent. Options: any CSS colour. data-greeting-loader-words Hello, Bonjour, Hola, Ciao, Hallo, Olá The greetings, in play order, commas between them (bars, Hello | Hi, you, when a greeting holds a comma). The headline's own text is added at the end unless the list already ends on it. Options: any list of short words; fewer for a short loader, more for a long one. data-greeting-loader-size clamp(40px, 6.2vw, 104px) Size of the greetings on the panel, any CSS unit. The last word is shown at this size before it grows to the headline's. data-greeting-loader-interval 0.62 Time from the first greeting starting to rise to the second one doing so, in seconds. Every next beat is accel times the one before, never under 0.2 s. data-greeting-loader-accel 0.78 Share of the previous beat each next greeting keeps, the ratio of the rhythm. data-greeting-loader-enter expo.out Curve a greeting rises in on, a GSAP ease name; the last word rises on it too. Options: expo.out (fast out of the window, long soft landing), power4.out, power3.out (rounder), back.out(1.4) (a slight overshoot). data-greeting-loader-leave power3.in Curve a greeting leaves on, a GSAP ease name. Options: power3.in (holds, then goes), expo.in (holds longer, leaves sharper), power2.in (softer). data-greeting-loader-stagger 0.022 Delay between two letters of a greeting as it rises, in seconds; the exit uses half of it, and both shrink as the rhythm speeds up. data-greeting-loader-tilt 7 Lean of a letter under the window, in degrees, turned from its lower left corner and settled at 0 as it lands. data-greeting-loader-hold 0.4 Pause on the last word once it has settled, before it flies, in seconds. data-greeting-loader-lift 1.2 How long the panel takes to lift and the last word to reach the headline, in seconds; both share one expo in-out. data-greeting-loader-lip clamp(36px, 7vw, 120px) Depth of the curve of the panel's lower edge at its fullest, any CSS unit. data-greeting-loader-max 6 Longest time, counted from the start, the last word waits for the page to be loaded before the panel lifts anyway, in seconds. data-greeting-loader-once true Whether the loader plays once per browser session and is skipped on the next pages. Options: true (the first page of the session only), false (every load).
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
GreetingLoader.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-greeting-loader-color #15100c Colour of the panel that covers the page. Options: any CSS colour. data-greeting-loader-ink #f4ece0 Colour of the greetings on the panel, and of the last word for as long as the panel is behind it. Options: any CSS colour that reads on color. data-greeting-loader-dot #ffab1a Colour of the dot before each greeting, the accent. Options: any CSS colour. data-greeting-loader-words Hello, Bonjour, Hola, Ciao, Hallo, Olá The greetings, in play order, commas between them (bars, Hello | Hi, you, when a greeting holds a comma). The headline's own text is added at the end unless the list already ends on it. Options: any list of short words; fewer for a short loader, more for a long one. data-greeting-loader-size clamp(40px, 6.2vw, 104px) Size of the greetings on the panel, any CSS unit. The last word is shown at this size before it grows to the headline's. Small words in a large panel, a quiet opening Large words, and a shorter growth to the headline data-greeting-loader-interval 0.62 Time from the first greeting starting to rise to the second one doing so, in seconds. Every next beat is accel times the one before, never under 0.2 s. A fast run from the first word A slow, deliberate start data-greeting-loader-accel 0.78 Share of the previous beat each next greeting keeps, the ratio of the rhythm. Under 0.6 the run reaches its fastest beat after three words At 1 every greeting keeps the beat of the first, no acceleration data-greeting-loader-enter expo.out Curve a greeting rises in on, a GSAP ease name; the last word rises on it too. Options: expo.out (fast out of the window, long soft landing), power4.out, power3.out (rounder), back.out(1.4) (a slight overshoot). data-greeting-loader-leave power3.in Curve a greeting leaves on, a GSAP ease name. Options: power3.in (holds, then goes), expo.in (holds longer, leaves sharper), power2.in (softer). data-greeting-loader-stagger 0.022 Delay between two letters of a greeting as it rises, in seconds; the exit uses half of it, and both shrink as the rhythm speeds up. At 0 the word moves in one block Past 0.04 the word unrolls like a wave and the long greetings arrive late data-greeting-loader-tilt 7 Lean of a letter under the window, in degrees, turned from its lower left corner and settled at 0 as it lands. At 0 the letters rise straight Past 12 the lean reads as a tumble data-greeting-loader-hold 0.4 Pause on the last word once it has settled, before it flies, in seconds. The word leaves almost as it lands A beat on the headline word data-greeting-loader-lift 1.2 How long the panel takes to lift and the last word to reach the headline, in seconds; both share one expo in-out. A snap A slow, heavy lift, the word drifting to its place data-greeting-loader-lip clamp(36px, 7vw, 120px) Depth of the curve of the panel's lower edge at its fullest, any CSS unit. At 0 a flat edge, a plain wipe A deep bowl, the corners of the page showing well before its middle data-greeting-loader-max 6 Longest time, counted from the start, the last word waits for the page to be loaded before the panel lifts anyway, in seconds. A slow page is uncovered half loaded The visitor may be held on the last word for a long time data-greeting-loader-once true Whether the loader plays once per browser session and is skipped on the next pages. Options: true (the first page of the session only), false (every load). -
Drive it from JavaScript
When the page plays something of its own as the panel lifts, or when the loader has to be played again without a reload.const element = document.querySelector('[data-greeting-loader]'); element.addEventListener('greeting-loader:handover', (e) => { console.log(e.detail); }); const loader = GreetingLoader.mount(element); // the instance, playing as soon as GSAP is there, or skipped element.addEventListener('greeting-loader:done', () => { // the headline is in place, the page is free }); loader.replay(); // plays it again, whatever the session says // When the loader has to go for good: loader.destroy(); // gives the scroll and the headline back, leaves the overlay empty and hidden
-
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. The overlay stays empty.
- Mark the headline with
data-greeting-loader-target: anh1, or any element that holds text, aspaninside a heading included. Its text is the last word of the run, so the list of greetings does not have to end on it. The greetings take the face, the weight and the letter spacing of that headline, so the last swap does not change typeface; their size issize. - The headline should be in the first screen. The word flies to wherever the headline is; a headline further down the page sends it out of the window.
- One setting for every page, without opening the file: declare
window.GreetingLoaderSettings = { hold: 1 }in a script placed beforegreeting-loader.js. The three levels, least specific first, areDEFAULTS, then that object, then adata-greeting-loader-*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 hides the overlay and warns in the console, since a loader must never be the reason a page cannot be read. The order of the two script tags does not matter. - Once per session. The first page of a visit plays the loader and writes
greeting-loader:playedinsessionStorage; on the next pages the engine builds nothing and hides the overlay as soon as it runs. While you work on a page, setdata-greeting-loader-once="false"so every reload plays. - The last word waits for the page. Once its pause is over, the panel lifts when the
loadevent has fired, ormaxseconds after the start, whichever comes first. - What the script writes:
data-greeting-loader-stateon the overlay,waitinguntil the faces are in,greetingduring the run,handoverwhile the panel lifts, thendone;fadingunder reduced motion,skippedon a later page of the session,offonce destroyed. Three custom properties on the overlay,--greeting-loader-color,--greeting-loader-inkand--greeting-loader-dot.data-greeting-loader-lockonhtmlwhile the scroll is held, anddata-greeting-loader-awayon the headline while its copy travels; both are taken off at the end, and everything built inside the overlay with them. The custom properties stay untildestroy(). - Events, all bubbling from the overlay, with
{ loader }asevent.detail:greeting-loader:startat the start of a play,greeting-loader:wordat each greeting (withindex,wordandlast),greeting-loader:handoverwhen the panel starts to lift and the word to fly, the cue for anything else the page plays, andgreeting-loader:doneonce the overlay is out (withskipped: truewhen it never played). - From JavaScript:
GreetingLoader.mount(element, overrides)mounts and plays at once. The api carriesplay()(a new play if the loader is idle, respectingonce),replay()(a new play whatever the session says),skip()(straight to done),state(),timeline()(the GSAP timeline of the play under way, null outside one),isRunning(),settingsanddestroy().GreetingLoader.replay()plays every mounted overlay again, which is how the library's cards replay the preview. - **
destroy()** kills the timeline, gives the scroll and the headline back, removes what the script built, its listeners and its custom properties. The overlay is left empty and hidden (data-greeting-loader-state="off"): put back as it was written, it would cover the page. - Touch: nothing to touch, the loader plays the same on every device.
- **
prefers-reduced-motion**: no greetings and no flight. The headline is already in place under the panel, and the panel fades out in 0.3 s. - Without any script, the stylesheet lets the overlay go after eight seconds: a loader must never be the reason a page cannot be read.
- The words of the markup are the preview's, and they ship as they are. Put the user's own words in their place whenever they want.
- 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
- The panel shows for an instant on the later pages of a session when the script loads late: the overlay is painted by the stylesheet, and only the script knows the session has seen it. To rule it out, put this line in the head, before the stylesheet paints anything:
<script>try{if(sessionStorage.getItem('greeting-loader:played'))document.documentElement.setAttribute('data-greeting-loader-seen','')}catch(e){}</script>. The stylesheet hides the overlay under that attribute. - The first paint takes the default colour.
colorreaches the overlay when the script runs. For a panel that is not ink from the very first frame, also write--greeting-loader-coloron the overlay in your own stylesheet. - Do not hide the headline yourself. The engine hides it while its copy travels and shows it again on landing; a headline hidden by the page stays hidden on a session where the loader is skipped.
- An entrance animation on the headline fights the hand-over. The copy lands on the headline as it is measured at that moment: a headline that is still moving, split into animated letters or faded in by another script will not be under it. Start such an animation on
greeting-loader:done. - A transform on an ancestor of the headline is fine as long as it is at rest; the copy is laid out flat, so a rotated or scaled headline is not matched.
- The copy takes the typography, not the decoration: font, size, weight, spacing, line height, alignment, case and colour. A gradient text, a background, a text shadow or a stroke on the headline do not travel, and appear on landing.
- **Settings handed to
mount()after the page loaded are dropped.** The script mounts as soon as it runs, andmount()on an overlay that already carries the loader returns the existing one. A page-wide block goes inwindow.GreetingLoaderSettings, declared before the engine. - In React, Vue or any framework that owns the DOM, render the overlay in the first HTML the server sends and mount it once in a top-level effect; the engine never removes the overlay, it hides it by its state attribute. Call
destroy()in the cleanup.
- The panel shows for an instant on the later pages of a session when the script loads late: the overlay is painted by the stylesheet, and only the script knows the session has seen it. To rule it out, put this line in the head, before the stylesheet paints anything:
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
Greeting loader is a page transition for Webflow and vanilla JavaScript, built on GSAP 3. Also called hello preloader, multilingual greeting preloader or words preloader.
A loading screen that says hello. A panel of ink covers the page, and greetings run through its middle, Hello, Bonjour, Hola, Ciao, Hallo, Olá, each one rising out of a one line window letter by letter, leaning and settling on an expo out, then leaving upwards on a power3 in while the next one is already rising under it: the two overlap, never a hard swap. A dot sits before each word and travels to the front of the next one on a curve of its own. The rhythm speeds up, and every greeting still lands and is read. The run closes on the headline of the page itself, set in its own face. That word stays, the dot leaves, and the word flies from the middle of the panel to the place of the headline, growing to its size, while the panel lifts off the page: its lower edge is one SVG curve, pulled taut as it crosses the screen and flat again as it leaves, on an expo in-out. Where the panel still covers it the word is the ink of the panel, where the page shows it is already the colour of the headline, so the lip changes its colour as it passes. On landing the copy is the headline, pixel for pixel, and gives way to it. Once per browser session, and never on the next pages; the last word waits for the page to be loaded, within a longest time. Under reduced motion there are no greetings: the headline is in place and the panel fades out. The whole play is one GSAP timeline.
Updated
Questions
Paste it with the overlay, an empty block marked data-greeting-loader, as the first element of the page, then mark your headline with data-greeting-loader-target: that is where the last word lands. Settings are custom attributes on the overlay, for instance data-greeting-loader-hold="1".
The GSAP 3 core with no plugins; the embed loads 3.12.5 from jsDelivr, and the whole play is one GSAP timeline. On Webflow the engine waits for the copy of GSAP that Webflow injects instead of loading a second one. If GSAP never arrives, the overlay is hidden after max seconds, 6 by default, with a warning in the console.
Write them in data-greeting-loader-words, in play order with commas between them; the default is Hello, Bonjour, Hola, Ciao, Hallo, Olá. The text of the headline is added at the end unless the list already ends on it. Fewer words make a shorter loader, more words a longer one.
Lower data-greeting-loader-interval, the time in seconds between the first greeting and the second, 0.62 by default. Each next beat is data-greeting-loader-accel times the one before, 0.78 by default, and never under 0.2 seconds. At an accel of 1 every greeting keeps the beat of the first.
No. It plays on the first page of a browser session, writes greeting-loader:played in sessionStorage, and is skipped on the next pages. Set data-greeting-loader-once="false" to play it on every load, which helps while you work on a page.