A simple cursor effect that follows your mouse.
If you only came for the code here is the repo.
Inspired by Minh Pham portfolio.
Initializing the project
Create a simple Nextjs project with TailwindCSS
npx create-next-app
Then we can create two elements, each one with the same height and one of our elements must have an absolute position so we can make them overlap each other. I have added some style just to make it fancy, but heres the resulting code of our page.tsx
export default function Home() {
return (
<main className="h-screen cursor-default">
<div className="w-full h-full flex items-center justify-center text-black bg-red-500">
<h1 className="text-9xl font-light">I'm a Software Developer</h1>
</div>
<div className="w-full h-full flex items-center justify-center bg-black text-red-500">
<h1 className="text-9xl font-light">I don't know what am I doing</h1>
</div>
</main>
)
}
Now, our mouse position hook, if you don't know about hooks then check this.
The resulting code of our mousePosition.js
import { useState, useEffect } from 'react'
type Position = {
x: number | null
y: number | null
}
const useMousePosition = (): Position => {
const [mousePosition, setMousePosition] = useState<Position>({
x: null,
y: null,
})
const updateMousePosition = (e: MouseEvent) => {
setMousePosition({
x: e.clientX + window.scrollX,
y: e.clientY + window.scrollY,
})
}
useEffect(() => {
window.addEventListener('mousemove', updateMousePosition)
return () => {
window.removeEventListener('mousemove', updateMousePosition)
}
}, [])
useEffect(() => {
window.addEventListener('mousemove', updateMousePosition)
}, [mousePosition])
return mousePosition
}
export default useMousePosition
The mouse position is tracked every time the mousemove event is triggered by our useEffect(), and we store it into our useState().
We also calculate the window scroll to be used with a scrollable element.
Moving foward let's get back to our page.tsx and continue... But before, we need Framer Motion to handle the animations.
npm i framer-motion
Now, we need to use the directive 'use client', because our component is now a client component, so let's place it at the beginning of our file. Let's implement Framer Motion along with our hook and add some css because Tailwind doesn't have a good support for masks yet.
Our page.tsx
'use client';
import useMousePosition from "@/hooks/useMousePosition"
import { motion } from 'framer-motion';
import { useState } from "react";
import styles from './mask.module.css'
export default function Home() {
const [isHovered, setIsHovered] = useState(false)
const [maskScale, setMaskScale] = useState(50)
const { x, y } = useMousePosition()
const size = isHovered ? maskScale * 10 : maskScale
const handleMouseToggle = (scale: number, hover: boolean): void => {
setMaskScale(scale)
setIsHovered(hover)
}
return (
<main className="h-screen cursor-default">
<motion.div
animate={{
WebkitMaskPosition: `${Number(x) - size / 2}px ${
Number(y) - size / 2
}px`,
WebkitMaskSize: `${size}px`,
}}
transition={{ type: 'tween', ease: 'backOut', duration: 0.5 }}
className={`${styles.mask} absolute w-full h-full flex items-center justify-center text-black bg-red-500`}>
<h1
onMouseEnter={() => handleMouseToggle(50, true)}
onMouseLeave={() => handleMouseToggle(50, false)}
className="text-9xl font-light">I'm a Software Developer</h1>
</motion.div>
<div className="w-full h-full flex items-center justify-center bg-black text-red-500">
<h1 className="text-9xl font-light">I don't know what am I doing</h1>
</div>
</main>
)
}
And our mask.module.css
.mask {
mask-image: url('../../public/mask.svg');
mask-repeat: no-repeat;
mask-size: 40px;
position: absolute;
}
And that's the final result.
You can see the demo here

Top comments (0)