Footer reveal
The footer is not at the end of the page, it is under it.
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 tutorialThree things belong to the page rather than to the component. The page above the footer needs a background of its own: the footer sits behind it the whole way down, and a transparent section is a window onto it. The footer shares its parent with that page. And no ancestor of the footer cuts its overflow withhidden,autoorscroll: useoverflow: clipthere, or the footer has nowhere to hold and simply follows the page. -
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-footer-reveal Goes on the footer and takes no value: leave the value field empty. It marks the element that holds under the page while the page lifts off it. The footer has to share its parent with the page above it. Without it on any element of the page, the embed marks the nearest footer itself. data-footer-reveal-page Marks the block above the footer, the sheet that lifts. It takes no value. The stylesheet puts it over the footer in the stacking order, and it needs a background of its own, or the footer shows through it. Without it anywhere on the page, the engine marks every block before the footer in their parent itself. [ Optional ] data-footer-reveal-wordmark Marks the name that comes out of a mask letter by letter, inside the footer. It takes no value. Plain text is split into one box per letter and keeps its text in an aria-label; a wordmark holding elements, an SVG logo for one, comes out in one piece. Without it nothing is split. [ Optional ] data-footer-reveal-columns Marks the block of the footer that trails the page and closes the gap as it leaves. It takes no value, and may be set on more than one block. Without it nothing trails. [ Optional ] data-footer-reveal-state Written by the engine on the footer: live within one screen of it, idle further away, still under reduced motion, flat when an ancestor cuts its overflow and the footer cannot hold. The stylesheet lets go of the hold on still and flat. Never written by hand. [ Optional ] data-footer-reveal-edge, data-footer-reveal-shade, data-footer-reveal-veil Written by the engine on the three blocks it adds: the edge, a block of no height just before the footer, which moves with the page and is what is watched and measured; the shadow inside it; and the veil, the last child of the footer, the shade it lies in while the page covers it. Never written by hand, and removed by destroy(). [ Optional ] data-footer-reveal-split, data-footer-reveal-word, data-footer-reveal-letter Written by the engine on a split wordmark, on the mask around each of its words and on the box around each letter. The stylesheet draws the mask on them. Never written by hand, and removed by destroy(). [ 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-footer-reveal-color #15100c Background of the footer, written on the footer itself so it wins over its class. Options: any CSS colour; taken out of the settings and the attributes, --footer-reveal-color on the footer takes over. data-footer-reveal-ink #f4ece0 Colour of the footer's text, the wordmark and the links included. Options: any CSS colour; taken out of the settings and the attributes, --footer-reveal-ink on the footer takes over. data-footer-reveal-shadow 0.35 Darkest the shadow of the page gets, 0 to 1: the shade over the whole footer while it is covered, which lifts with the page, and the band under the edge of the page, which grows as it lifts. data-footer-reveal-scale-from 1 Size the footer starts from while it is covered, in times its own size; it is back to 1 once uncovered. data-footer-reveal-parallax 0.12 How far below their place the columns start, as a share of the lift, a screen at most; they close the gap as the page leaves. data-footer-reveal-rise 1.3 Time one letter of the wordmark takes to come up from under its line, in seconds, on an expo out. data-footer-reveal-stagger 0.07 Delay between two letters of the wordmark, in seconds: each letter starts that much later than the one before. data-footer-reveal-smoothing 0.2 Share of the distance to the scroll the motion closes each frame, 0 to 1, counted at 60 frames a second.
Two files, no build step.
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
FooterReveal.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-footer-reveal-color #15100c Background of the footer, written on the footer itself so it wins over its class. Options: any CSS colour; taken out of the settings and the attributes, --footer-reveal-color on the footer takes over. data-footer-reveal-ink #f4ece0 Colour of the footer's text, the wordmark and the links included. Options: any CSS colour; taken out of the settings and the attributes, --footer-reveal-ink on the footer takes over. data-footer-reveal-shadow 0.35 Darkest the shadow of the page gets, 0 to 1: the shade over the whole footer while it is covered, which lifts with the page, and the band under the edge of the page, which grows as it lifts. At 0 no shadow at all, the page is a flat cut over a footer already lit Past 0.5 the covered footer is nearly lost in the dark, and the band under the edge is heavy on a light footer data-footer-reveal-scale-from 1 Size the footer starts from while it is covered, in times its own size; it is back to 1 once uncovered. Under 0.9 the footer visibly zooms in, and its text with it At 1 the footer does not change size data-footer-reveal-parallax 0.12 How far below their place the columns start, as a share of the lift, a screen at most; they close the gap as the page leaves. At 0 the columns do not move Past 0.4 the columns run into what sits under them while they travel data-footer-reveal-rise 1.3 Time one letter of the wordmark takes to come up from under its line, in seconds, on an expo out. Under 0.6 the letters snap into place Past 2 the name is still arriving long after the scroll has stopped data-footer-reveal-stagger 0.07 Delay between two letters of the wordmark, in seconds: each letter starts that much later than the one before. At 0 the whole name comes up as one piece Past 0.12 a long name takes seconds to finish data-footer-reveal-smoothing 0.2 Share of the distance to the scroll the motion closes each frame, 0 to 1, counted at 60 frames a second. Under 0.06 the motion floats behind the scroll At 1 the motion is the scroll, and a mouse wheel shows its steps -
Drive it from JavaScript
When the page grows after it loaded without a scroll or a resize, when the footer is built late, or when something else on the page follows the lift.const element = document.querySelector('[data-footer-reveal]'); const reveal = FooterReveal.mount(element); // the footer auto() mounted, or a new one reveal.progress(); // the lift, 0 covered, 1 uncovered reveal.refresh(); // after the page above changed height on its own // When the footer leaves the page: reveal.destroy(); // gives the wordmark back and clears everything it wrote
-
Fit it to your page
- Structure: the footer carries
data-footer-reveal, and the page above it is its sibling: both sit in the same parent, the page first. Inside the footer,data-footer-reveal-wordmarkmarks the name that comes out of the mask anddata-footer-reveal-columnsthe block that trails the page. Both are optional, and each may be used more than once. - The page above carries
data-footer-reveal-page. It needs a background of its own: the footer is behind it the whole time, and a page with no background shows it through. Without the mark anywhere on the page, the script marks every block before the footer in their parent itself, and says in the console when one of them has no background. - The wordmark is plain text. The script wraps each word in a mask and each letter in a box of its own, and keeps the text for a screen reader in an
aria-label. A wordmark holding elements instead, a logo drawn in SVG for one, is not split: it comes out of the mask in one piece. - One setting for every footer of the site, without opening the file: declare
window.FooterRevealSettings = { parallax: 0.3 }in a script placed beforefooter-reveal.js. The three levels, least specific first, areDEFAULTS, then that object, then adata-footer-reveal-*attribute on the footer itself (data-footer-reveal-parallax="0.3",data-footer-reveal-scale-from="0.92"). - The two colours are written inline on the footer, so they win over the class it wears. They can also come from the stylesheet: without a
coloror aninkin the attributes or in the settings block, the custom properties--footer-reveal-colorand--footer-reveal-inkon the footer are read, thenDEFAULTS. The engine writes its own copy ofDEFAULTSinto the Embed, so on a Webflow page the settings block, or an attribute on the footer, is the way to change them. - What the script writes:
data-footer-reveal-stateon the footer,livewithin one screen of it,idlefurther away,stillunder reduced motion,flatwhen the footer cannot hold. One empty block just before the footer,data-footer-reveal-edge, holding the shadow, and one inside the footer, last and out of the flow,data-footer-reveal-veil, the shade over it. The two colours, inline. And while the footer is near, the transforms of the motion, taken off again as soon as a part is back in its place and all at once when the footer is far. - **From JavaScript, beyond
mount,autoanddestroy**: the api carrieselement,settings,progress(), the lift from 0 to 1,refresh(), which measures again and draws where the scroll is, andframes(),isRunning()andisListening(), for a test that wants to see the loop sleep at rest and the listener leave when the footer is far. The effect emits no event. - **
destroy()leaves the page as it was**: the wordmark gets its own text node back, the edge is removed, the listeners and the observers are gone, and every attribute and inline style the script wrote is taken off. - A footer taller than the window, the usual case on a phone once the columns stack, holds by its head instead of its foot: its first screen is uncovered by the page, then the rest scrolls up as any page does, and a wordmark at its foot comes out of its mask as it enters the window. This is the stylesheet's doing,
top: 0andbottom: 0together, and nothing is measured for it. - Touch: the same motion, driven by the same scroll. The window height is read again each time the browser's bars come and go.
- **
prefers-reduced-motion**: the footer simply follows the page, name in place, nothing split and nothing listened to. The state readsstill, and turning the preference off brings the effect back without a reload. - 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.
- Structure: the footer carries
-
Avoid the pitfalls
- An ancestor that cuts its overflow stops the hold.
overflow: hidden,autoorscrollon any ancestor of the footer, a page wrapper for one, makes that ancestor the box the footer sticks to, and a box that does not scroll has nowhere to hold it. The script sees it, leaves the footer to follow the page, stateflat, and names the ancestor in the console. Useoverflow: clipon that ancestor instead, which cuts the same and stops nothing. Atransformon an ancestor is harmless here: the hold is sticky, not fixed. - The footer shares its parent with the page. A footer alone in its own wrapper has no room to hold in, and is left
flatthe same way. - The page needs a background. The footer is drawn behind it from the top of the page on. A transparent section in the page is a window onto the footer.
- The page scrolls in the window. A page that scrolls inside a box of its own, a smooth scroll wrapper with
overflow: auto, is the first pitfall again, and the reveal stays flat. - The footer cuts what passes its top and its foot (
overflow-y: clip), so a letter waiting under its line never adds its height to the scroll. A block of the footer set to stick out above or below it is cut too. - **While it is under its size, the footer carries a shadow of its own colour and a
clip-path**, inline, which fill the two slivers a smaller footer would leave at its sides. Abox-shadowor aclip-pathof your own on the footer is covered during the lift. A footer whose background is a picture shows those slivers in the plain colour: setscaleFromto 1. - The veil is the footer's last child, out of the flow, at
z-index: 10inside it. A block of the footer with a higher z-index of its own stays lit while the rest is in the shade. - Leave room under the columns. They start below their place and travel up to it; with a high
parallaxand nothing but a hairline between them and the next block, they cross it on the way. - **Settings handed to
mount()after the page loaded are dropped.**auto()mounts on DOM ready, andmount()on a footer that already carries the effect returns the existing one. A page-wide block goes inwindow.FooterRevealSettings, whichauto()reads itself. - Content that grows the page after it loaded, without a scroll or a resize, moves the edge without telling the script. Call
refresh()on the instance once it is in.
- An ancestor that cuts its overflow stops the hold.
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
Footer reveal is a scroll animation for Webflow and vanilla JavaScript, with no library. Also called footer reveal on scroll, sticky footer reveal or fixed footer reveal.
The footer is not at the end of the page, it is under it. It holds at the bottom of the window behind the page, and the last screen of the page lifts off it like a sheet: a shadow grows under the edge of the page, the footer comes out of the shade, its columns trail the page and close the gap as it leaves, and the name of the brand comes out of a mask letter by letter, each letter starting a moment after the one before it. The hold itself is CSS, position sticky, so it is drawn by the browser with the scroll and never lags it; the script only adds the motion on top, eased towards the scroll so a mouse wheel never chops it. It listens to the scroll only within one screen of the footer, draws no frame at rest, and needs no library. A footer taller than the window holds by its head, shows its first screen, then scrolls as any page does. With reduced motion, or with no script at all, the footer simply follows the page with its name in place. Not the gravity footer: nothing falls into this one, it is the page that moves.
Updated
Questions
Give the footer the custom attribute data-footer-reveal, with no value, and keep the page above it as its sibling: both in the same parent, the page first. That page block takes data-footer-reveal-page and needs a background of its own, because the footer sits behind it the whole time. Settings are more attributes on the footer, for instance data-footer-reveal-parallax="0.3".
Neither. The hold is CSS position: sticky, drawn by the browser with the scroll, and no library is loaded. The script only adds the motion on top, the shadow, the columns and the wordmark, and listens to the scroll only within one screen of the footer.
Most often an ancestor of the footer has overflow: hidden, auto or scroll, a page wrapper for instance, and a sticky footer cannot hold inside a box that does not scroll. The script then leaves the footer to follow the page, writes the state flat and names that ancestor in the console. Put overflow: clip on it instead, and check the footer is not alone in a wrapper of its own.
It holds by its head instead of its foot: the page uncovers the first screen of the footer, then the rest scrolls up as any page does. This is the usual case on a phone once the columns stack. The stylesheet does it with top: 0 and bottom: 0 together, and nothing is measured.
Set data-footer-reveal-parallax, 0.12 by default: how far below their place the columns marked data-footer-reveal-columns start, as a share of the lift. At 0 the columns do not move, and past 0.4 they run into what sits under them while they travel.