/
accessorizing the cursor with js

Note: I no longer use this effect on the site. While I liked a lot about it, ultimately the lag of the shadow behind the real cursor made the site feel sluggish.
With iPad OS 13, Apple finally announced mouse and touchpad support. But the way we use a mouse or trackpad is fundamentally different than the way we use touch. To bridge this gap, Apple developed a cursor that snaps to interactable items and adjusts to content. Tech Crunch’s article does a deep dive on it.
Similarly, to highlight interactable items on my site, I wanted to create a custom cursor that would adapt to the content being hovered over. But building a custom cursor for the web has its own set of considerations, chief among them, accessibility.
What’s out there
There are a few ways to modify the cursor on a webpage. CSS even has a built-in way to specify an image as the cursor.
* {
cursor: url('path-to-image.png') x-cord y-cord, auto;
}
Unfortunately, it kinda sucks. The image you specify is static and can’t be animated. Not to mention that it removes the existing OS defined cursor, which is a huge hit to accessibility.
In the world of custom JS cursors, the standard practice is to remove the existing cursor with CSS and use JS to draw a new one in its place. Not only is it bad for accessibility, but it’s noticeably more sluggish than the default cursor, making the site feel slower.
My approach
Instead of replacing the cursor altogether, let’s try and add something to it. For this, we’ll need some JS to keep track of the mouse position on the page and move a custom cursor element accordingly.
Within each page of the site we can add our cursor element.
...
<div class="cursor"></div>
...
And we’ll style it as a semi-transparent circle to start.
.cursor {
display: none; /* starts off hidden */
width: 25px;
height: 25px;
background-color: rgba(0, 0, 0, 0.12);
border-radius: 13px;
position: fixed;
transform: translate(-50%, -50%); /* offsets the div to its center */
pointer-events: none;
z-index: 999;
transition: 0.15s ease; /* makes the custom cursor "follow" the real one */
}
Now for the fun part. With some JS, we can make our custom cursor follow the real one and animate over interactable elements. The code is pretty messy, but I’ve tried to comment it as best as I can.
jQuery(document).ready(function () { // Wait for the DOM to load
cursor = document.querySelector(".cursor"); // Get the cursor element
cursor.style.display = "inline-block"; // Make it visible
cursor.style.width = "25px"; // Set it's starting dimentions
cursor.style.height = "25px";
hovered = false; // Create a boolean for whether it's hovering over something clickable
itemX = 0; // Two values for the position of the clickable element
itemY = 0;
window.addEventListener("mousemove", update);
function update(e) {
x = e.clientX; // Each time the mouse moves, get its new position
y = e.clientY;
if (!hovered) { // If it's not hovering over something, just make it follow the real cursor
cursor.style.top = y + "px";
cursor.style.left = x + "px";
} else { // If it is hovering, snap it to the position of the hovered element
cursor.style.top = itemY + "px";
cursor.style.left = itemX + "px";
}
}
Array.from(document.querySelectorAll("a")).forEach((item) => { // Do this for every clickable element (just <a> tags on my site)
item.addEventListener("mouseenter", function (event) { // Create a listener for when the mouse starts hovering
itemRect = item.getBoundingClientRect(); // Get the bounding box of the element and find its center
itemX = itemRect.left + item.offsetWidth / 2.0;
itemY = itemRect.top + item.offsetHeight / 2.0;
hovered = true; // Set the hovered flag
cursor.style.width = item.offsetWidth + 12 + "px"; // Make the cursor the size of the element, plus 12px of padding
cursor.style.height = item.offsetHeight + 12 + "px";
}, false);
item.addEventListener("mouseleave", function (event) {
hovered = false; // When the mouse leaves, set the flag back and reset the cursor size
cursor.style.width = "25px";
cursor.style.height = "25px";
}, false);
});
window.addEventListener('scroll', function () {
hovered = false; // If the user scrolls, reset everything as well so the cursor doesn't stay big
cursor.style.width = "25px";
cursor.style.height = "25px";
}, true);
document.addEventListener("mousedown", function (event) { // When the mouse is pressed, make the cursor 8px smaller
cursor.style.width = parseInt(cursor.style.width, 10) - 8 + "px";
cursor.style.height = parseInt(cursor.style.height, 10) - 8 + "px";
}, true);
document.addEventListener("mouseup", function (event) { // Make it bigger again when the mouse is released
cursor.style.width = parseInt(cursor.style.width, 10) + 8 + "px";
cursor.style.height = parseInt(cursor.style.height, 10) + 8 + "px";
}, false);
});
Last but not least, we can add some CSS to hide the custom cursor on touch devices.
@media (hover: none) {
.cursor {
display: none !important;
}
}
Here’s the final result
If I haven’t grown tired of it, the cursor should still be on my site now (Edit: I grew tired of it). With similar techniques, I’m sure there are endless possibilities for making sites feel more interactable, or just plain fun.