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.
This service exceeded our expectations, since not only was the
development technically flawless, but Manu and his team also acted as
strategic partners by encouraging us to add ...
Georg Weingartner
CMO at Renderwork