A Pointed Look At Custom Cursors in Javascript

See the Pen Custom Cursors.

Custom > Common

As a design element, cursors can get overlooked. Most operating systems and browsers have a few different cursor styles baked in. Links get a hand 👆to let you know they're clickable; text areas and input boxes get a styled caret to let you know you can type; draggable elements get variations on arrows and plus signs to let you know you can move and resize them. And on and on it goes.

These context clues do a pretty good job of subconsciously telling the user that these items are interactive, but we had questions. How much could we communicate through a 20px icon that represents a user's mouse movement? Could we add a quality of surprise or even delight?

We had fun adding custom cursors to last year's Annual Report, and on Haute Living's carousel, but when we started designing a new site for an existing client, we wanted to see if we could take the custom cursor to a whole different level.

cursor: url('sparkly-trail.gif');

Custom cursors have been readily available since CSS2. Hell, you probably even tricked out your GeoCities page with a sparkly trail cursor to add some flair to your page while people signed your guestbook. While you could spend hours browsing the amazing assortment of options the internet has to offer, there's only so much a custom cursor image can achieve.

  1. You can't animate them (only old versions of IE support animated gifs)
  2. You can't transition from one cursor into another
  3. You can't add special effects when a user clicks the mouse

As a studio, we're not particularly large fans of "can't." So we left the idea of a custom CSS cursor image behind. In its place we wrote a simple javascript module with an incredible amount of flexibility built right in.

Javascript to the Rescue

Using javascript to build custom cursors is actually pretty simple. First up, add a mousemove event listener, and update the transform3d property of a <div> that is already on the page. Then simply add some classes to different elements—like carousels, modals, and navigation. When a user hovers over those elements, our fake cursor <div> gets a new class and we get to decide how to style it. Bam.

The flexibility of simply adding classes to—and removing classes from—a <div> means we can use CSS transitions to animate and style our cursor any way we want. Show the cursor as a chevron animating to the right? Easy. Rotate and scale an "X" when a user intends to close a modal? No prob. This system is extremely extensible and customizable. And it adds a bit of a smile in an unexpected way. It's a little nod to design and user experience without being too heavy handed. A finer point, if you will. Am I Right?


export default class Cursor { constructor(el) { var $cursor = $('.js-cursor-el'); var $body = $('body'); var cursorSize = '80'; var mouseIsDown = false, lastMousePosition = { x: 0, y: 0 }; // Add a class when the users clicks / holds the mouse down function onMouseDown() { $cursor.addClass('is-mouse-down'); mouseIsDown = true; requestAnimationFrame(update); } // Remove the class when the user is no longer clicking / holding the mouse down function onMouseUp() { $cursor.removeClass('is-mouse-down'); mouseIsDown = false; requestAnimationFrame(update); } // Update the mouse position function onMouseMove(evt) { lastMousePosition.x = evt.clientX; lastMousePosition.y = evt.clientY; requestAnimationFrame(update); } function update() { // Get the element we're hovered on var hoveredEl = document.elementFromPoint(lastMousePosition.x, lastMousePosition.y); // Check if the element or any of its parents have a .js-cursor class if ($(hoveredEl).parents('.js-cursor').length || $(hoveredEl).hasClass('js-cursor')) { var $whichCursor = $(hoveredEl).parents('.js-cursor').attr('data-cursor') || $(hoveredEl).attr('data-cursor'); $body.addClass('is-cursor-active'); $cursor.addClass('is-active'); $cursor.attr('data-cursor', $whichCursor); } else { $body.removeClass('is-cursor-active'); $cursor.removeClass('is-active'); } // now draw object at lastMousePosition $cursor.css({ 'transform': `translate3d(${lastMousePosition.x}px, ${lastMousePosition.y}px, 0)` }); } // Listen for clicks document.addEventListener('mousedown', onMouseDown, false); // Listen for mouseup document.addEventListener('mouseup', onMouseUp, false); // Listen for mouse movement document.addEventListener('mousemove', onMouseMove, false); // Make sure a user is still hovered on an element when they start scrolling document.addEventListener('scroll', update, false); } };
The author's avatar

One Design Company is a research-driven design and development studio. For over a decade, we’ve explored the intersection of experience and technology—where powerful brands come to life. Want to learn more? Let’s chat.

Most Recent Issue