Creating A Custom Cursor Follower

Code
20 February 2025
Demo

We're going to create a custom cursor follower. I love these kinds of UI elements that add that extra bit of magic to our websites, while potentially allowing us to clean up a bit.

For example, this cursor follower pops up over a swiper section and lets us know we can drag and swipe. This allows us to omit the swiper arrows, indicators etc.

Admittedly, it's not always the case that we do want to omit them, but still, a good opportunity to build a cursor follower!

The HTML 🏗️

First, let's add a container for our cursor:

<div class="cursor">
<div class="cursor_content">
<span>Drag Me!</span>
</div>
</div>

Alternatively, you should be able to add the cursor in the DOM using JS using `createElement`.

The Styling Magic ✨

Now, let's add some CSS that makes our cursor look smooth:

.cursor {
width: 80px;
height: 80px;
border-radius: 50%;
position: fixed;
transform-origin: center;
pointer-events: none !important;
z-index: 100;
transition:
transform 0.4s cubic-bezier(0.195, 1, 0.225, 1),
opacity 1.4s cubic-bezier(0.195, 1, 0.225, 1);
transform: scale(0);
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
color: #fff;
background-color: #000;
}

.cursor_content {
transition:
transform 0.5s cubic-bezier(0.19, 1, 0.22, 1),
opacity 1.4s linear;
opacity: 0;
transition-delay: 0.15s;
}



The JavaScript Wizardry 🧙‍♂️

Here's where the real magic happens. We'll create a function that brings our cursor to life:

function initCursorFollower(lastMouseX = 0, lastMouseY = 0) {
const cursor = document.querySelector('.cursor')
const cursorContent = document.querySelector('.cursor_content')
const hoverSection = document.querySelector('#hero-slider .swiper-wrapper')

const cursorWidth = 80
const cursorHeight = 80
let isHovering = false

const updateCursorPosition = (x, y) => {
const translateX = x - cursorWidth / 2
const translateY = y - cursorHeight / 2
cursor.style.transform = `translate(${translateX}px, ${translateY}px) scale(${isHovering ? 1 : 0})`
}

document.addEventListener('pointermove', function (event) {
lastMouseX = event.clientX
lastMouseY = event.clientY
updateCursorPosition(lastMouseX, lastMouseY)
})

document.addEventListener('scroll', function (event) {
lastMouseX = event.clientX
lastMouseY = event.clientY
updateCursorPosition(lastMouseX, lastMouseY)
})

hoverSection.addEventListener('mouseenter', function () {
isHovering = true
cursor.style.opacity = '1'
cursor.style.transform = `translate(${lastMouseX}px, ${lastMouseY}px) scale(1)`
cursorContent.style.opacity = '1'
cursorContent.style.transform = 'scale(1)'
})

hoverSection.addEventListener('mouseleave', function () {
isHovering = false
cursor.style.transform = `translate(${lastMouseX}px, ${lastMouseY}px) scale(0)`
cursor.style.opacity = '0'
cursorContent.style.opacity = '0'
cursorContent.style.transform = 'scale(0)'
})
}


The main ideas are:

  1. Function update the cursor position: Use the cursor height and width to make sure the follower is placed with the cursor at its center

  2. Event listeners to make sure the follower has the position of the cursor at all times. Besides an event on 'pointermove', we are updating the position on the 'scroll' event as well. We don't want to scroll while the follower is visible and misplace it.

  3. Section specific: Add event listeners on the section we want the follower to appear.

  4. Styling: Animations for cursor content



Pro Tips 💡

  1. Performance Matters: We're using transform instead of changing top and left properties because it's more performant.

  2. Smooth Transitions: Those fancy cubic-bezier transitions? They make everything butter-smooth. Play around with the values to find your perfect animation.



Making It Your Own 🎨

Feel free to customize this! You could:

  • Change the cursor size

  • Add different hover effects

  • Modify the transition timing

  • Add different cursor styles for different sections



Wrapping Up 🎁

And there you have it! A cursor follower that'll make your website feel more interactive and engaging.

Happy coding! 🚀


P.S.: If you're using this with React or Vue, you might want to adjust the event listeners to work with your framework's lifecycle methods.

Want to see an example? Demo