Zoom through
A transition between two sections, driven by the scroll.
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 next section needs an opaque background, since the word opens on it; the section takes no height of its own, its height being the next section plus the scroll the zoom takes; and no parent of the section may cut its overflow withoverflow: hidden, which stops the panel from pinning. Useoverflow-x: clipwhere a parent has to cut. -
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-zoom-through Marks the section that holds the panel and the next section, its two blocks. It takes no value: leave the value field empty. That section is what the stylesheet makes taller by the scroll the zoom takes, and what carries the settings. Without it on any element of the page, the embed marks the block holding the panel. data-zoom-through-panel Marks the panel, the block that holds the screen with the word on it, a child of the section. It takes no value. Without it the engine takes the first block of the section. [ Optional ] data-zoom-through-word Marks the word that grows, a heading in real text inside the panel. It takes no value. Without it the engine takes the first heading of the panel, and mounts nothing when the panel has none. [ Optional ] data-zoom-through-next Marks the section the word opens on, the block after the panel, a child of the section too. It takes no value, and it needs an opaque background. Without it the engine takes the block after the panel. [ Optional ] data-zoom-through-fade Marks what leaves the panel as the zoom starts, a strip of labels for instance: it fades out over the first eighth of the scroll. Without it everything on the panel stays until the letters cover it. [ Optional ] data-zoom-through-state Written by the engine on the section: idle before the zoom, live while it runs, done once through, still under reduced motion. Never written by hand; it is what the stylesheet reads to pin the panel and to blend it. [ 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-zoom-through-background #f4ece0 Colour of the panel, the ground the word sits on. Options: any plain CSS colour. It is what stays around the letters until the last stroke covers the screen. data-zoom-through-color #15100c Colour of the word at rest, before its ink turns into the window. Options: any plain CSS colour. Give it the background colour of the next section and the word opens without a seam. data-zoom-through-distance clamp(600px, 160vh, 1800px) Scroll the zoom takes, any CSS length: how long the panel holds the screen. data-zoom-through-origin-x auto Zoom point across the word, 0 at its left edge and 1 at its right, or auto. data-zoom-through-origin-y auto Zoom point down the word, 0 at the top of its box and 1 at the bottom, or auto. data-zoom-through-scale auto Size of the word when the scroll ends, as a factor, or auto. data-zoom-through-ease inOut Shape of the zoom along the scroll: linear, in, out, inOut or a cubic-bezier(x1, y1, x2, y2). Options: linear follows the wheel evenly, in holds the word before it leaves, out leaves at once and lands softly, inOut does both. data-zoom-through-reveal 0.3 Share of the scroll over which the ink of the word turns into the window, 0 to 1. data-zoom-through-smooth 0.12 Time the zoom takes to catch the scroll, in seconds.
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
ZoomThrough.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-zoom-through-background #f4ece0 Colour of the panel, the ground the word sits on. Options: any plain CSS colour. It is what stays around the letters until the last stroke covers the screen. data-zoom-through-color #15100c Colour of the word at rest, before its ink turns into the window. Options: any plain CSS colour. Give it the background colour of the next section and the word opens without a seam. data-zoom-through-distance clamp(600px, 160vh, 1800px) Scroll the zoom takes, any CSS length: how long the panel holds the screen. A quick punch through, the word jumps at the reader A slow travel into the letter, more wheel to turn before the next section data-zoom-through-origin-x auto Zoom point across the word, 0 at its left edge and 1 at its right, or auto. Towards the first letter Towards the last letter. On auto the engine aims at the thickest stroke nearest the middle; a point that is not on ink never fills the window data-zoom-through-origin-y auto Zoom point down the word, 0 at the top of its box and 1 at the bottom, or auto. Towards the top of the letters Towards their foot. On auto the engine picks the deepest ink of the column originX names data-zoom-through-scale auto Size of the word when the scroll ends, as a factor, or auto. The zoom stops short and what is left of the panel fades out The window is open long before the scroll ends, and the rest of the hold shows nothing new. On auto it is the smallest scale at which the stroke covers the panel data-zoom-through-ease inOut Shape of the zoom along the scroll: linear, in, out, inOut or a cubic-bezier(x1, y1, x2, y2). Options: linear follows the wheel evenly, in holds the word before it leaves, out leaves at once and lands softly, inOut does both. data-zoom-through-reveal 0.3 Share of the scroll over which the ink of the word turns into the window, 0 to 1. At 0 the word is a cut out from the first pixel At 1 the word stays mostly solid, and the next section only shows at the very end data-zoom-through-smooth 0.12 Time the zoom takes to catch the scroll, in seconds. At 0 the zoom is locked to the scroll, right with a smooth scroll library already on the page A zoom that glides after a wheel notch, and lags behind a fast scroll -
Drive it from JavaScript
When the word or an attribute changes after load, or when the page reacts to the zoom starting and to the word being through.const element = document.querySelector('[data-zoom-through]'); element.addEventListener('zoomthrough:change', (e) => { console.log(e.detail); }); const zoom = ZoomThrough.mount(element); // the section auto() mounted, or a new one zoom.progress(); // 0 to 1 zoom.refresh(); // after changing an attribute, or the word element.addEventListener('zoomthrough:change', (e) => { console.log(e.detail.state); // idle, live, done or still }); // When the section leaves the page: ZoomThrough.destroy(element); // removes the shade and gives back the markup as it was
-
Fit it to your page
- Structure: one section marked
data-zoom-through, with two blocks as its own children. First the panel,data-zoom-through-panel, holding the word,data-zoom-through-word, a heading in real text. Then the next section,data-zoom-through-next, with whatever it holds. Without the marks the engine takes the first block as the panel, its first heading as the word and the block after it as the next section. - The next section needs an opaque background. The word opens on it, and the two blends that make the window need something solid to work on. It is given at least one screen of height while the zoom is on.
- What else the panel holds stays put while the word grows, and is covered by the letters as they come. Mark what should leave first with
data-zoom-through-fade: it fades out over the first eighth of the scroll. - One setting for every section of the page, without opening the file: declare
window.ZoomThroughSettings = { color: '#1f3a2e' }in a script placed beforezoom-through.js. The three levels, least specific first, areDEFAULTS, then that object, then adata-zoom-through-*attribute on the section itself (data-zoom-through-origin-x="0.12"), so one section can still differ from the page-wide setting. - The two colours are written by the engine as custom properties of the section,
--zoom-through-backgroundand--zoom-through-color, which the stylesheet puts on the panel and on the word. For the seamless version, give the word the background colour of the next section. - The zoom point: left on
auto, the engine draws the word once on a small canvas, reads where its ink is, and aims at the thickest stroke nearest the middle. Name it yourself withoriginXandoriginY, shares of the word's box, to open on a given letter: 0.12 and 0.5 is usually the stem of the first letter. Aim at ink, never at the hole of a letter: the ink is the window. - What the script writes:
data-zoom-through-stateon the section,idlebefore the zoom,livewhile it runs,doneonce through,stillunder reduced motion. While live, a transform and a colour on the word, and an opacity on the marked labels; at rest and once through, nothing inline at all. While live it also adds one block after the panel, the shade, a black copy of the panel that takes no room and is hidden from assistive technology; it is removed as soon as the zoom is back at rest or through, so the page keeps one heading. - From JavaScript:
ZoomThrough.mount(element, overrides)returns the instance, withelement,settings,state(),progress()from 0 to 1,refresh()after an attribute or the word changed, anddestroy(). The section emitszoomthrough:change, which bubbles, with{ element, state }, each time the state changes. - **
destroy()leaves the section as it was**: the shade removed, the listeners and the observers gone, the state attribute and every inline style taken off. - Touch: the zoom follows the page scroll, so a finger drives it as a wheel does. Nothing listens to touch events.
- **
prefers-reduced-motion**: no pin and no zoom. The panel is a plain block, the next section follows it, and the page reads from top to bottom. Turning the preference off brings the zoom back without a reload. - Smooth scroll libraries: nothing to wire. The engine listens to scroll events and reads the section's place from the layout, which is what Lenis and the like move. Set
smoothto 0 if the two glides add up. - 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: one section marked
-
Avoid the pitfalls
- An ancestor that cuts its overflow kills the pin, silently.
overflow: hiddenon a parent makes it the scroll container of the sticky panel, and a container that does not scroll has nowhere to pin. Useoverflow-x: clipwhere a parent has to cut. The engine says so in the console when it finds one. - The panel and the next section are the section's own children, and the section holds nothing else. The scroll the zoom takes is added under them by the stylesheet, as the
::afterof the section: a section that already uses its::afterloses it while the zoom is on. - Give the section no height of its own. Its height is the next section plus the distance; a fixed height cuts the hold short or leaves a blank.
- A thin word needs a long zoom. The scale the window needs grows as the stroke gets thinner: a light weight at a small size can ask for a factor of several hundred, which reads as a rush at the end. Heavy weights and big sizes make the best windows.
- **
autoreads the word as one line of one font.** A word that wraps on two lines, or mixes sizes inside the heading, is measured wrong: giveoriginX,originYandscaleyourself. - The colours are read as plain opaque colours. A transparent or gradient panel, or a word with an alpha, does not blend right: the window is made of a multiply and a screen, which only know solid colours.
- **Settings handed to
mount()after the page loaded are dropped.**auto()mounts on DOM ready, andmount()on a section that already carries the effect returns the existing one. A page-wide block goes inwindow.ZoomThroughSettings, whichauto()reads itself; a later change goes through an attribute andrefresh(). - Links of the next section take the pointer only once through. While the zoom runs the panel is still over it.
- An ancestor that cuts its overflow kills the pin, silently.
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
Zoom through is a scroll animation for Webflow and vanilla JavaScript, with no library. Also called text zoom on scroll, zoom through text or text mask scroll transition.
A transition between two sections, driven by the scroll. A panel holds the screen with one huge word on it; as the page scrolls, the word grows on an eased curve, its zoom point travelling to the middle of the screen, until the stroke of one letter fills the window. The ink of the word is the window: over the first part of the scroll it turns from its colour into a cut out, the next section shows through the letters, and once a single stroke covers the screen the next section has taken over and scrolls on as any section does. The word is real text, a heading of the page, selectable and read by search engines; nothing is drawn to a canvas and there is no picture. The zoom point and the final scale can be left to the engine, which reads the ink of the word and aims at its thickest stroke, so the window is fully open exactly as the scroll ends, on every screen. No library: the hold is sticky positioning, the position is read from the layout, so it follows the browser's own scroll and a smooth scroll library alike. Under reduced motion there is no pin and no zoom: the panel, then the next section. Pattern type zoom through text, text mask transition, scroll driven reveal.
Updated
Questions
Mark one section data-zoom-through and give it two children: the panel, data-zoom-through-panel, holding a heading marked data-zoom-through-word, then the next section, data-zoom-through-next. Settings are custom attributes on the section, for instance data-zoom-through-color="#1f3a2e". The next section needs an opaque background, and the section itself takes no height of its own.
No. The hold is CSS sticky positioning and the progress is read from the layout, with no library, so nothing is inserted when the pin starts and nothing is given back when it ends. It follows the browser's own scroll and a smooth scroll library such as Lenis alike; set smooth to 0 if the two glides add up.
Real text. The word is a heading of the page, selectable and read by search engines, and there is no picture. The window is made of two copies of the panel blended with a multiply and a screen, which only know solid colours: a transparent or gradient panel does not blend right.
Set data-zoom-through-distance, the scroll the zoom takes, in any CSS length; the default is clamp(600px, 160vh, 1800px). Lower gives a quick punch through, higher a slow travel into the letter with more wheel to turn before the next section.
Left on auto, the engine reads the ink of the word and aims at the thickest stroke nearest the middle. To open on a given letter, set data-zoom-through-origin-x and data-zoom-through-origin-y, shares of the word's box from 0 to 1: 0.12 and 0.5 is usually the stem of the first letter. Aim at ink, never at the hole of a letter, and give both of them and scale yourself when the word wraps on two lines.