f you've ever landed on a site where your mouse wheel felt "floaty," where Page Down stopped doing anything, or where scrolling to the bottom took an unreasonably long, syrupy fade — you were on a site running a smooth-scroll library. Somewhere along the way, "make scrolling smoother" became a default addition to a lot of portfolio and agency sites, including plenty of good-looking ones. We don't run one, anywhere on this site, on purpose.
Whatisscrollhijacking?It'salibraryinterceptingyournativescrollinput—wheel,trackpad,arrowkeys—andreplayingitthroughitsownphysicsinsteadofthebrowser's.
Feel it yourself
native
Scroll me — this is the browser's own physics, untouched.
Scroll me — this is the browser's own physics, untouched.
Scroll me — this is the browser's own physics, untouched.
Scroll me — this is the browser's own physics, untouched.
Scroll me — this is the browser's own physics, untouched.
Scroll me — this is the browser's own physics, untouched.
Scroll me — this is the browser's own physics, untouched.
Scroll me — this is the browser's own physics, untouched.
simulated hijack
Scroll me — every wheel tick gets intercepted and replayed on a lag.
Scroll me — every wheel tick gets intercepted and replayed on a lag.
Scroll me — every wheel tick gets intercepted and replayed on a lag.
Scroll me — every wheel tick gets intercepted and replayed on a lag.
Scroll me — every wheel tick gets intercepted and replayed on a lag.
Scroll me — every wheel tick gets intercepted and replayed on a lag.
Scroll me — every wheel tick gets intercepted and replayed on a lag.
Scroll me — every wheel tick gets intercepted and replayed on a lag.
The box on the left is a plain overflow-y-auto element — the browser's own scroll physics, exactly what your operating system and input device already agreed on. The box on the right intercepts every wheel event, cancels the browser's default handling of it, and replays your input through a deliberately sluggish easing loop instead — a small, honest simulation of what a scroll-hijacking library does at a larger scale, across an entire page instead of one box.
What breaks when a library owns your scroll
Once a library is intercepting wheel and touch events to drive its own scroll animation, it has to reimplement everything the browser used to give you for free: Page Up/Page Down, Home/End, spacebar, screen-reader scroll commands, momentum scrolling on trackpads, scroll-anchoring so the page doesn't jump when an image above you finishes loading. Most libraries reimplement some of this and quietly miss the rest — which is exactly the shape of the complaint "scrolling on this site feels broken," because functionally, for that one user's input method, it is.
typescript
// components/ScrollReset.tsx — the entire scroll-behavior // customization on this site. Not a hijack: it doesn't touch // wheel, keyboard, or touch input at all. export function ScrollReset() { const pathname = usePathname(); useEffect(() => { if (window.location.hash) return; window.scrollTo(0, 0); }, [pathname]); return null; }
What we do instead
Every scroll-driven effect on this site — the sheet stack, the scrub-in text, the reading-line focus, the scroll-progress bar — reads scroll position, it never sets it. Framer Motion's useScroll subscribes to the native scrollY the browser is already tracking and turns it into a number other elements react to. The browser stays in sole control of the actual act of scrolling; nothing ever calls scrollTo or intercepts a wheel event to fake it. That's the whole difference: react to native scrolling, don't replace it.
The one exception, shown above, is ScrollReset — and it's not a hijack either. It doesn't touch how scrolling behaves at all; it just resets the position to the top when you navigate to a new route, which is what browsers already do on a full page load and what single-page apps have to do manually since they never actually reload the page. Nothing about wheel, keyboard, or touch input passes through custom code anywhere on this site.