A Next.js project that simulates 3D scrolling through depth (the Z-axis) using pure mathematics - no 3D engines, no Three.js, no WebGL. Just mathematical transformations applied to 2D elements to create the illusion of moving through space.
Test it Yourself => https://z-scroll.vercel.app/
Recording.2025-12-08.170932.mp4
This project demonstrates how to create a convincing 3D depth-scrolling effect by manipulating 2D elements with mathematical formulas. Instead of scrolling vertically or horizontally, users scroll "through the screen" - moving forward and backward through layers of content positioned at different depths.
The entire effect relies on mathematical transformations applied to CSS properties. Here's the breakdown:
Each element is assigned a z-depth value (0-15) representing its position in 3D space:
[Component, x, y, width, height, z]
[TitleComponent, 0, 0, 360, 180, 0], // z = 0 (closest)
[InfoComponent, 10, 30, 384, 192, 1], // z = 1
[ImageComponent, -10, 27, 400, 300, 2], // z = 2
// ... up to z = 13As you scroll, elements scale based on their distance from the "camera" (current scroll position):
scale: `calc(${Math.exp(scroll - z)})`;Mathematical principle: Exponential scaling creates realistic perspective. Objects further away (when scroll < z) shrink exponentially, while objects behind the camera (when scroll > z) grow exponentially.
- When
scroll = z: Element is at the focal plane (scale = 1) - When
scroll < z: Element is "in front" (scale > 1, appears larger) - When
scroll > z: Element is "behind" (scale < 1, appears smaller)
Elements shift position based on depth using exponential parallax:
left: `calc(50dvw + ${(Math.pow(6, scroll - z) * x) / 2}% - ${w / 2}px)`;
top: `calc(50dvh + ${(Math.pow(6, scroll - z) * y) / 2}% - ${h / 2}px)`;Mathematical principle: The formula 6^(scroll - z) creates dramatic parallax motion. Elements with offset values (x, y) appear to "fly by" the camera at different speeds based on their depth:
- Base 6 was chosen for pronounced effect
- The offset (
x,y) determines lateral movement direction - Division by 2 moderates the extreme values
Elements blur based on their distance from focus:
filter: Math.abs(scroll - z) < 0
? `blur(${Math.pow(Math.abs(z - scroll), 2) * 0.4}px)`
: `blur(${Math.pow(Math.abs(z - scroll) + 0.4, 6)}px)`;Mathematical principle:
- Behind camera (
scroll - z < 0): Quadratic blur (x²) for gentle defocus - In front of camera (
scroll - z > 0): Sixth-power blur (x⁶) for rapid defocus - Creates a realistic depth-of-field effect like a camera lens
Elements fade in/out using a skewed Gaussian distribution:
function skewedGaussian(x, A, b, c1, c2) {
const c = x < b ? c1 : c2;
return A * Math.exp(-Math.pow(x - b, 2) / (2 * Math.pow(c, 2)));
}
opacity: skewedGaussian(scroll - z, 1, 0, 1, 0.5);Mathematical principle:
- Standard Gaussian (bell curve) centered at focal point (
b = 0) - Asymmetric falloff (
c1 = 1behind,c2 = 0.5in front) - Elements fade faster when passing the camera than when approaching
Elements too far from view are hidden for performance:
display: Math.abs(scroll - z) > 2 ? "none" : "block";Only renders elements within ±2 depth units of current scroll position.
GSAP provides smooth interpolation between scroll states:
gsap.to(smoothScroll, {
current: newScroll,
duration: 0.5,
ease: "power2.out",
});Prevents jarring jumps and creates fluid motion through 3D space.
The illusion works because:
- Exponential functions (
e^x,6^x) mimic real-world perspective projection - Gaussian distribution creates natural visibility falloff like atmospheric depth
- Power functions (quadratic, sixth-power) simulate camera lens blur characteristics
- Synchronized transformations across scale, position, blur, and opacity create coherent depth perception
No actual 3D rendering occurs - just clever application of calculus and exponential functions to 2D CSS properties.
- Next.js 15.3 - React framework
- React 19 - UI library
- TypeScript - Type safety
- GSAP - Animation engine
- Tailwind CSS - Styling
# Install dependencies
npm install
# Run development server
npm run devOpen http://localhost:3000 and scroll to experience the depth effect.
- Mouse Wheel: Scroll through depth (desktop)
- Touch Drag: Swipe to navigate depth (mobile)
- Depth Indicator: Top-right corner shows current Z-position (0-15)
Modify the arr array in page.tsx to add/remove elements:
[Component, xOffset, yOffset, width, height, zDepth];xOffset,yOffset: Lateral displacement from center (%)width,height: Element dimensions (px)zDepth: Position in 3D space (0 = front, 15 = back)
- Elements beyond ±2 depth units are culled (not rendered)
- Mobile disables blur effects for better performance
- GSAP's
power2.outeasing provides smooth 60fps animation - Only visible elements receive backdrop blur effects
MIT
No 3D engines. No libraries. Just mathematics.