Getting Started
IntroductionLayout Components
Expandable PanelMask Reveal on Hover
Reveals alternate content through a circular mask that follows your cursor. The base content is shown dimmed; hovering grows the mask to reveal the overlay content. Uses CSS mask-image and GSAP for smooth position and size animations.
React
Tailwind CSS
GSAP
Interactive
Mask
CSS
Writing beautiful code means thinking like an artist and debugging like a detective. Every function is a story, every variable a character. Master your craft through practice, patience, and endless curiosity about how things work.
Building great software requires seeing beyond syntax into architecture and design. Test early, refactor often, document clearly. Success comes from collaboration, continuous learning, and caring deeply about user experience.
Using CLI
npx dimaac add MaskRevealOnHoverManual Installation
npm install react @gsap/reactlib/utils.ts
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
} components/ui/interactive/MaskRevealOnHover.tsx
'use client';
import React, { useRef } from 'react';
import { gsap } from 'gsap';
import { useGSAP } from '@gsap/react';
import { cn } from '@/lib/utils';
interface MaskRevealOnHoverProps {
originalContent: React.ReactNode;
maskContent: React.ReactNode;
maskSizeSmall?: number;
maskSizeLarge?: number;
maskBackground?: string;
className?: string;
}
const MaskRevealOnHover: React.FC<MaskRevealOnHoverProps> = ({
originalContent,
maskContent,
maskSizeSmall = 20,
maskSizeLarge = 100,
maskBackground = '#DDFC3E',
className,
}) => {
const containerRef = useRef<HTMLDivElement>(null);
const maskRef = useRef<HTMLDivElement>(null);
const contentRef = useRef<HTMLDivElement>(null);
useGSAP(
() => {
if (!containerRef.current || !maskRef.current || !contentRef.current) return;
const container = containerRef.current;
const mask = maskRef.current;
const content = contentRef.current;
const handleMouseMove = (e: MouseEvent) => {
const rect = container.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
gsap.to(mask, {
'--mask-x': `${x}px`,
'--mask-y': `${y}px`,
duration: 0.6,
ease: 'back.out(1.7)',
});
};
const handleMouseEnter = () => {
gsap.to(mask, {
'--mask-size': `${maskSizeLarge}px`,
duration: 0.4,
ease: 'power2.out',
});
};
const handleMouseLeave = () => {
gsap.to(mask, {
'--mask-size': `${maskSizeSmall}px`,
duration: 0.3,
ease: 'power2.in',
});
};
document.addEventListener('mousemove', handleMouseMove);
content.addEventListener('mouseenter', handleMouseEnter);
content.addEventListener('mouseleave', handleMouseLeave);
return () => {
document.removeEventListener('mousemove', handleMouseMove);
content.removeEventListener('mouseenter', handleMouseEnter);
content.removeEventListener('mouseleave', handleMouseLeave);
};
},
{ dependencies: [maskSizeSmall, maskSizeLarge] }
);
return (
<div
ref={containerRef}
className={cn(
'relative w-full flex items-center justify-center overflow-hidden cursor-none',
className
)}
>
<div ref={contentRef} className="relative z-0 flex items-center justify-center w-full">
{originalContent}
</div>
<div
ref={maskRef}
className="absolute inset-0 z-10 flex items-center justify-center"
style={{
background: maskBackground,
pointerEvents: 'none',
maskImage:
'radial-gradient(circle var(--mask-size, 20px) at var(--mask-x, -50px) var(--mask-y, -50px), black 100%, transparent 100%)',
WebkitMaskImage:
'radial-gradient(circle var(--mask-size, 20px) at var(--mask-x, -50px) var(--mask-y, -50px), black 100%, transparent 100%)',
} as React.CSSProperties}
>
{maskContent}
</div>
</div>
);
};
export default MaskRevealOnHover;