Page wipe
A sober page transition, between real pages.
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 tutorialThis code has to be on every page of the site, which an Embed on one page is not. Best place: Site settings, Custom code tab, Head code, the<style>and the scripts together (the field holds 50 000 characters, this is under 30 000), then publish: the page reached is under the sheet from its first paint. Otherwise keep the Embed, as a Component in your navbar. A second copy of the script on a page does nothing. The name is set in the typeface of the Body; one rule on.page-wipe-namegives it another. -
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-page-wipe Marks the element whose links are taken: the Body for every link of the page, or a menu for its links only. It takes no value: leave the value field empty. The settings go on the same element. Without it on any element of the page, the embed marks the Body. data-page-wipe-skip On a link, or on any block: the links inside are left to the browser, with no transition. It takes no value. [ Optional ] data-page-wipe-name On a link: the name written at the foot of the sheet, in place of the words of the link. Without it the link writes its own words, a link made of a picture its alt text, and a link whose words are too long the last part of its address. [ Optional ] data-page-wipe-state Written by the engine on the html element: idle at rest, leaving while the sheet comes in, covered until the browser changes page and while the sheet waits on the page reached, arriving while it lifts, frozen for a held picture, still under reduced motion. 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-page-wipe-background #15100c Colour of the sheet, and of the screen the next page starts under. Options: any CSS colour; a light one, with a dark color, for a sheet of paper over a dark site. data-page-wipe-color #f4ece0 Colour of the name written at the foot of the sheet. Options: any CSS colour that reads on background. data-page-wipe-from bottom Side of the window the sheet comes from; it leaves by the opposite side, going on the way it came. Options: bottom rises, top falls, left and right cross the window sideways. data-page-wipe-cover 0.8 Time the sheet takes to cover the page, from the click, in seconds. data-page-wipe-hold 0.25 Time the page stays covered before the browser is sent on, in seconds; the name finishes rising during it. data-page-wipe-reveal 0.9 Time the sheet takes to uncover the page reached, in seconds. data-page-wipe-patience 4 Longest the sheet waits, in seconds: on the page being left, for the browser to leave; on the page reached, for its typefaces and the pictures in the window, counted from the start of the navigation. data-page-wipe-label true Whether the name of the page being reached is written on the sheet. Options: true writes it; false leaves the sheet bare. data-page-wipe-label-size clamp(44px, 9vw, 136px) Size of the name, any CSS unit; a name too long for the window is set smaller, on one line. data-page-wipe-prefetch true Whether the next page is asked for while the sheet is still coming in, so it is there when the browser goes. Options: true fetches it, through speculation rules or a prefetch link, except an address carrying a query and except on a site whose own page took over 0.8 s to answer; false lets the browser fetch it when it goes. data-page-wipe-links a[href] CSS selector of the links the transition takes, among those inside the marked element. Options: a[href] takes every link; a narrower selector, nav a for one, takes only those.
Two files and their pictures (1), 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
PageWipe.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-page-wipe-background #15100c Colour of the sheet, and of the screen the next page starts under. Options: any CSS colour; a light one, with a dark color, for a sheet of paper over a dark site. data-page-wipe-color #f4ece0 Colour of the name written at the foot of the sheet. Options: any CSS colour that reads on background. data-page-wipe-from bottom Side of the window the sheet comes from; it leaves by the opposite side, going on the way it came. Options: bottom rises, top falls, left and right cross the window sideways. data-page-wipe-cover 0.8 Time the sheet takes to cover the page, from the click, in seconds. A snap, the click answered at once and the gesture lost A slow rise, which makes the site feel slow on every click data-page-wipe-hold 0.25 Time the page stays covered before the browser is sent on, in seconds; the name finishes rising during it. At 0 the page changes as the sheet lands, the name still rising A pause on the name before every page data-page-wipe-reveal 0.9 Time the sheet takes to uncover the page reached, in seconds. The page is simply there A long uncovering that keeps the content from the reader data-page-wipe-patience 4 Longest the sheet waits, in seconds: on the page being left, for the browser to leave; on the page reached, for its typefaces and the pictures in the window, counted from the start of the navigation. The sheet withdraws from a slow page early, which then arrives plain, and lifts on pictures still loading A slow page keeps the visitor longer under the sheet data-page-wipe-label true Whether the name of the page being reached is written on the sheet. Options: true writes it; false leaves the sheet bare. data-page-wipe-label-size clamp(44px, 9vw, 136px) Size of the name, any CSS unit; a name too long for the window is set smaller, on one line. A caption at the foot of the sheet A title that fills the width data-page-wipe-prefetch true Whether the next page is asked for while the sheet is still coming in, so it is there when the browser goes. Options: true fetches it, through speculation rules or a prefetch link, except an address carrying a query and except on a site whose own page took over 0.8 s to answer; false lets the browser fetch it when it goes. data-page-wipe-links a[href] CSS selector of the links the transition takes, among those inside the marked element. Options: a[href] takes every link; a narrower selector, nav a for one, takes only those. -
Drive it from JavaScript
When a page change comes from your own code, a form sent or a card clicked, or when something on the page should start as the sheet lifts.const element = document.querySelector('[data-page-wipe]'); element.addEventListener('page-wipe:covered', (e) => { console.log(e.detail); }); const wipe = PageWipe.mount(element); // the instance auto() made for the marked element wipe.play({ name: 'Sorted' }); // the sheet covers the page and uncovers it in place // wipe.leave('/order-desk', { name: 'Order desk' }); // covers, writes the name, then goes to that address document.addEventListener('page-wipe:arrive', () => { /* the sheet starts to lift */ }); // When the links go back to the browser: wipe.destroy(); // removes the layer and the listener
-
Fit it to your page
- Structure: one element marked
data-page-wipe, and every link inside it to another page of the same site is taken. Put it on<body>for the whole page, or on a menu for its links only. The effect reads no class and no child, and adds one block to the page while a pass is on screen,page-wipe-layer, removed when the pass is over. - Every page loads the two files. The page being left covers itself and writes the pass; the page reached reads it and lifts the sheet. A page that does not load the script is reached with a plain navigation, and nothing is left covered.
- **Load them in the
<head>**, the stylesheet first, the script withoutdefer: the script reads the pass as it runs and builds the sheet at once, so the page reached never shows before the sheet is on it. - Which links are taken: a plain left click, or Enter, on a link to another page of the same origin. Left to the browser: another site,
target="_blank"or any other frame,download, a file that is not a page (a PDF, a picture),mailto:andtel:, an anchor of the page itself, a click with Cmd, Ctrl, Shift or Alt, a click another script already cancelled, and any link markeddata-page-wipe-skipor inside an element marked so. Thelinkssetting narrows it further with a selector. - The name written is the words of the link as they are written, 28 characters at most.
data-page-wipe-name="Order desk"on the link gives another one. A link made of a picture writes itsaria-labelor thealtof the picture; a link whose words run longer than a title writes the last part of its address,/order-deskas "Order desk"; a link to the home page with no words writes nothing, and the sheet is bare. - The typeface of the name is the one of the marked element, read as the link is clicked. One rule on
.page-wipe-namegives another, with its weight and its spacing: the rules of the stylesheet sit in:where(). - One setting for every page, without opening the file: declare
window.PageWipeSettings = { from: 'left', background: '#f4ece0', color: '#15100c' }in a script placed beforepage-wipe.js. The three levels, least specific first, areDEFAULTS, then that object, then adata-page-wipe-*attribute on the marked element (data-page-wipe-cover="0.6"). - What the script writes:
data-page-wipe-stateon<html>,idleat rest,leavingwhile the sheet comes in,covereduntil the browser changes page and on the page reached while the sheet waits,arrivingwhile it lifts,frozenfor a held picture,stillunder reduced motion. A rule of the page can read it, to keep an entrance of its own for the moment the sheet lifts. Nothing else: no inline size, no class. - Events, all bubbling, on the marked element (on
<html>for a page reached before any element is mounted):page-wipe:leaveas a taken link is clicked, with{ url, name }, andpreventDefault()on it leaves the click to the browser;page-wipe:coveredjust before the browser is sent on;page-wipe:arriveon the page reached as the sheet starts to lift, with{ name };page-wipe:doneonce the sheet is gone. - **From JavaScript, beyond
mount,autoanddestroy**: the api carrieselement,settings,state(),leave(url, { name })which plays the pass and goes to an address as a click would,play({ name })which covers and uncovers in place with no page change, andfreeze()andunfreeze(), which hold the sheet partway as a still picture, for a thumbnail. - **
destroy()leaves the page as it was**: the layer removed, the click listener gone, the state attribute taken off with the last element. The links go back to the browser. - Keyboard and screen readers: Enter on a focused link is taken like a click. The sheet is hidden from assistive technology and moves no focus; the page reached is a page the browser loaded, so its title is announced and the focus starts at its top, as on any navigation. Escape while the sheet is coming in or waiting calls the page off and gives the page back.
- Back and forward: they are the browser's own moves and play nothing. A page the browser kept in memory, left under the sheet, is given back at once as it was before the click; a page it loads again arrives plain, since the pass was already used.
- A slow page: the sheet stays on the page being left until the browser leaves it, then on the page reached until its typefaces and the pictures in the window are there,
patienceseconds at most in all. A page that never comes, a link that turned out to be a download, gets the sheet withdrawn the way it came afterpatience. - Without JavaScript nothing is ever covered: the sheet only exists once the script has built it, and the links are plain links.
- **
prefers-reduced-motion**: nothing is taken, the links navigate as they always did, and a pass written by the page before is dropped. The state readsstill, and turning the preference off brings the transition back without a reload. - Inside a frame: a page shown in an
<iframe>replaces itself instead of adding an entry, so Back leaves the page holding the frame rather than stepping through the framed pages. - The words and the photographs of the markup are the preview's, and they ship as they are. Put the user's own in their place whenever they want, and point the links at their own pages.
- 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.
- On Webflow, the code goes on every page, once. Two places. The Embed of the paste runs on the page it is pasted on, and a transition needs both ends. Webflow opens custom code, the Code Embed element included, to a Core, Growth, Agency or Freelancer Workspace, or to a site with an active Site plan; a site on the free Starter Workspace with no Site plan has neither place.
- Place one, the site's code. Site settings, Custom code tab, Head code: paste the whole code of the Embed there, the settings, the
<style>and the two<script>, click Save changes, publish, then delete the Embed from the page. The field holds 50 000 characters and the code is under 30 000. It reaches every page of the site, the CMS pages and the 404 with them, and it is the only place that puts the page reached under the sheet from its first paint. Its limits: the field is one for the whole site, shared with whatever else lives there, and nothing shows before the site is published. - Place two, a shared component. Select the Embed, turn it into a Component, and put that Component in something every page carries, the navbar for one, as high in the page as it goes. Nothing leaves the Designer. Its limits: a page that does not carry the Component is left and reached with a plain navigation; the script runs when the browser reaches the Embed in the body, so a heavy page may show for an instant before the sheet is on it; and the Designer runs no script, the transition plays in preview and on the published site only.
- Either way every internal link of the site is taken, with nothing to mark: the code marks the Body when no element is marked, and a link marked
data-page-wipe-skipis left out. The code may be on a page twice, the Embed and the site's code: the second copy does nothing. - Webflow interactions set on page load start under the sheet. They play while the page is still covered. For an entrance that should wait for the sheet, start it on
page-wipe:arrive, or key its rule onhtml[data-page-wipe-state="idle"].
- Structure: one element marked
-
Avoid the pitfalls
- A script loaded late lets the page flash. With the script at the end of the body, deferred, or in an Embed far down the page, the page reached may paint once before the sheet is on it. The transition still plays; the flash is the cost. Put the two files in the head, or the Embed first in the body.
- **
prefetchasks for a page before the browser goes to it.** A link whose address does something when it is fetched, signs out, adds to a basket, must not be fetched twice: an address with a query is never prefetched, and any other such link is markeddata-page-wipe-skipor the setting is turned off. - **One page far slower than the rest of the site loses with
prefetch.** The engine only knows how fast the page it is on answered. A page that takes over about two seconds on a site that is otherwise quick is prefetched, then asked for again by Chrome, and takes twice as long to come: mark the links to itdata-page-wipe-skip, or turnprefetchoff. - **Only pictures written as
<img>are waited for.** A background image, a video or a canvas that fills the first screen may still be loading as the sheet lifts. Listen topage-wipe:arriveto start what the page plays on entry. - **Settings handed to
mount()after the page loaded are dropped.**auto()mounts on DOM ready, andmount()on an element that already carries the effect returns the existing one. A page-wide block goes inwindow.PageWipeSettings, whichauto()reads itself. - The page reached takes its sheet from the page before. Colours, side and times travel in the pass, so two pages with two settings change sheet from one click to the next, never in the middle of a passage. Keep one block for the site.
- A router has nothing to take. In a single page app the links never reload the page. Mark the router's links
data-page-wipe-skipand callplay()around the route change, or use the router's own transition. - A site with view transitions of its own (
@view-transition { navigation: auto; }) keeps them for Back, Forward and the links left to the browser; on a link the sheet takes, the browser's transition is skipped, so the two never play at once. - The layer sits over everything, at the top of the stacking order (
z-index: 2147483000), and takes the pointer while a pass is on screen, so nothing is clicked twice. A cookie banner or a chat bubble is covered with the rest. - sessionStorage blocked (some private modes, an embedded frame with storage partitioned off): the page being left still covers itself, the page reached arrives plain.
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
Page wipe is a page transition for Webflow and vanilla JavaScript, with no library. Also called page wipe transition, overlay page transition or page transition without barba.
A sober page transition, between real pages. A click on a link to another page of the site brings one flat sheet of ink over the window, from the bottom by default, starting gently and settling; its soft shadow falls on the page along the edge that travels. The name of the page being reached rises through a one line window at the foot of the sheet, large, in the typeface of the site. The browser then goes to the next page for real, which starts under the same sheet from its first paint, the name standing where it stood. The sheet waits there for what the visitor will see first, the typefaces and the pictures in the window, then the name leaves upwards and the sheet goes on the way it came, uncovering the page. There is no router and no library: the pass between the two pages, the name, the colours, the side and the times, is kept in sessionStorage for the time of the navigation, and the next page is fetched while the sheet is still coming in. It works wherever links lead to real pages, a Webflow site for one. Links to another site, to a new tab, to a file, mailto: and tel:, and anchors of the page are left to the browser, like any click with a modifier key; one attribute on a link leaves it out. A page that does not answer is given back after a few seconds, Escape gives it back at once, and Back never finds a page left under the sheet. Under reduced motion nothing is taken: the links navigate as they always did.
Updated
Questions
Paste the component, then move the code of its Embed to Site settings, Custom code, Head code and publish, or turn the Embed into a Component placed in your navbar: the code has to be on every page, since a transition has two ends. It marks the Body by itself, so every link to another page of the site is taken. Settings are custom attributes on the Body, for instance data-page-wipe-from="left".
No. The browser loads the next page for real, so Webflow interactions, forms and analytics run as on any page. The pass between the two pages (the name, the colours, the side and the times) is kept in sessionStorage for the time of the navigation, and the sheet is moved with the Web Animations API.
Back and Forward play nothing, and a page the browser kept in memory under the sheet is given back at once as it was before the click. On a slow page the sheet waits for the typefaces and the pictures in the window, data-page-wipe-patience seconds at most (4 by default); a page that never comes gets the sheet withdrawn the way it came, and Escape gives the page back at once.
Mark the link, or any block around it, data-page-wipe-skip. Links to another site, target="_blank", download, files, mailto: and tel:, anchors of the page itself and clicks with Cmd, Ctrl, Shift or Alt are already left to the browser.
Yes. data-page-wipe-from takes bottom, top, left or right; data-page-wipe-cover, data-page-wipe-hold and data-page-wipe-reveal are the three times in seconds (0.8, 0.25 and 0.9 by default). The name written is the words of the link, data-page-wipe-name="Order desk" on the link gives another one, and data-page-wipe-label="false" leaves the sheet bare.