Multilevel spatial composition where background, midground, and foreground planes translate at proportional depth intensities in response to pointer or scroll offsets.
Preview
Z-PLANE // FOREGROUND (1.0x)
Tri-Plane Depth Composition
Customize
4 props22
22
Installation
Copies the component source and its CSS Module into your project.
npx motionfoundry add parallax-depth-sceneWrites components/ui/parallax-depth-scene.tsx and components/styles/parallax-depth-scene.module.css.
Usage
Updates live as you change the controls — only props that differ from the defaults are included.
example.tsx
1import { ParallaxDepthScene } from "@motionfoundry/ui";2 3export function Example() {4 return (5 <ParallaxDepthScene />6 );7}Props
Accessibility
Automatically dampens motion on touch/small viewports and disables layer displacement when prefers-reduced-motion is active.
Toggle prefers-reduced-motion in your OS or browser devtools (Rendering → Emulate CSS media feature) to verify the reduced-motion state.
Source
components/ui/parallax-depth-scene.tsx
1"use client";2 3import * as React from "react";4import { motion, useMotionValue, useSpring, useTransform, useReducedMotion } from "motion/react";5import styles from "../styles/parallax-depth-scene.module.css";6 7export interface ParallaxDepthSceneProps {8 /** Foreground card title */9 title?: string;10 /** Maximum translation in pixels */11 depthMultiplier?: number;12 /** Spring damping */13 damping?: number;14 /** Render deep background grid layer */15 showGridLayer?: boolean;16 /** Optional className */17 className?: string;18}19 20export function ParallaxDepthScene({21 title = "Tri-Plane Depth Composition",22 depthMultiplier = 22,23 damping = 22,24 showGridLayer = true,25 className = "",26}: ParallaxDepthSceneProps) {27 const prefersReducedMotion = useReducedMotion();28 const normX = useMotionValue(0);29 const normY = useMotionValue(0);30 31 const smoothX = useSpring(normX, { stiffness: 200, damping });32 const smoothY = useSpring(normY, { stiffness: 200, damping });33 34 const bgX = useTransform(smoothX, (v) => v * -depthMultiplier * 0.4);35 const bgY = useTransform(smoothY, (v) => v * -depthMultiplier * 0.4);36 const midX = useTransform(smoothX, (v) => v * depthMultiplier * 0.65);37 const midY = useTransform(smoothY, (v) => v * depthMultiplier * 0.65);38 const fgX = useTransform(smoothX, (v) => v * depthMultiplier);39 const fgY = useTransform(smoothY, (v) => v * depthMultiplier);40 41 const handlePointerMove = (e: React.PointerEvent<HTMLDivElement>) => {42 if (prefersReducedMotion || e.pointerType === "touch") return;43 const rect = e.currentTarget.getBoundingClientRect();44 const nx = (e.clientX - rect.left) / rect.width - 0.5;45 const ny = (e.clientY - rect.top) / rect.height - 0.5;46 normX.set(nx * 2);47 normY.set(ny * 2);48 };49 50 const handlePointerLeave = () => {51 normX.set(0);52 normY.set(0);53 };54 55 return (56 <div57 onPointerMove={handlePointerMove}58 onPointerLeave={handlePointerLeave}59 className={`${styles.scene} ${className}`.trim()}60 >61 {showGridLayer && (62 <motion.div63 aria-hidden="true"64 style={{ x: prefersReducedMotion ? 0 : bgX, y: prefersReducedMotion ? 0 : bgY }}65 className={styles.bgLayer}66 />67 )}68 <motion.div69 aria-hidden="true"70 style={{ x: prefersReducedMotion ? 0 : midX, y: prefersReducedMotion ? 0 : midY }}71 className={styles.midLayer}72 >73 <span className={styles.planeLabel}>Z-PLANE // MIDGROUND (0.65x)</span>74 </motion.div>75 <motion.div76 style={{ x: prefersReducedMotion ? 0 : fgX, y: prefersReducedMotion ? 0 : fgY }}77 className={styles.fgLayer}78 >79 <span className={styles.planeLabel}>Z-PLANE // FOREGROUND (1.0x)</span>80 <h4 className={styles.title}>{title}</h4>81 </motion.div>82 </div>83 );84}