Stackbits is going through a refresh! Some features may not be available. Follow @stackbitss on twitter for updates.
Components > Mask Cursor Effect

Mask Cursor Effect

A dynamic and interactive React component that creates an engaging cursor-following mask effect using Framer Motion animations. This TypeScript component reveals hidden content through a smooth circular mask that tracks mouse movement, expanding on hover to create stunning reveal animations. Perfect for modern web applications seeking to enhance user engagement with creative UI interactions, spotlight effects, or content reveals. Built with performance in mind using CSS masks and optimized animations.

Meet the Designer

All the design credits goes to

Preview

I'm a "full-stack developer" powered by Stack Overflow and prayer. My code is 10% genius, 90% duct tape.
I'm a software engineer specializing in React, Next.js, and TypeScript. I build creative interfaces with solid backend logic.

Follow below steps 👇🏻

Install dependencies

npm i framer-motion

Component

Create a file mask-cursor-effect.tsx in your components folder and paste this code

'use client'; import React, { useEffect, useRef, useState } from 'react'; import { motion } from 'framer-motion'; import { cn } from '@/lib/utils'; const getMaskDataUrl = () => { const svgString = `<svg width="526" height="526" viewBox="0 0 526 526" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="263" cy="263" r="263" fill="black" /> </svg>`; return `data:image/svg+xml;base64,${btoa(svgString)}`; }; const MaskCursorEffect = ({ children, hiddenComponent, className, compressedMaskSize = 40, expandedMaskSize = 350 }: { children: React.ReactNode; hiddenComponent?: React.ReactNode; className?: string; compressedMaskSize?: number; expandedMaskSize?: number; }) => { const [mousePosition, setMousePosition] = useState({ x: 20, y: 20 }); const [isHovered, setIsHovered] = useState(false); const wrapperRef = useRef<HTMLDivElement>(null); const MASK_SIZE = isHovered ? expandedMaskSize : compressedMaskSize; useEffect(() => { const handleMouseMove = (e: MouseEvent) => { const { left, top } = wrapperRef.current?.getBoundingClientRect() || { left: 0, top: 0 }; setMousePosition({ x: e.clientX - left, y: e.clientY - top }); }; wrapperRef.current?.addEventListener('mousemove', handleMouseMove); return () => wrapperRef.current?.removeEventListener('mousemove', handleMouseMove); }, [wrapperRef]); return ( <div ref={wrapperRef} className="h-full w-full relative flex flex-col"> <motion.div animate={{ WebkitMaskPosition: `${mousePosition.x - MASK_SIZE / 2}px ${ mousePosition.y - MASK_SIZE / 2 }px`, maskSize: `${MASK_SIZE}px ${MASK_SIZE}px` }} style={{ maskImage: `url("${getMaskDataUrl()}")`, WebkitMaskImage: `url("${getMaskDataUrl()}")`, maskRepeat: 'no-repeat', WebkitMaskRepeat: 'no-repeat', backgroundColor: '#EA5A47', color: 'black' }} transition={{ type: 'tween', ease: 'backOut' }} className={cn( 'h-[800px] w-full flex items-center justify-center absolute z-10 ', className )} > <div onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}> {hiddenComponent} </div> </motion.div> <div className={cn('h-[800px] w-full flex items-center justify-center text-white/70', className)} > {children} </div> </div> ); }; export default MaskCursorEffect;

Usage

<div className="h-full w-full flex flex-col gap-5 items-center justify-center overflow-y-auto relative p-10"> <MaskCursorEffect hiddenComponent={ <div className="max-w-4xl text-7xl font-bold"> I'm a "full-stack developer" powered by Stack Overflow and prayer. My code is 10% genius, 90% duct tape. </div> } > <div className="max-w-4xl text-7xl font-bold"> I'm a software engineer specializing in React, Next.js, and TypeScript. I build creative interfaces with solid backend logic. </div> </MaskCursorEffect> </div>