DEV Community

Cover image for Create Stunning Custom Cursor Animations with Framer Motion
Bora Açıcı
Bora Açıcı

Posted on

Create Stunning Custom Cursor Animations with Framer Motion

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
Enter fullscreen mode Exit fullscreen mode

2. Installing Framer Motion

Next, install Framer Motion:

npm install framer-motion
Enter fullscreen mode Exit fullscreen mode

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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:

StackBlitz Project


Feel free to share your thoughts and questions about this project in the comments! Happy coding! 🎉


Top comments (2)

Collapse
 
aliozzaim profile image
Ali Ozzaim

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

Collapse
 
boraacici profile image
Bora Açıcı

Thanks ✨