Scroll Tilted Grid
componentry/scroll-tilted-gridFreeComponent
A cinematic image grid that tilts and resolves into focus as each frame crosses the viewport.
Live preview
live · rendered by the registry
Rendered by componentry on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://componentry.dev/r/scroll-tilted-grid.jsonSource
1"use client";23import { cn } from "@/lib/utils";4import { useReducedMotion } from "framer-motion";5import ReactLenis from "lenis/react";6import {7 type CSSProperties,8 useEffect,9 useMemo,10 useRef,11 useState,12} from "react";1314export interface ScrollTiltedGridImage {15 src: string;16 alt: string;17}1819export interface ScrollTiltedGridProps {20 images: readonly ScrollTiltedGridImage[];21 loop?: boolean;22 initialCycles?: number;23 maxCycles?: number;24 smoothScroll?: boolean;25 aspectRatio?: string;26 perspective?: number;27 maxTilt?: number;28 maxBlur?: number;29 rounded?: string;30 sectionPadding?: string;31 className?: string;32}3334type TileVariables = CSSProperties & {35 "--tile-blur": string;36 "--tile-brightness": number;37 "--tile-saturation": number;38 "--tile-transform": string;39 "--tile-image-scale": number;40};4142function clamp(value: number, minimum = 0, maximum = 1) {43 return Math.min(maximum, Math.max(minimum, value));44}4546function GalleryTile({47 image,48 index,49 aspectRatio,50 perspective,51 maxTilt,52 maxBlur,53 rounded,54 reduceMotion,55}: {56 image: ScrollTiltedGridImage;57 index: number;58 aspectRatio: string;59 perspective: number;60 maxTilt: number;61 maxBlur: number;62 rounded: string;63 reduceMotion: boolean;64}) {65 const tileRef = useRef<HTMLElement>(null);66 const side = index % 2 === 0 ? -1 : 1;6768 useEffect(() => {69 const tile = tileRef.current;70 if (!tile || reduceMotion) return;7172 let frame = 0;73 const update = () => {74 frame = 0;75 const rect = tile.getBoundingClientRect();76 const travel = window.innerHeight + rect.height;77 const position = clamp((window.innerHeight - rect.top) / travel);78 const distance = Math.abs(position - 0.5) * 2;79 const signed = (position - 0.5) * 2;80 const eased = distance * distance * (3 - 2 * distance);81 const x = side * eased * 18;82 const y = -signed * eased * 24;83 const tilt = -signed * maxTilt;84 const roll = side * signed * 3;85 const skew = -side * signed * 7;8687 tile.style.setProperty("--tile-blur", `${eased * maxBlur}px`);88 tile.style.setProperty("--tile-brightness", String(1 - eased * 0.5));89 tile.style.setProperty("--tile-saturation", String(1 - eased * 0.5));90 tile.style.setProperty("--tile-image-scale", String(1.03 + eased * 0.15));91 tile.style.setProperty(92 "--tile-transform",93 `translate3d(${x}%, ${y}%, ${eased * 180}px) rotateX(${tilt}deg) rotateZ(${roll}deg) skewX(${skew}deg)`,94// … truncated
More from componentry
All 59 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Animated Gradientanimated-gradient | componentry | Components | Free | no deps | |
| ASCII Effectascii-effect | componentry | Components | Free | no deps | |
| Aurora Flowaurora-flow | componentry | Components | Free | no deps | |
| auth-modalauth-modal | componentry | Components | Free | no deps | |
| border-beamborder-beam | componentry | Components | Free | no deps | |
| circuit-boardcircuit-board | componentry | Components | Free | no deps | |
| closing-plasmaclosing-plasma | componentry | Components | Free | no deps | |
| collection-surfercollection-surfer | componentry | Components | Free | no deps |
