Docs
Svg Mask Effect
A mask reveal effect, hover the cursor over a container to reveal what's underneath.
Installation
Install dependencies
npm install framer-motion lucide-react
Update tailwind.config.js
Add the following to your tailwind.config.js file.
module.exports = {
theme: {
extend: {
}
}
}
Run the following command
It will create a new file svg-mask-effect.tsx
inside the components/mage-ui/hero
directory.
mkdir -p components/mage-ui/hero && touch components/mage-ui/hero/svg-mask-effect.tsx
Paste the code
Open the newly created file and paste the following code:
"use client";
import React, { useState, useRef } from "react";
import maskSvg from "@/public/mask.svg";
// Utility function for combining classnames
const cn = (...classes: (string | undefined)[]) => {
return classes.filter(Boolean).join(" ");
};
// MaskContainer Component
const MaskContainer = ({
children,
revealText,
size = 10,
revealSize = 600,
className
}: {
children?: React.ReactNode;
revealText?: React.ReactNode;
size?: number;
revealSize?: number;
className?: string;
}) => {
const [isHovered, setIsHovered] = useState(false);
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
const containerRef = useRef<HTMLDivElement>(null);
const updateMousePosition = (e: React.MouseEvent) => {
if (containerRef.current) {
const rect = containerRef.current.getBoundingClientRect();
setMousePosition({
x: e.clientX - rect.left,
y: e.clientY - rect.top
});
}
};
const maskSize = isHovered ? revealSize : size;
return (
<div
ref={containerRef}
className={cn("relative h-screen transition-colors duration-300", className)}
style={{
backgroundColor: isHovered ? "var(--slate-900)" : "var(--white)",
}}
onMouseMove={updateMousePosition}
>
<div
className="absolute flex h-full w-full items-center justify-center bg-black text-6xl dark:bg-white"
style={{
maskImage: `url(${maskSvg.src})`,
maskRepeat: "no-repeat",
maskSize: `${maskSize}px`,
WebkitMaskImage: `url(${maskSvg.src})`,
WebkitMaskRepeat: "no-repeat",
WebkitMaskSize: `${maskSize}px`,
maskPosition: `${mousePosition.x - maskSize / 2}px ${mousePosition.y - maskSize / 2}px`,
WebkitMaskPosition: `${mousePosition.x - maskSize / 2}px ${mousePosition.y - maskSize / 2}px`,
transition: "mask-size 0.3s ease-in-out, -webkit-mask-size 0.3s ease-in-out",
}}
>
<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>
</div>
<div className="flex h-full w-full items-center justify-center">
{revealText}
</div>
</div>
);
};
// Main component that uses the MaskContainer
const SVGMaskEffectDemo = () => {
return (
<div className="flex h-[40rem] w-full items-center justify-center overflow-hidden">
<MaskContainer
revealText={
<p className="mx-auto max-w-4xl text-center text-4xl font-bold text-slate-800 dark:text-white">
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.
</p>
}
className="h-[40rem] rounded-md border text-white dark:text-black"
>
Discover the power of{" "}
<span className="text-blue-500">Tailwind CSS v4</span> with native CSS variables
and container queries with{" "}
<span className="text-blue-500">advanced animations</span>.
</MaskContainer>
</div>
);
};
// Export the page component
export default function MaskEffectPage() {
return (
<div className="min-h-screen w-full bg-white dark:bg-slate-950">
<SVGMaskEffectDemo />
</div>
);
}