Create a custom cursor with CSS effects in iPadOS style

Ralph J. Smit Laravel Software Engineer

Custom cursors and hover effects have been advancing steadily on the web for the last two years. It makes a website more sophisticated and gives a certain elegance to it. I've been playing with custom animated cursors and hover effects and it turns out that it's really easy to create a custom animated cursor – without much impact on page load and without huge JS libraries.

In this tutorial, I'll show you how to create a custom animated cursor and implement that on your website.

Overview

What do we need to do to display a custom cursor? First, we'll create the HTML markup for the cursor and style it with CSS. Then, we'll track the position of the cursor with a simple bit of JavaScript and position our custom cursor correctly. Finally, we'll hide the default cursor.

  1. Creating the HTML & CSS markup

  2. Positioning the custom cursor

  3. Hiding the default cursor

  4. Adding hover effects

  5. Examples of CSS cursor effects

Creating the HTML & CSS markup

The HTML markup is very simple. We'll create a new element, with one child element, and give that a position absolute.

<div id="rjs_cursor" class="rjs-cursor">
<div class="rjs-cursor-icon"></div>
</div>

To style this, we have the following CSS:

.rjs-cursor {
position: fixed; /* Fixed position.. */
top: 0; /* at the top left.. */
left: 0;
z-index: 999999; /* above everything else. */
pointer-events: none; /* Cant't be clicked. */
transition: none; /* Cursor is always accurate, e.g. not delayed by a transition*/
opacity: 0; /* Hidden by default */
}
 
.rjs-cursor-icon { /* Styling for the visible part */
width: 12px;
height: 12px;
border-radius: 100%; /* Circle */
background-color: rgba(123, 123, 123, 0.7); /* Backup value */
}
 
/* Classes to show and hide the cursor */
.rjs-cursor.rjs_cursor_visible { opacity: 1; }
.rjs-cursor.rjs_cursor_hidden { opacity: 0; }

Positioning the custom cursor

We'll now use JavaScript to position the custom cursor. Our JavaScript is made up of several 'components'.

First, we'll get the cursor from the DOM and store the reference in a variable.

var rjs_cursor = document.getElementById("rjs_cursor"); //Getting the cursor

Then, we'll write a function that shows or hides the cursor. It adds/removes classes from the CSS in order to show or hide it. Sometimes it must be hidden, for example when the user opens the Browser Inspector. If you do not hide it, the custom cursor would remain visible at the side of the browser window.

function rjs_show_cursor(e) { //Function to show/hide the cursor
 
if(rjs_cursor.classList.contains('rjs_cursor_hidden')) {
rjs_cursor.classList.remove('rjs_cursor_hidden');
}
 
rjs_cursor.classList.add('rjs_cursor_visible');
 
}

Then, we'll register a function that determines the position of the cursor.

function rjs_mousemove(e) { //Function to correctly position the cursor
rjs_show_cursor(); //Toggle show/hide
 
var rjs_cursor_width = rjs_cursor.offsetWidth * 0.5; //The actual cursor is in the centre of the custom cursor
var rjs_cursor_height = rjs_cursor.offsetHeight * 0.5;
 
var rjs_cursor_x = e.clientX - rjs_cursor_width; //x-coordinate
var rjs_cursor_y = e.clientY - rjs_cursor_height; //y-coordinate
var rjs_cursor_pos = `translate(${rjs_cursor_x}px, ${rjs_cursor_y}px)`;
rjs_cursor.style.transform = rjs_cursor_pos;
}

Last, we'll attach an eventlistener to the event mousemove to call the positioning function.

window.addEventListener('mousemove', rjs_mouse); //Attach an event listener

<iframe id="cp_embed_poEQzMZ" src="//codepen.io/anon/embed/poEQzMZ?height=250&theme-id=1&slug-hash=poEQzMZ&default-tab=result" height="250" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest="" name="CodePen Embed poEQzMZ" title="CodePen Embed poEQzMZ" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe>

Showing and hiding the cursor at the correct times

To show and to hide the cursor at the correct time, I have two EventListeners. The second EventListener hides the mouse when it leaves the body element. The first EventListener fires when the mouse moves.

When the mouse enters the viewport again, the mousemove constantly fires, making sure that the cursor is visible at the correct time (by adding/removing CSS classes).

Hiding the default cursor

We can hide the default cursor with the following simple CSS:

* { cursor: none; }

But what about devices that don't have a cursor, like touch screens? To accommodate for that, we'll only hide the cursor on devices that have a pointing device which is also accurate (mouse, trackpad, etc.). We can do this with the CSS pointer media feature. This media query has one of three values:

  • none – the device doesn't have a pointing device.

  • coarse – the device has a pointing device of limited accuracy (things like gaming consoles or cars with which you can access the internet).

  • fine – the device has an accurate pointing device, like a mouse or a trackpad.

We only want to show our cursor on devices with an accurate pointing device, meaning that we'll only hide the default cursor on those devices.

@media (pointer: fine) {
* { cursor: none; }
}

But at the same time, we also need to hide our new cursor at those other devices. That's why I prefer to do this:

* { cursor: none; }
 
@media (pointer: none), (pointer: coarse) {
#rjs_cursor, #rjs_cursor .rjs-cursor-icon { display: none !important; visibility: hidden; opacity: 0; }
 
* { cursor: auto !important; }
}

And here we have our fully working custom cursor! 🥁

Why are the Codepen banners not working?

If you're viewing this page on a mobile device, the cursor will not be visible in the Codepen embed, because the Codepen acts like your browser. Thus, if you view this on a touch device, you should not see the cursor because it would be a redundant element without function.

Adding hover effects

Now that we have a fully functioning cursor, we can start to add hover effects.

//Hover behaviour
function rjs_hover_cursor(e) { rjs_cursor.classList.add('rjs_cursor_hover'); }
function rjs_unhover_cursor(e) { rjs_cursor.classList.remove('rjs_cursor_hover'); }
 
 
document.querySelectorAll('a').forEach(item => {
item.addEventListener('mouseover', rjs_hover_cursor);
item.addEventListener('mouseleave', rjs_unhover_cursor);
})

First, we'll register two functions. The first function adds a certain class to the rjs_cursor variable. The second one removes the class. Now, what we're doing is basically adding an EventListener to every link and to every item on the page that need to be hovered.

As you see, we'll select every link (every 'a'). Then we'll add two EventListeners. One addEventListener listens to the moment that the cursor moves over the element ('mouseover'). The other addEventListener listens to the moment that the mouse leaves the element ('mouseleave'), just like we did with the <body> element.

.rjs-cursor-icon {
/* Earlier styles */
transition: all 0.2s ease; /* Short & sweet animation */
transform-origin: 50% 50%;
/* When we use the transform property to e.g. scale an animation, we'll do it from the center and not the top left */
}
 
.rjs-cursor.rjs_cursor_hover .rjs-cursor-icon {
transform: scale(2);
}

These simple CSS styles result in the following great effect:

Adding hover effects to clickable elements that are not <a>

The above example works great for <a> elements. But on most websites, there will also be elements clickable that are not an <a>. Think of <input> elements and submit buttons in forms. Or an other element to open the mobile menu. To add the hover effect to them is really easy too.

For each (sort of) element you want the hover effect to appear on, add the following lines. You only need to change the parameter of the querySelectorAll. You can just use normal CSS selectors. Repeat for each element you want the hover effect for.

document.querySelectorAll('input').forEach(item => { //Input tags
item.addEventListener('mouseover', rjs_hover_cursor);
item.addEventListener('mouseleave', rjs_unhover_cursor);
})
 
document.querySelectorAll('button').forEach(item => { //Button tags
item.addEventListener('mouseover', rjs_hover_cursor);
item.addEventListener('mouseleave', rjs_unhover_cursor);
})
 
document.querySelectorAll('.mycustomclass').forEach(item => { //A custom class
item.addEventListener('mouseover', rjs_hover_cursor);
item.addEventListener('mouseleave', rjs_unhover_cursor);
})

Check out the below demo webpage, featuring the four sorts of elements I mentioned (<a>, <input>, <button> & '.mycustomclass').

Examples of CSS cursor effects

Now that you have the framework for creating custom cursors, you can creating different variants on the cursor. For example changing the colors, the opacity, adding a border, etc. Below I created a few variants of the cursor.

Cursor with different color

Cursor with border

A custom cursor like this gives a website a whole new look and feel. I've used it on several websites and it's a great way to enhance a site. Let me know if you have used this on a website!

Published by Ralph J. Smit on in Guides . Last updated on 10 March 2022 .