How to create a custom cursor in React Part — 1

P.S — No modules were used while writing this code 😆

If you’re looking for the source code then it’s available in the video 😉

Please support by watching this video 😢

Basic HTML — CSS layout of the cursor

We’re gonna start with writing the JSX(HTML) for cursor dot and cursor outline.

We also created the ref for these elements so that we can access the DOM elements later on.

Next, we’re gonna add the CSS styling to these elements.

cursor: none is added to the universal selector * because we want to hide the default cursor.

pointer-events: none so that this element doesn’t become a target of the pointer-events. MDN pointer-events

position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);

This code snippet is used to move both elements in the centre.

opacity: 1; is used so that later on we can hide and show our cursor based on mouseenter and mouseleave event. That’s why transition is also used on the opacity property.

Transition on transform is used so that we can increase and reduce the size of the dot and outline based on some mouse events (will soon get to know which one 😉).

It’ll look something like this.

Adding images to show cursor hover effect

We’re gonna add some images to show that smooth cursor hover transition.

Created this <Link /> component which can be reused for displaying the images.

Now, we need to import this into our <Cursor /> component and pass the images.

We’ve added ref to its container because we’ll be adding the mouse events to all those images.

The getImage is a util function that I created in order to get the images based on the index value provided by the map function. You can find the function in the source code (not adding any image because it isn’t that important. Sorry getImage function! 😥).

Bonus Points 🤩

  • The import statement’s path

In order to use that I used jsconfig.json. You can add this file at the root of your project and add the baseUrl property inside compilerOptions.

  • The [...Array(4).keys()] code snippet

The Array(4) will create an empty array of size 4.

The keys() method returns a new Array Iterator object that contains the keys for each index in the array.

And spreading the array will give us an array — [0, 1, 2, 3]

Okay, let’s get back to what we were doing 😅.

The final thing is to add a bit of styling to our images container.

And now the final outcome will look something like this —

Adding Animation to our cursor

Finally, the time has come to add the animation to our cursor (poor cursor sitting alone at the centre of the screen 😥).

Adding ref variables

We need to add some more refs to our Cursor component so that we can update the changes.

delay will be used as a factor by which we will change how quickly or slowly the cursor outline should follow the cursor dot. A higher number means slow and vice versa.

cursorVisible will help in toggling the visibility of the cursor.

cursorEnlarged will help in toggling the size of the cursor.

endX is the X-position of the cursor dot. We will be saving the X position of the dot in this variable. It is initialised with window.innerWidth/2 because that’s mid of X-axis.

endY is the Y-position of the cursor dot. We will be saving the Y position of the dot in this variable. The concept is same as endX.

_x is the X-position of the cursor outline. We will be saving the X position of the outline in this variable. It’s initialised with 0 because we want to show movement animation. (The outline coming from the top left corner of the screen to the middle point where the dot is present at the start of the application).

_y is the Y-position of the cursor outline. We will be saving the Y position of the outline in this variable. The concept is same as _x.

requestRef will save an ID returned from one important function (will see it when we add an animation to the cursor outline).

Adding cursor toggle functions

  • toggleCursorVisibility — If the cursor is visible then we will change the opacity to 1 otherwise we will change it to 0.

  • toggleCursorSize — If the cursorEnlarged is true then we will decrease the size of the dot and increase the size of the outline. In case of a false, we will change it back to the original size.

Adding over, out, enter, leave mouse events

‘mouseover’ — The mouseover event is fired when the cursor comes on top of the element or its children.

‘mouseout’ — The mouseout event is fired when the cursor is not on top of the element or its children.

‘mouseleave’ — The mouseleave event is fired when we leave the page viewport.

‘mouseenter’ — The mouseenter event is fired when we enter the page viewport.

Now, we can pass mouseOverEvent and mouseOutEvent to our image component.

These functions will be called whenever the mouse cursor comes on top of the image.

Adding mousemove event

The first thing we need to make sure is that our cursor is visible, that’s why we’ve updated the currentVisible and called the toggleCursorVisibility function.

Then, we are taking the current position of the cursor with the help of pageX and pageY. The next step is to update the top and left property of the dot cursor to show it moving around on the page.

P.S — This StackOverflow answer has a very good explanation about the difference between clientX and pageX.

Adding cursor outline animation

This is a special recursive function that we call inside requestAnimationFrame function. In this function, we update the position of cursor outline with respect to the position of cursor dot.

The mathematical explanation for this is present in part 2 of the article.

This article is great to understand how requestAnimationFrame works and why it was created.

Calling all event functions

We can call all our event functions inside useEffect (componentDidMount). At the time of unmounting, we should remove all the event listeners and also cancel the requestAnimationFrame using cancelAnimationFrame function.

And with all these functions, our poor cursor can finally move 🥳

Some of the important links —

VanillaJS codepen for custom cursor — https://codepen.io/kjbrum/pen/qooQJJ

20