NEW
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
Learn more
Skip to content
Docs

Vertical Tiles

A preloader component that makes transition to any section look vivid

Loading...

Installation

CLI

pnpm dlx shadcn@latest add https://animata.design/r/preloader/vertical-tiles.json

Manual

Install dependencies

npm install motion lucide-react

Run the following command

It will create a new file vertical-tiles.tsx inside the components/animata/preloader directory.

mkdir -p components/animata/preloader && touch components/animata/preloader/vertical-tiles.tsx

Paste the code

Open the newly created file and paste the following code:

import { motion, useInView } from "motion/react";
import type React from "react";
import { useCallback, useEffect, useRef, useState } from "react";
 
import { cn } from "@/lib/utils";
 
interface Tile {
  id: number;
  width: number;
  order: number;
}
 
interface VerticalTilesProps {
  tileClassName?: string;
  minTileWidth?: number;
  animationDuration?: number;
  animationDelay?: number;
  stagger?: number;
  children?: React.ReactNode;
}
 
export default function VerticalTiles({
  tileClassName,
  minTileWidth = 32,
  animationDuration = 0.5,
  animationDelay = 1,
  stagger = 0.05,
  children,
}: VerticalTilesProps) {
  const [tiles, setTiles] = useState<Tile[]>([]);
  const containerRef = useRef<HTMLDivElement>(null);
  const isInView = useInView(containerRef, { once: true, amount: 0.3 });
 
  const calculateTiles = useCallback(() => {
    if (containerRef.current) {
      const { offsetWidth: width, offsetHeight: _ } = containerRef.current;
      const tileCount = Math.max(3, Math.floor(width / minTileWidth));
      const tileWidth = width / tileCount + 1;
 
      const newTiles = Array.from({ length: tileCount }, (_, index) => ({
        id: index,
        width: tileWidth,
        order: Math.abs(index - Math.floor((tileCount - 1) / 2)),
      }));
 
      setTiles(newTiles);
    }
  }, [minTileWidth]);
 
  useEffect(() => {
    calculateTiles();
    const resizeObserver = new ResizeObserver(calculateTiles);
    if (containerRef.current) {
      resizeObserver.observe(containerRef.current);
    }
    return () => resizeObserver.disconnect();
  }, [calculateTiles]);
 
  return (
    <div ref={containerRef} className="relative overflow-hidden">
      {children}
 
      <div className="absolute inset-0 flex">
        {tiles.map((tile) => (
          <motion.div
            key={tile.id}
            className={cn("bg-gray-800", tileClassName)}
            style={{
              width: tile.width,
              position: "absolute",
              left: `${(tile.id * 100) / tiles.length}%`,
              top: 0,
              height: "100%",
            }}
            initial={{ y: 0 }}
            animate={isInView ? { y: "100%" } : { y: 0 }}
            transition={{
              duration: animationDuration,
              delay: animationDelay + tile.order * stagger,
              ease: [0.45, 0, 0.55, 1],
            }}
          />
        ))}
      </div>
    </div>
  );
}

Credits

Built by SatyamVyas04

Inspired by SteviaPlease.me