Reveal content with a sliding block animation — add to your site in seconds
Content revealed with style
Copy URL or Code
Paste to your AI coding assistant and customize:
Done. Your AI handles the rest.
Fully customizable. Supports four directions (left-right, right-left, top-bottom, bottom-top), trigger modes (mount, scroll, manual), custom colors, and duration settings.
/*
================================================================================
AI COMPONENT: BlockReveal
================================================================================
SETUP:
1. Create file: components/BlockReveal.tsx
2. Copy this entire code block into that file
3. Import: import BlockReveal from "@/components/BlockReveal"
4. No external dependencies required - pure React + CSS
================================================================================
QUICK CUSTOMIZATION (via props)
================================================================================
| Prop | Type | Default |
|------------|----------------------------------------|------------------------------|
| direction | "lr" | "rl" | "tb" | "bt" | "lr" |
| trigger | "mount" | "scroll" | "manual" | "mount" |
| bgColor | string (CSS color) | "#7f40f1" |
| duration | number (milliseconds) | 500 |
| delay | number (milliseconds) | 0 |
| coverArea | number (0-100, percent to leave) | 0 |
| easing | string (CSS easing) | "cubic-bezier(0.7, 0, 0.3, 1)" |
| className | string | "" |
| onStart | () => void | undefined |
| onCover | () => void | undefined |
| onComplete | () => void | undefined |
================================================================================
DIRECTION OPTIONS
================================================================================
| Direction | Description |
|-----------|---------------------------------------|
| lr | Block slides left to right |
| rl | Block slides right to left |
| tb | Block slides top to bottom |
| bt | Block slides bottom to top |
================================================================================
TRIGGER OPTIONS
================================================================================
| Trigger | Description |
|---------|--------------------------------------------------|
| mount | Animation plays when component mounts |
| scroll | Animation plays when element scrolls into view |
| manual | Animation plays when element is clicked |
================================================================================
COMMON MODIFICATIONS
================================================================================
1. BASIC USAGE:
<BlockReveal>
<h1>Your Content Here</h1>
</BlockReveal>
2. DIFFERENT DIRECTION:
<BlockReveal direction="rl">
<p>Reveals from right to left</p>
</BlockReveal>
3. SCROLL TRIGGER:
<BlockReveal trigger="scroll" delay={100}>
<div>Appears when scrolled into view</div>
</BlockReveal>
4. CUSTOM COLOR:
<BlockReveal bgColor="#FF6B6B">
<span>Red reveal block</span>
</BlockReveal>
5. WITH CALLBACKS:
<BlockReveal
onStart={() => console.log("Started")}
onCover={() => console.log("Content covered")}
onComplete={() => console.log("Done")}
>
<div>Tracked animation</div>
</BlockReveal>
================================================================================
SOURCE CODE
================================================================================
*/
"use client";
import React, {
useRef,
useEffect,
useState,
useCallback,
ReactNode,
} from "react";
export type RevealDirection = "lr" | "rl" | "tb" | "bt";
export type RevealTrigger = "mount" | "scroll" | "manual";
export interface BlockRevealProps {
children: ReactNode;
direction?: RevealDirection;
trigger?: RevealTrigger;
bgColor?: string;
duration?: number;
delay?: number;
coverArea?: number;
easing?: string;
className?: string;
onStart?: () => void;
onCover?: () => void;
onComplete?: () => void;
}
export default function BlockReveal({
children,
direction = "lr",
trigger = "mount",
bgColor = "#7f40f1",
duration = 500,
delay = 0,
coverArea = 0,
easing = "cubic-bezier(0.7, 0, 0.3, 1)",
className = "",
onStart,
onCover,
onComplete,
}: BlockRevealProps) {
const containerRef = useRef<HTMLDivElement>(null);
const revealerRef = useRef<HTMLDivElement>(null);
const contentRef = useRef<HTMLDivElement>(null);
const [isRevealed, setIsRevealed] = useState(false);
const [contentVisible, setContentVisible] = useState(false);
const animatingRef = useRef(false);
const getTransformSettings = useCallback(() => {
switch (direction) {
case "rl":
return {
initial: { scaleX: 0, scaleY: 1 },
origin: "100% 50%",
final: { scaleX: coverArea / 100, scaleY: 1 },
};
case "tb":
return {
initial: { scaleX: 1, scaleY: 0 },
origin: "50% 0%",
final: { scaleX: 1, scaleY: coverArea / 100 },
};
case "bt":
return {
initial: { scaleX: 1, scaleY: 0 },
origin: "50% 100%",
final: { scaleX: 1, scaleY: coverArea / 100 },
};
case "lr":
default:
return {
initial: { scaleX: 0, scaleY: 1 },
origin: "0% 50%",
final: { scaleX: coverArea / 100, scaleY: 1 },
};
}
}, [direction, coverArea]);
const reveal = useCallback(() => {
if (animatingRef.current || isRevealed) return;
animatingRef.current = true;
onStart?.();
const revealer = revealerRef.current;
const content = contentRef.current;
if (!revealer || !content) return;
const settings = getTransformSettings();
// Set initial state
revealer.style.transformOrigin = settings.origin;
revealer.style.transform = `scaleX(${settings.initial.scaleX}) scaleY(${settings.initial.scaleY})`;
revealer.style.transition = `transform ${duration}ms ${easing}`;
// Phase 1: Block slides in to cover content
requestAnimationFrame(() => {
revealer.style.transform = "scaleX(1) scaleY(1)";
});
// At midpoint, show content and trigger onCover
setTimeout(() => {
setContentVisible(true);
onCover?.();
// Phase 2: Block slides out to reveal content
const finalX =
direction === "lr" || direction === "rl" ? settings.final.scaleX : 1;
const finalY =
direction === "tb" || direction === "bt" ? settings.final.scaleY : 1;
// Change origin for slide out
const slideOutOrigin =
direction === "lr"
? "100% 50%"
: direction === "rl"
? "0% 50%"
: direction === "tb"
? "50% 100%"
: "50% 0%";
revealer.style.transformOrigin = slideOutOrigin;
revealer.style.transform = `scaleX(${finalX}) scaleY(${finalY})`;
}, duration);
// Animation complete
setTimeout(() => {
animatingRef.current = false;
setIsRevealed(true);
onComplete?.();
}, duration * 2);
}, [
direction,
duration,
easing,
getTransformSettings,
isRevealed,
onComplete,
onCover,
onStart,
]);
// Reset function for re-triggering
const reset = useCallback(() => {
setIsRevealed(false);
setContentVisible(false);
animatingRef.current = false;
if (revealerRef.current) {
const settings = getTransformSettings();
revealerRef.current.style.transform = `scaleX(${settings.initial.scaleX}) scaleY(${settings.initial.scaleY})`;
}
}, [getTransformSettings]);
// Mount trigger
useEffect(() => {
if (trigger === "mount" && !isRevealed) {
const timer = setTimeout(reveal, delay);
return () => clearTimeout(timer);
}
}, [trigger, delay, reveal, isRevealed]);
// Scroll trigger
useEffect(() => {
if (trigger !== "scroll") return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && !isRevealed) {
setTimeout(reveal, delay);
}
});
},
{ threshold: 0.3 },
);
if (containerRef.current) {
observer.observe(containerRef.current);
}
return () => observer.disconnect();
}, [trigger, delay, reveal, isRevealed]);
return (
<div
ref={containerRef}
className={`relative overflow-hidden ${className}`}
onClick={trigger === "manual" ? reveal : undefined}
style={{ cursor: trigger === "manual" ? "pointer" : undefined }}
>
{/* Content */}
<div
ref={contentRef}
style={{
opacity: contentVisible ? 1 : 0,
transition: "opacity 0ms",
}}
>
{children}
</div>
{/* Revealer block */}
<div
ref={revealerRef}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
backgroundColor: bgColor,
transform: "scaleX(0)",
transformOrigin: "0% 50%",
pointerEvents: "none",
}}
/>
</div>
);
}
// Named export for convenience
export { BlockReveal };import BlockReveal from "@/components/BlockReveal";
// Basic reveal on mount (left to right)
export default function BasicReveal() {
return (
<BlockReveal>
<h1 className="text-4xl font-bold">Welcome</h1>
<p className="text-gray-600">Content revealed with style</p>
</BlockReveal>
);
}
// Different directions
export function DirectionExamples() {
return (
<div className="space-y-8">
<BlockReveal direction="lr" bgColor="#7f40f1">
<p>Left to Right</p>
</BlockReveal>
<BlockReveal direction="rl" bgColor="#FF6B6B">
<p>Right to Left</p>
</BlockReveal>
<BlockReveal direction="tb" bgColor="#4ECDC4">
<p>Top to Bottom</p>
</BlockReveal>
<BlockReveal direction="bt" bgColor="#FFE66D">
<p>Bottom to Top</p>
</BlockReveal>
</div>
);
}
// Scroll-triggered reveal
export function ScrollReveal() {
return (
<div className="min-h-screen flex items-center justify-center">
<BlockReveal trigger="scroll" direction="lr" delay={200}>
<div className="p-8 bg-gray-100 rounded-lg">
<h2>Appears on scroll</h2>
</div>
</BlockReveal>
</div>
);
}
// Click-triggered reveal
export function ClickReveal() {
return (
<BlockReveal trigger="manual" bgColor="#2D3436">
<div className="p-8 bg-white border rounded-lg">
<p>Click to reveal!</p>
</div>
</BlockReveal>
);
}
// Staggered reveals with delays
export function StaggeredReveal() {
const items = ["First", "Second", "Third", "Fourth"];
return (
<div className="space-y-4">
{items.map((item, i) => (
<BlockReveal key={item} delay={i * 150} bgColor="#7f40f1">
<div className="p-4 bg-gray-100 rounded">{item}</div>
</BlockReveal>
))}
</div>
);
}