Hello dev.to community!
In this post, I want to share how we can create custom cursor and button animations in a React application using Framer Motion. Framer Motion is a powerful animation library for React, perfect for creating interactive animations that enhance user experience.
About the Project
This project is a simple React application that includes a custom cursor and various button animations. Our goal is to make user interactions more engaging and enjoyable. Check out the live demo below to see.
Demo
Getting Started
First, let's set up the project using Vite.
1. Setting up the Project
npm create vite@latest custom-cursor-framer-motion -- --template react
cd custom-cursor-framer-motion
npm install
npm run dev
2. Installing Framer Motion
Next, install Framer Motion:
npm install framer-motion
Creating a Custom Cursor
To customize the cursor, we use Framer Motion to create a motion.div that responds to mouse movements with animations. We'll also use Context API to manage the cursor animation variant state globally.
Creating Context
First, create a context to manage the cursor animation variants:
// src/components/CustomCursor.jsx
import { createContext, useState, useContext } from 'react';
const CursorContext = createContext();
export const useCursorContext = () => useContext(CursorContext);
export const CursorContextProvider = ({ children }) => {
const [initialCursorVariant, setInitialCursorVariant] = useState('');
const [animateCursorVariant, setAnimateCursorVariant] = useState('');
// This function allows for smooth transitions between cursor states
const animateCursor = (variant) => {
setInitialCursorVariant(animateCursorVariant);
setAnimateCursorVariant(variant);
};
return (
<CursorContext.Provider
value={{
initialCursorVariant,
setInitialCursorVariant,
animateCursorVariant,
setAnimateCursorVariant,
animateCursor,
}}
>
{children}
</CursorContext.Provider>
);
};
Custom Cursor Component
Here's the code for the Cursor component, which uses Framer Motion for animations and manages the cursor's position and appearance:
// src/components/CustomCursor.jsx
import { motion, useMotionValue } from 'framer-motion';
import { useEffect } from 'react';
import '../assets/style/custom-cursor.css';
import { useCursorContext } from '../components/CursorContext.jsx';
function Cursor() {
const { initialCursorVariant, animateCursorVariant, animateCursor } = useCursorContext();
const cursorX = useMotionValue();
const cursorY = useMotionValue();
const variants = {
cursorEnter: {
border: '1px solid #eeff00',
boxShadow: '0 0 1px 0px #eeff00 inset, 0 0 1px 0px #eeff00',
scale: 2,
borderRadius: '50%',
backgroundColor: 'transparent',
transition: {
duration: 0.2,
},
},
cursorLeave: {
scale: 0,
border: 0,
backgroundColor: 'transparent',
transition: {
duration: 0.2,
},
},
buttonHover: {
scale: 1,
backgroundColor: '#eeff00',
borderRadius: '50%',
},
};
useEffect(() => {
const app = document.querySelector('#app');
const mouseMoveHandler = (e) => {
cursorX.set(e.clientX - 12);
cursorY.set(e.clientY - 12);
};
const mouseEnterHandler = () => {
animateCursor('cursorEnter');
};
const mouseLeaveHandler = () => {
animateCursor('cursorLeave');
};
window.addEventListener('mousemove', mouseMoveHandler);
app.addEventListener('mouseenter', mouseEnterHandler);
app.addEventListener('mouseleave', mouseLeaveHandler);
return () => {
window.removeEventListener('mousemove', mouseMoveHandler);
app.removeEventListener('mouseenter', mouseEnterHandler);
app.removeEventListener('mouseleave', mouseLeaveHandler);
};
}, [animateCursor, cursorX, cursorY]);
return (
<motion.div
className="cursor"
variants={variants}
initial={initialCursorVariant}
animate={animateCursorVariant}
style={{
translateX: cursorX,
translateY: cursorY,
transformOrigin: 'center',
}}
/>
);
}
export default Cursor;
Explanation:
- useMotionValue: This hook from Framer Motion is used to create motion values that will be updated with the cursor's position.
- variants: Defines different styles for cursor states such as when entering a certain area or hovering over buttons.
- useEffect: Sets up event listeners for mouse movement and cursor interactions. It updates the cursor's position and changes its appearance based on the cursor's state.
- motion.div: This is the animated cursor element, styled and animated according to the current cursor state.
Button Animations
Defining animations for buttons is also straightforward. Here's an example of a button that plays animations when hovered over or clicked:
// src/components/ButtonHover.jsx
import { motion } from 'framer-motion';
import '../assets/style/button-hover.css';
import { useCursorContext } from '../components/CursorContext.jsx';
function ButtonHover() {
const { animateCursor } = useCursorContext();
const mouseEnterHandler = () => {
animateCursor('buttonHover');
};
const mouseLeaveHandler = () => {
animateCursor('cursorEnter');
};
return (
<motion.button
className="button-hover"
onMouseEnter={mouseEnterHandler}
onMouseLeave={mouseLeaveHandler}
initial={{
backgroundColor: '#000',
color: '#fff',
}}
whileHover={{
backgroundColor: '#eeff00',
color: '#000',
}}
whileTap={{
scale: 0.9,
}}
>
Hover and Click Me!
</motion.button>
);
}
export default ButtonHover;
Explanation:
- useCursorContext: Retrieves the animateCursor function from the Cursor Context to change the cursor's appearance based on button interaction.
- mouseEnterHandler: When the mouse enters the button area, the cursor animation is set to buttonHover.
- mouseLeaveHandler: When the mouse leaves the button area, the cursor animation is reverted to cursorEnter.
- motion.button: This button element uses Framer Motion for animations, including hover and click effects. It changes its background color and scales down slightly on click.
App Component
Here's the code for the main App component which integrates all parts of the application:
// src/App.jsx
import './App.css';
import CustomCursor from './components/CustomCursor.jsx';
import ButtonHover from './components/ButtonHover.jsx';
import { CursorContextProvider } from './components/CursorContext.jsx';
function App() {
return (
<div id="app">
<CursorContextProvider>
<CustomCursor />
<div className="center">
<ButtonHover />
</div>
</CursorContextProvider>
</div>
);
}
export default App;
Explanation:
- CursorContextProvider: Wraps the application to provide cursor context to all child components, ensuring the custom cursor's position and animations are managed globally.
- CustomCursor: The component that renders and animates the custom cursor.
- ButtonHover: A button component with hover and click animations.
CSS Files
For styling, the project includes the following CSS files:
- src/assets/style/custom-cursor.css: Contains the styles for the custom cursor.
- src/assets/style/button-hover.css: Contains the styles for the button hover effect.
- src/App.css: General styling for the application layout.
You can explore these CSS files directly in the demo project to understand how the styles are applied and customize them as needed.
Live Demo and Code
You can check out the complete project and live demo via the following StackBlitz link:
Feel free to share your thoughts and questions about this project in the comments! Happy coding! 🎉
Top comments (2)
I genuinely like the hover effect and the cursor's design. I have also used it on my website. It's fully functional and well documented. Thank you for the study
Thanks ✨