SVG Mask Effect
A mask reveal effect, hover the cursor over a container to reveal what's underneath.
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.
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 framer-motion clsx tailwind-merge
Add util file
lib/utils.ts
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
Add mask in public folder
public/mask.svg
<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 "framer-motion";
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("h-screen relative", className)}
animate={{
backgroundColor: isHovered ? "var(--slate-900)" : "var(--white)",
}}
>
<motion.div
className="w-full h-full flex items-center justify-center text-6xl absolute bg-black bg-grid-white/[0.2] text-white [mask-image:url(/mask.svg)] [mask-size:40px] [mask-repeat:no-repeat]"
animate={{
maskPosition: `${mousePosition.x - maskSize / 2}px ${
mousePosition.y - maskSize / 2
}px`,
maskSize: `${maskSize}px`,
}}
transition={{
duration: 0,
}}
>
<div className="absolute inset-0 bg-black h-full w-full z-0 opacity-50" />
<div
onMouseEnter={() => {
setIsHovered(true);
}}
onMouseLeave={() => {
setIsHovered(false);
}}
className="max-w-4xl mx-auto text-center text-white text-4xl font-bold relative z-20"
>
{children}
</div>
</motion.div>
<div className="w-full h-full flex items-center justify-center text-white">
{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 |