Diona Rodrigues

How to create a custom cursor follower with GSAP

In this article I will show you how I created a custom cursor follower for this personal website using GSAP, a famous JavaScript animation library.

Posted on Oct 29, 2023 11 min to read
#Development
Diona Rodrigues
Diona Rodrigues - they/she
Front-end designer
How to create a custom cursor follower with GSAP

I've seen a lot of websites using custom cursors for a while now and I was very curious to know how they usually do it. What I discovered is that one of the ways to achieve this effect is using the GSAP library.

Check out the Codepen result with vanilla JS here.

What's the GSAP library?

As they describe themselves, it's a "a wildly robust JavaScript animation library built for professionals".

GSAP allows you to effortlessly animate anything JS can touch. Delivering silky-smooth performance and unmatched support so you can focus on the fun stuff. - GSAP

How to create a cursor follower

Of course, there are other ways to create this effect, but here we'll focus on developing it using GSAP with Vanilla JS and also with React.

Let's start with CSS

For the Vanilla JS and React implementation, we are using the same styles, which you can check below.

.cursor-follower {
  position: fixed;
  top: 0;
  left: 0;
  width: 20px;
  height: 20px;
  border-radius: 100%;
  background-color: rgba(144, 70, 254, 0.6);
  opacity: 0;
  z-index: 10000;
  user-select: none;
  pointer-events: none;
}

I believe this code is self-explanatory but I also have some additional notes for a better understanding:

  • z-index: the high number will make sure the cursor element is over all elements
  • user-select: when set as none, it will prevent the element to be selected. A use case scenario is when the user is trying to select some text for example.
  • pointer-events: this prevents the element from being the target of pointer events when set to none. By using this style we guarantee that the element will not cause bugs when the user tries to click on links and buttons.

Implementation using GSAP with Vanilla JS

In order to use GSAP with Vanilla JS we need to import its scripts from the CDN.

Example:

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>

Check more options to import GSAP from its docs.

Besides that, you need an HTML element containing the class cursor-follower.

Code:

// Check if it's a touch device
const isTouchDevice = 'ontouchstart' in window;

const createCursorFollower = () => {
  const $el = document.querySelector('.cursor-follower');

  // Each time the mouse coordinates are updated,
  // we need to pass the values to gsap in order
  // to animate the element
  window.addEventListener('mousemove', (e) => {
    const { target, x, y } = e;
    // Check if target is inside a link or button
    const isTargetLinkOrBtn = target?.closest('a') || target?.closest('button');
    // GSAP config
    gsap.to($el, {
      x: x + 3,
      y: y + 3,
      duration: 0.7,
      ease: 'power4', // More easing options here: https://gsap.com/docs/v3/Eases/
      opacity: isTargetLinkOrBtn ? 0.6 : 1,
      transform: `scale(${isTargetLinkOrBtn ? 3 : 1})`,
    });
  });

  // Hidding the cursor element when the mouse cursor
  // is moved out of the page
  document.addEventListener('mouseleave', (e) => {
    gsap.to($el, {
      duration: 0.7,
      opacity: 0,
    });
  });
};

// Only invoke the function if isn't a touch device
if (!isTouchDevice) {
  createCursorFollower();
}

Some explanations for better understanding of this JavaScript snippet code (cursor element is the custom element that will follow the mouse cursor):

  • isTouchDevice check if it's a touch device to apply the effect only when it's not touchable
  • the cursor element reference is stored in a variable to avoid selecting it whenever we need it
  • so we have two different event listeners: mousemove() will animate the cursor element according to the mouse cursor position (x and y), while mouseleave() will do it when the mouse cursor is moved off the page
  • isTargetLinkOrBtn is where we store the information if the mouse cursor is over some link or button as we use it to make the cursor element a bit different in these cases. So target?.closest('a') means: if target exists, check if it has an a as a parent or any ancestors.
  • then we use the power of GSAP when we declare gsap.to($el, {...}). Basically, it uses the to method to set the destination values of the cursor element animation. This is how we move the element through the viewport. This method expects the target whose properties will be animated (in this case the cursor element) and an object containing all the properties to be animated.

For target selection, GSAP uses document.querySelectorAll internally. So you can use ".class", "#id", etc.

ease property is the way GSAP changes the look and feel of our animations. You can play with it in the Easy visualizer in the GSAP documentation.

Implementation using GSAP with React

In order to use GSAP with React we need to import its Node dependency from NPM.

Check out all options to import GSAP from its docs.

Code:

import { useRef, useEffect } from 'react';
import gsap from 'gsap';

import './styles.css';

export default function CursorFollower() {
  // Using useRef() to store the cursor element reference
  const cursorRef = useRef(null);
  // Check if it's a touch device
  const isTouchDevice = 'ontouchstart' in window;

  // useEffect() to only execute this code
  // when the HTML is ready as well as the element reference
  useEffect(() => {
    const cursor = cursorRef.current;

    // If device is touchable or cursor element
    // doesn't exist, stop here
    if (isTouchDevice || !cursor) {
      return;
    }

    // Using mousemove() to animate the element cursor
    // based on the mouse cursor position (x and y)
    window.addEventListener('mousemove', (e) => {
      const { target, x, y } = e;
      // check if the mouse cursor is over some link or button
      const isTargetLinkOrBtn =
        target?.closest('a') || target?.closest('button');
      // using the GSAP power to animate some properties
      gsap.to(cursor, {
        x: x + 3,
        y: y + 3,
        duration: 0.7,
        ease: 'power4',
        opacity: isTargetLinkOrBtn ? 0.6 : 1,
        transform: `scale(${isTargetLinkOrBtn ? 3.5 : 1})`,
      });
    });

    // Using mouseleave() to animate the element cursor
    // when the mouse cursor is moved off the page
    document.addEventListener('mouseleave', () => {
      gsap.to(cursor, {
        duration: 0.7,
        opacity: 0,
      });
    });
  }, []);

  return <S.Cursor ref={cursorRef} />;
}

If you need more explanation about this code, you can check the notes in the section above related to Implementation with Vanilla JS.

Conclusion

I hope you learned how simple it is to implement a custom cursor follower using GSAP animation library with Vanilla JS and also with React. GSAP is a very famous animation library that you can also use to build advanced animations using SVG or even combined with WebGL to create 3D animations.

See you next time! 😁