More Scroll Snippets
Hero Parallax Grid — Free HTML CSS JS Scroll Snippet
Hero Parallax Grid · Scroll · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Hero Parallax Grid — Rows Sliding Opposite Ways on Scroll

The hero parallax grid is the showpiece header where several rows of project tiles slide horizontally as you scroll the page — adjacent rows moving in opposite directions — so the gallery drifts past in a layered parallax. This snippet builds it with plain HTML, CSS, and a lean vanilla JavaScript scroll handler, and it's a favorite opener for studio and portfolio sites.
Scroll drives horizontal movement
Unlike a normal page where scrolling only moves things vertically, here the vertical scroll position is translated into horizontal motion. The update() function reads window.scrollY and, for each row, applies translateX proportional to it: base + scrollY * 0.25 * dir. So as you scroll down, the rows glide sideways. The 0.25 factor controls how fast they slide relative to scroll — smaller is subtler, larger is faster.
Opposite directions per row
Each row carries a data-dir of 1 or -1, and the translate is multiplied by it, so odd rows slide one way and even rows the other. This counter-motion between neighboring rows is the essence of the parallax — your eye reads the opposing layers as depth and energy, whereas rows all sliding the same way would just look like a single moving band. Each row also starts at a different base offset so they aren't aligned at the top edge, which makes the staggering feel intentional.
Efficient scroll handling
The scroll listener is { passive: true } and throttled through requestAnimationFrame with a ticking guard, so the transforms update at most once per frame however fast you scroll. Because the only work per frame is reading scrollY and writing a translateX on three elements, it stays smooth even on long pages. The rows have will-change: transform so the browser promotes them to their own compositor layers.
Overflow-wide rows
Each row is width: max-content and filled with seven tiles in a flex line, so the row is much wider than the viewport and there's plenty of content to slide into view from either side. overflow-x: hidden on the body clips the off-screen tiles so the horizontal motion doesn't create a scrollbar. The tiles themselves lift slightly on hover, so the gallery is interactive as well as animated.
Image-free, themeable tiles
Tiles are gradient rectangles with a label and a bottom scrim, generated in JavaScript from a color palette — so the hero is dependency-free and colorful. Swap each tile's --g background for a real image to turn it into an actual portfolio wall; the parallax logic doesn't care what fills the tiles.
Customizing it
Tune the 0.25 speed factor for more or less drift, add rows (each picks up its direction and a base offset automatically), change the per-row base offsets, adjust tile size, or drop in real images. Combine it with a static headline above, as shown, so the moving grid reads as a backdrop to your title. Pair it with a 3D marquee section or a flip link navigation for a striking studio homepage.
Step by step
How to Use
- 1Paste HTML, CSS, and JSA headline sits above three rows of colorful project tiles.
- 2Scroll the pageThe rows slide horizontally as you move down.
- 3Watch the parallaxAdjacent rows drift in opposite directions.
- 4Hover a tileIt lifts slightly, so the gallery stays interactive.
- 5Swap in imagesSet each tile's --g to a real project image.
- 6Tune the speedChange the 0.25 scroll factor for more or less drift.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
The update function reads window.scrollY and applies translateX to each row as base + scrollY * 0.25 * dir. So scroll position is mapped to horizontal offset rather than vertical, and the rows glide sideways as you scroll the page. The 0.25 factor sets how fast they slide relative to scroll.
Each row has a data-dir of 1 or -1 that multiplies its translate, so adjacent rows slide in opposite directions. The eye reads the opposing layers as depth and motion. Different per-row base offsets also keep the rows from starting aligned, which makes the layering feel deliberate.
No. The rows are width: max-content and wider than the viewport, but overflow-x: hidden on the body clips the off-screen tiles. So tiles slide in and out from the sides without ever adding a horizontal scrollbar to the page.
Yes. The scroll listener is passive and throttled with requestAnimationFrame behind a ticking flag, so it updates at most once per frame. The only per-frame work is reading scrollY and writing a translateX on a few rows, and will-change: transform promotes those rows to their own compositor layers for smooth movement.
Render the rows and tiles from data, then run the scroll handler in a mount effect with cleanup, writing each row's translateX via refs so scrolling doesn't re-render. Keep the ticking flag in a ref. The CSS ports directly. In Tailwind, build the rows with flex and w-max, hide overflow on a wrapper, and apply the computed transforms inline.