SVG Mask Effect
A mask reveal effect, hover the cursor over a container to reveal what's underneath.
Discover the power of Tailwind CSS v4 with native CSS variables and container queries withadvanced animations.
The first rule of MRR Club is you do not talk about MRR Club. The second rule of MRR Club is you DO NOT talk about MRR Club.
Installation
Install dependencies
npm i motion clsx tailwind-merge
Add util file
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
Add mask in public folder
<svg
width="1298"
height="1298"
viewBox="0 0 1298 1298"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="649" cy="649" r="649" fill="black" />
</svg>
Copy the source code
components/ui/svg-mask-effect.tsx
"use client";
import { useState, useEffect, useRef } from "react";
import { motion } from "motion/react";
import { cn } from "@/lib/utils";
export const MaskContainer = ({
children,
revealText,
size = 10,
revealSize = 600,
className,
}: {
children?: string | React.ReactNode;
revealText?: string | React.ReactNode;
size?: number;
revealSize?: number;
className?: string;
}) => {
const [isHovered, setIsHovered] = useState(false);
const [mousePosition, setMousePosition] = useState<any>({ x: null, y: null });
const containerRef = useRef<any>(null);
const updateMousePosition = (e: any) => {
const rect = containerRef.current.getBoundingClientRect();
setMousePosition({ x: e.clientX - rect.left, y: e.clientY - rect.top });
};
useEffect(() => {
containerRef.current.addEventListener("mousemove", updateMousePosition);
return () => {
if (containerRef.current) {
containerRef.current.removeEventListener(
"mousemove",
updateMousePosition,
);
}
};
}, []);
let maskSize = isHovered ? revealSize : size;
return (
<motion.div
ref={containerRef}
className={cn("relative h-screen", className)}
animate={{
backgroundColor: isHovered ? "var(--slate-900)" : "var(--white)",
}}
transition={{
backgroundColor: { duration: 0.3 },
}}
>
<motion.div
className="absolute flex h-full w-full items-center justify-center bg-black text-6xl [mask-image:url(/mask.svg)] [mask-repeat:no-repeat] [mask-size:40px] dark:bg-white"
animate={{
maskPosition: `${mousePosition.x - maskSize / 2}px ${
mousePosition.y - maskSize / 2
}px`,
maskSize: `${maskSize}px`,
}}
transition={{
maskSize: { duration: 0.3, ease: "easeInOut" },
maskPosition: { duration: 0.15, ease: "linear" },
}}
>
<div className="absolute inset-0 z-0 h-full w-full bg-black opacity-50 dark:bg-white" />
<div
onMouseEnter={() => {
setIsHovered(true);
}}
onMouseLeave={() => {
setIsHovered(false);
}}
className="relative z-20 mx-auto max-w-4xl text-center text-4xl font-bold"
>
{children}
</div>
</motion.div>
<div className="flex h-full w-full items-center justify-center">
{revealText}
</div>
</motion.div>
);
};
Props
Prop name | Type | Description |
---|---|---|
className | string | The class name of the Background Beams component. |
children | string | ReactNode | The content of the page that you want to keep static / always on the page |
revealText | string | ReactNode | The component that is revealed on hover |
size | number | size of the mask |
revealSize | number | Hovered over size of the mask |
Get beautiful, hand-crafted templates and components with Aceternity UI Pro
Professional, beautiful and elegant templates for your business. Get the best component packs and templates with Aceternity UI Pro.
Manu was quick to respond, very professional, and delivered a website within a week. Very good job. Looking forward to collaborating again
Asriel Han
Founder, CTO at Advex AI