Image parallax
The parallax every site asks for, as one piece.
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 frame keeps the size you give it, a ratio, a height or the height of its picture, and cuts everything that leaves it: keep captions and shadows outside it, and give it no padding. A wrapper set tooverflow: hiddenabove the picture, as most Webflow pages have, is no trouble: the engine sees it and times the picture on the scroll of the page instead of on its frame. On a marked picture the Embed setsheight,max-height,margin-top,margin-bottom,object-fitandvertical-alignwith!important, since the class of nearly every image sets a height: those six are no longer yours on that image, so size and space the frame, not the picture, and frame the photograph withobject-position. If a rule of yours still holds the picture shorter, or the file is too small for its frame, the console says so with the numbers. -
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-image-parallax Marks the box that holds a picture, or the image itself. Its value is the intensity, the share of the scroll the picture holds back: 0.2 for a picture that moves a fifth slower than the page, a negative number to send it the other way, 0 to hold it still. Left empty it takes the default, 0.15. It is the setting <code>intensity</code> written the short way, and it wins over <code>data-image-parallax-intensity</code>; the other settings go beside it, on the same element, and on an element marked <code>data-image-parallax-layer</code> the same way. data-image-parallax-layer On any other element, a title, a caption, a block of words: it moves by the same law, with no frame and nothing enlarged. Its value is its intensity; keep it small, 0.03 to 0.12, negative to move faster than the page. [ Optional ] data-image-parallax-media Inside a marked box that holds several pictures: marks the one that moves. It takes no value. Without it the first img, video or canvas of the box is taken. [ Optional ] data-image-parallax-state Written by the engine on what you marked: view when the browser drives the picture on a view timeline, root when it is timed on the scroll of the page, script when the script moves it, still under reduced motion or at intensity 0. Never written by hand. [ Optional ] 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-image-parallax-intensity 0.15 The share of the scroll the picture holds back, from -1 to 1. At 0.2 the picture moves a fifth slower than the page, as if it stood behind it. Two pictures with the same number sit at the same depth, whatever their sizes. data-image-parallax-narrow 0.6 The share of the intensity kept on a screen narrower than 768px, from 0 to 1. A phone window is tall next to its pictures, so the same depth asks each one for much more room. data-image-parallax-limit 1.6 The most a picture may outgrow its frame, as a multiple of the frame's height, 1 and up. A small frame at a strong intensity would need a picture several times its own height: past the limit the travel is shortened instead. data-image-parallax-native true Whether the travel runs on the browser's own scroll timeline where there is one. Options: true, a CSS view timeline in Chrome, Edge and Safari 26, the script elsewhere; false, the script everywhere, for a page whose scroll is not the document's or to compare the two.
Two files and their pictures (9), 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
ImageParallax.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-image-parallax-intensity 0.15 The share of the scroll the picture holds back, from -1 to 1. At 0.2 the picture moves a fifth slower than the page, as if it stood behind it. Two pictures with the same number sit at the same depth, whatever their sizes. Closer to the page; at 0 the picture is still and nothing is enlarged. Below zero the picture moves the other way, faster than the page A deeper picture that travels further in its frame, and needs more of the photograph to do it; above 0.3 most frames reach their limit data-image-parallax-narrow 0.6 The share of the intensity kept on a screen narrower than 768px, from 0 to 1. A phone window is tall next to its pictures, so the same depth asks each one for much more room. Calmer on a phone; at 0 nothing moves there The same depth as on a desktop; at 1 small pictures reach their limit and are cropped more data-image-parallax-limit 1.6 The most a picture may outgrow its frame, as a multiple of the frame's height, 1 and up. A small frame at a strong intensity would need a picture several times its own height: past the limit the travel is shortened instead. The crop stays closer to the one you framed, and small pictures move less; at 1 nothing moves Small pictures keep their full travel and show a tighter crop of the photograph data-image-parallax-native true Whether the travel runs on the browser's own scroll timeline where there is one. Options: true, a CSS view timeline in Chrome, Edge and Safari 26, the script elsewhere; false, the script everywhere, for a page whose scroll is not the document's or to compare the two. -
Drive it from JavaScript
When pictures arrive after the page has loaded, when a layout changes without changing any size, or when the page wants to know what the engine decided.const element = document.querySelector('[data-image-parallax]'); element.addEventListener('image-parallax:fit', (e) => { console.log(e.detail); }); // Pictures added after the page loaded: mount everything marked inside a block. const container = element.parentElement; ImageParallax.auto(container); const picture = ImageParallax.mount(element); // the element auto() mounted, or a new one picture.mode(); // 'view', 'root', 'script' or 'still' picture.travel(); // px the picture moves in its frame picture.soft(); // true when the file is too small // After a change of layout that resizes nothing: ImageParallax.refresh(); // When the section leaves the page: ImageParallax.destroy(container); // every picture as the page wrote it
-
Fit it to your page
A picture in a frame
- Mark the frame and give it a number.
<div data-image-parallax="0.2">around an<img>is the whole of it. The frame keeps the size the page gave it, a ratio, a height or the height of its picture; the engine never changes it. - Or mark the image.
<img data-image-parallax="0.2">makes its parent the frame. Handy when the parent is a Webflow Div Block you would rather not touch; the parent then cuts everything that leaves it. - Any ratio, any size. A wide hero, a tall plate, a square thumbnail: the room is worked out for each. A frame that takes its height from its picture (
width: 100%; height: auto) works as well as one with a ratio. - The background of a section. Mark the section, put the image in it with
position: absolute,top: 0,left: 0,width: 100%,height: 100%, and the words after it withposition: relative. The preview's dark band is built that way. - Twenty pictures on a page cost nothing while the page scrolls where the browser drives them, and one number each where the script does.
- The picture is the page's own.
alt,srcset,sizes,loading,decoding,<picture>with its sources: all kept. A<video>or a<canvas>moves the same way. - Negative numbers send the picture the other way, faster than the page: a plate that seems to float in front of it.
What the number means
The intensity is a depth, not a distance. At
0.2the picture holds back a fifth of the scroll for as long as its frame is in the window, so it travels0.2 x (height of the window + height of the frame)from the moment the frame comes in at the foot of the window to the moment it leaves at the top. A large frame therefore travels further than a small one, and the two still look as if they stood at the same distance. That travel is the room the picture needs: it is made that much taller, and no more.When the photograph is taller than its frame's ratio, a portrait in a landscape frame for one, the room is taken from the part of the photograph that was hidden and nothing is enlarged at all. When it is not, the picture is scaled by the least that covers the room, evenly, and loses a little on its sides.
limitcaps that.Layers
data-image-parallax-layer="0.1"on a title, a caption or any block moves the element itself: slower than the page above zero, faster below. Nothing is cut and nothing is enlarged, so keep the numbers small, 0.03 to 0.12: the element leaves its place byintensity x (window + its own height) / 2at each end. A layer inside a marked frame is cut by that frame.From JavaScript
ImageParallax.mount(element, overrides)returns the handle of a marked element, mounting it if it was not:{ element, frame, picture, settings, mode(), travel(), soft(), refresh(), destroy() }.mode()isview,root,scriptorstill;travel()the distance in px the picture moves in its frame, end to end;soft()true when the file is enlarged.ImageParallax.auto(root)mounts everything marked insideroot, and runs once on its own.ImageParallax.refresh()measures everything again;ImageParallax.destroy(root)gives the markup back.ImageParallax.nativesays whether the browser has scroll timelines.- One event, bubbling from the marked element:
image-parallax:fit, each time the fit of that element changes, with{ element, picture, mode, travel, soft }indetail. - **Under
prefers-reduced-motion: reduce** nothing moves and nothing is enlarged: every picture is exactly as the page wrote it. - Without JavaScript, and on the canvas of the Webflow Designer, the same: plain pictures in their frames.
- The words and the photographs of the demo are the demo's: on a real page every text and every picture is the user's own, or asked for.
- Mark the frame and give it a number.
-
Avoid the pitfalls
- The frame cuts everything that leaves it. It gets
overflow: clipand paint containment. A caption that hangs outside the frame, a shadow on the picture, a tooltip: put them outside the frame, as siblings. - Give the frame no padding. The picture covers the box it was given; a padding on the frame is a strip the picture only covers part of the time. Put the padding on a parent.
- The picture must be allowed its height. The engine sets a height and two margins on it with
!important, and reads the result: when a rule of the page still holds the picture shorter, the travel is cut to the room it really got and the console says so. No empty edge either way. - A file too small is said, not hidden. When the file has fewer pixels than its frame and its travel need, the console gives the size it should have. The check counts CSS pixels: on a dense screen, ship twice that through
srcset. - A page that scrolls inside a box. Where the browser drives the travel, a frame inside an element that really scrolls (
overflow: autoand content taller than it) follows that element, as it should. The script path only knows the scroll of the document: in Firefox such a frame stays still in the middle of its room. - **An ancestor with
overflow: hidden** that does not scroll is no trouble: the engine sees it and times the picture on the page's own scroll (mode()saysroot). That path reads where the frame is on the page: after a change of layout that does not change the height of the page or of the frame, callImageParallax.refresh(). - A frame inside a pinned or transformed parent (
position: sticky, a horizontal gallery driven by a transform) is fine on the view timeline and wrong on the two other paths, which take the frame's place in the page for granted. - **Do not animate
translateon the picture yourself.**transform,scaleandrotatestay yours. - In React or any framework that owns the DOM, mount in an effect and call
destroy()in its cleanup: the engine writes attributes and custom properties on the frame and on the picture, and adds one hidden element to the body while something is mounted.
- The frame cuts everything that leaves it. It gets
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
Image parallax is a scroll animation for Webflow and vanilla JavaScript, with no library. Also called image parallax on scroll, parallax scrolling effect or background image parallax section.
The parallax every site asks for, as one piece. Mark the box that holds a picture, or the picture itself, with data-image-parallax and a number: the share of the scroll the picture holds back. The picture then moves a little slower than the page inside its frame, as if it stood further away. The engine works out, for each frame, the room that travel needs from the height of the frame, the height of the window and the intensity, makes the picture exactly that much taller, and checks the result, so no edge ever shows whatever the speed of the scroll or the size of the frame. The file fills the taller box with object-fit, which uses the part of the photograph the frame was hiding before anything is enlarged, so nothing is stretched and nothing is blurred; when a file is too small for its frame and its travel the console says so, with the size it needs. The picture stays the real img of the page, with its alt, srcset, sizes and loading attributes. Where the browser has scroll driven animations (Chrome, Edge, Safari 26) the travel is a CSS view timeline on the compositor and no script runs while the page scrolls; elsewhere one scroll listener places every picture from numbers measured once. A second attribute, data-image-parallax-layer, moves any other element by the same law, for a title or a caption at its own depth. Under reduced motion nothing moves and nothing is enlarged. No library. Pattern type image parallax, scroll parallax, background parallax.
Updated
Questions
Select the Div Block that holds your image, or the Image itself, and add the custom attribute data-image-parallax with a number for its value, 0.2 to start with. The picture then moves a fifth slower than the page inside its frame. Nothing else is set: the engine works out the room the picture needs and makes it that much taller.
No. In Chrome, Edge and Safari 26 the travel is a CSS scroll driven animation, a view timeline on the frame, and no script runs while the page scrolls. In a browser without scroll timelines, Firefox today, one passive scroll listener places the pictures from numbers measured once. No library is loaded.
Because the picture was not made tall enough for the distance it travels, which depends on the height of the frame, the height of the window and the speed. Here that room is computed for each picture, intensity times the window plus the frame, written as a height on the img, then read back; if a rule of the page still holds the picture shorter, the travel is cut to match and the console says so.
Yes. Mark the section data-image-parallax, put the img inside it with position: absolute, top: 0, left: 0, width: 100% and height: 100%, and give the words after it position: relative. It stays a real img with its alt, srcset and loading, not a CSS background.
The value of the attribute is the speed: data-image-parallax="0.1" is subtle, 0.3 is deep, a negative number sends the picture the other way and 0 holds it still. Under 768px the intensity is multiplied by narrow, 0.6 by default, and limit, 1.6 by default, caps how far a small picture may outgrow its frame.