Pixel sweep reveal
Headings and paragraphs write themselves in, one block of pixels at a time, as they reach the viewport.
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 tutorial -
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 -
Use it on your own elements
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 element 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-pixel-reveal="right" the front travels from left to right, and this is what an empty value does data-pixel-reveal="left" it travels the other way data-pixel-reveal="down" it travels from the top down data-pixel-reveal="up" it travels from the bottom up data-pixel-reveal="read" it follows the lines the way they are read, one after the other data-pixel-reveal-group On a box, sweeps every heading, paragraph, list item, quote and text block inside it, which is one attribute instead of one per element. Its value is the direction of the whole group, and an element marked on its own keeps its own settings. [ Optional ] The value is optional: left empty, the block sweeps right. The direction is a setting of the table below as well, and the value written here wins over it.SettingsEvery setting is a custom attribute too, added the same way and on the same element. An attribute always wins over the defaults written in the code.Attribute Default What it is data-accent auto Tint of the leading edge of the sweep. Options: any CSS colour, or auto to take --pixel-reveal-accent, then the page's --accent. data-cell auto Side of one square block the text is written in, in px, or auto to take it from the font size. data-cell-ratio 0.4 How big an auto block is next to the text it covers, as a share of the font size. data-cell-min 8 The finest grain small text is allowed, floor of an auto block in px. data-cell-max 26 The coarsest grain a display heading is allowed, ceiling of an auto block in px. data-sweep 0.62 How much the order of the blocks follows the direction rather than chance, 0 to 1. data-duration 0.9 How long one block of text takes to write itself in full, in seconds. data-front 0.14 How long a cell stays lit by the accent before cooling to the text colour, in seconds. data-front-intensity 1 Strength of that coloured light, 0 to 1. data-flat 1 How solid the lit squares are, 0 to 1. data-stagger 0.11 Delay between two blocks that came into view together, in seconds. data-trigger 0.86 How far into the screen a block has to come before it starts, as a fraction of the viewport height. data-direction right Which way the front travels across a block. Options: right, left, down, up, or read, which follows the lines as they are read.
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
PixelReveal.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-accent auto Tint of the leading edge of the sweep. Options: any CSS colour, or auto to take --pixel-reveal-accent, then the page's --accent. data-cell auto Side of one square block the text is written in, in px, or auto to take it from the font size. A fine grain, more blocks to draw Chunky blocks, the words read later data-cell-ratio 0.4 How big an auto block is next to the text it covers, as a share of the font size. A fine grain on every size of text Display type turns into a mosaic data-cell-min 8 The finest grain small text is allowed, floor of an auto block in px. Captions keep a very fine grain Even small text shows visible blocks data-cell-max 26 The coarsest grain a display heading is allowed, ceiling of an auto block in px. Big titles stay finely grained A headline is drawn in a handful of squares data-sweep 0.62 How much the order of the blocks follows the direction rather than chance, 0 to 1. The blocks light up at random A clean front crossing the text data-duration 0.9 How long one block of text takes to write itself in full, in seconds. The text snaps in A slow write-on data-front 0.14 How long a cell stays lit by the accent before cooling to the text colour, in seconds. Almost no coloured edge A wide coloured band leads the sweep data-front-intensity 1 Strength of that coloured light, 0 to 1. The accent is only a hint The leading edge is full accent data-flat 1 How solid the lit squares are, 0 to 1. A light that fades behind the front At 1 real squares of flat colour, which then cool into the text data-stagger 0.11 Delay between two blocks that came into view together, in seconds. The whole section writes itself at once The paragraphs arrive one after the other data-trigger 0.86 How far into the screen a block has to come before it starts, as a fraction of the viewport height. It waits until it has climbed higher up the screen It starts as it enters at the bottom data-direction right Which way the front travels across a block. Options: right, left, down, up, or read, which follows the lines as they are read. -
Drive it from JavaScript
When text arrives after the page has loaded, when a block has to sweep in without waiting for the viewport, or when you want to react to a sweep.const element = document.querySelector('[data-pixel-reveal]'); element.addEventListener('pixelreveal:done', (e) => { console.log(e.detail); }); const reveal = PixelReveal.mount(element); // reduced motion included: the text is simply there, and done still fires reveal.reveal(); // starts the sweep without waiting for the viewport // Text added after the page loaded: mount everything marked inside it. PixelReveal.auto(document.body); // When the block leaves the page: PixelReveal.destroy(element); // stops the sweep and hands the text back
-
Fit it to your page
- One setting for every block of the page, without opening the file: declare
window.PixelRevealSettings = { duration: 1.2 }in a script placed beforepixel-reveal.js. The four levels, least specific first, areDEFAULTS, then that object, then adata-*attribute on thedata-pixel-reveal-groupbox the element sits in, then adata-*attribute on the element itself, so one heading can still differ from its section and from the page. - The direction has a shortcut: it is written as the value of the marking attribute,
data-pixel-reveal="down", which is the shortest thing to type in the Webflow Settings panel.data-directionworks too, and the value ofdata-pixel-reveal-group="down"sets it for a whole group. - Mark a whole container, or one block at a time.
data-pixel-reveal-groupon a section picks up every heading, paragraph, list item, quote and text block inside it, which is one action in the Webflow Settings panel instead of one per element.data-pixel-revealon a single element does just that element, and is the way to give one block its own direction. The two can be mixed: inside a group, an element that carries its own attribute keeps its own settings. - A setting written on the group box is read by every block inside it.
data-duration="1.4"on thedata-pixel-reveal-groupsection slows the whole section down, and adata-durationon one of its paragraphs still wins for that paragraph. The attribute is the key in kebab-case, as everywhere:data-cell-ratio,data-front-intensity. - A group never reveals a wrapper whole: a
divonly counts when it holds text itself and holds no other candidate inside it, which is what a Webflow Text Block looks like. Adivwrapping three paragraphs is skipped and its paragraphs are taken instead. - The effect is a scroll reveal. Anything already above the trigger line when the page loads goes off immediately. A block too close to the end of the page to ever reach that line, the footer for one, starts once the page is scrolled as far as it goes; on a page too short to scroll, whatever is on screen starts at once.
- The colour a block settles on is read from its computed style when its sweep starts, and again if its box changes during the sweep, so a block coloured by your CSS keeps its colour even when the classes land after the script. Nothing in the mechanics of
pixel-reveal.csssets a font, a size or a colour: the text keeps the page's own typography from end to end. The look after the line "look, left out of the Webflow Embed" only dresses the pixel-reveal-* classes of the preview. staggeris global, not per section: two blocks entering the viewport at the same time always separate, whichever part of the page they come from.- **From JavaScript, beyond
mount,reveal,autoanddestroy**: the apiPixelReveal.mount(el, overrides)returns also carrieselement,settingsandfreeze(progress). Each element emitspixelreveal:startas well aspixelreveal:done, both bubbling, with{ element, direction }asevent.detail. - **Under
prefers-reduced-motionthere is no sweep**, and nothing else changes for the page:mount()returns the same api, the text is never made transparent, and each block still emitspixelreveal:startthenpixelreveal:done, back to back, when it is reached or whenreveal()is called.freeze()does nothing, since there is no painted state to hold. freeze(progress)takes a share of the sweep, 0 to 1, and leaves the block there: the cells up to that point are written, the accent front sits on the last of them, and nothing asks for another frame. It works on a block that has already finished too, which is how a still image of the effect is taken without racing the animation, andreveal()puts the block back in the queue and replays it.PixelReveal.freeze(el, progress)does the same from the element. A page that only wants the reveal never calls it.- The words of the markup are the preview's, and they ship as they are: pasted, the page reads the way the preview did. Put the user's own content in their place whenever they want, in the page's language.
- 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.
- One setting for every block of the page, without opening the file: declare
-
Avoid the pitfalls
- **Settings handed to
mount()after the page loaded are dropped.**auto()mounts on DOM ready, andmount()on an element that already carries a reveal returns the existing one untouched. A page-wide block therefore goes inwindow.PixelRevealSettings, whichauto()reads itself; handed to a latermount()call it is silently dropped, and the settings block ends up looking decorative. - Blocks are not watched before the page's stylesheets and fonts have landed, for 3 s at most. A stylesheet link still loading, or a font file that never finishes, keeps the first sweeps waiting that long, with the text hidden. Past 3 s the sweeps start anyway and follow the box when the late file changes it.
- The block must not have a background of its own:
background-clip: textwould clip that background to the glyphs too. Put the background on a parent instead. - Inline children keep their own colour if they set one, so a link inside a revealed paragraph will show through before its turn. Give such children
color: inheritwhile the reveal runs, or leave them out of it. - **Do not hide the waiting blocks yourself with
visibility: hidden.** The painting class already hides them, andvisibilitytakes the words out of the accessibility tree, which is a heavy price for an effect. The transparent text stays readable by a screen reader the whole time. - **
celltakes a number of px orauto, never a CSS length.**2vwis not read and falls back to the default.
- **Settings handed to
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
Pixel sweep reveal is a text animation for Webflow and vanilla JavaScript, with no library. Also called pixel text reveal animation, pixel reveal effect or pixelated text reveal on scroll.
Headings and paragraphs write themselves in, one block of pixels at a time, as they reach the viewport. The sweep runs across the block in a chosen direction, mixed with enough randomness that it never looks like a wipe, and its leading edge burns in the accent tint before cooling down to the text colour. It works on anything that holds text: a display heading, a lede, a paragraph, a mono label, a big number. The text is never replaced by an image: the block paints its own glyphs through a canvas the size of its cell grid, one canvas pixel per cell, clipped to the letters by background-clip: text. So the words stay real words, they reflow, they can be selected and searched, a screen reader reads them, and once the sweep is over the element goes back to being drawn natively by the browser. Blocks that arrive together fire one after the other rather than all at once. Pattern type scroll text reveal, digital decoding flavour.
Updated
Questions
After pasting, select a heading or a paragraph and add the custom attribute data-pixel-reveal in the Settings panel. To cover a whole section in one action, put data-pixel-reveal-group on it instead: it picks up every heading, paragraph, list item, quote and text block inside.
No. The engine has no dependency. It watches the blocks with an IntersectionObserver and paints the glyphs through a canvas clipped to the letters by background-clip: text.
The direction is the value of the marking attribute, for instance data-pixel-reveal="down". The options are right (the default), left, down, up and read, which follows the lines as they are read. The speed is data-duration, 0.9 seconds by default, and a lower value makes the text snap in.
Yes. The text is never replaced by an image: the words stay real words that reflow, can be selected and searched, and are read by a screen reader the whole time. Once the sweep is over, the element goes back to being drawn natively by the browser.
The revealed block has a background of its own, and background-clip: text clips that background to the glyphs too. Move the background to a parent element.