Working with mouseMove on the front-end

Articles|HΓ₯kon Underbakke | about 3 years ago

Mouse movement events can initially be really hard to wrap your head around, but bear with me, and follow along as I’m going to show you how simple it really is.

Let’s track mouse movements…

…Relative to the entire page πŸ’»

With this tiny bit of Javascript, we’re getting the coordinates of the mouse whenever it moves and passing them on to CSS-land using CSS variables.

const el = document.querySelector(".circle"); // which element to apply CSS variables

document.addEventListener("mousemove", (event) => {
   const {pageX, pageY} = event;
   el.style.setProperty("--x", pageX);
   el.style.setProperty("--y", pageY);
});

We can now use var(--x) and var(--y) to position something accordingly.

.circle{
  position:fixed;
  left:calc(var(--x) * 1px); /* convert number to pixel */
  top:calc(var(--y) * 1px);
  width:20px;
  height:20px;
  border-radius:50%;
  background:#000;
  transform:translate(-50%, -50%); /* Center on mousepointer: */
}

Result:

This works fine if you just want to track the cursor across the entire page, but let’s say you wanted to only track the movement when the cursor is inside of a specific element…

Let’s track mouse movements…

…Relative to an element πŸ‘Ύ

When dealing with stuff inside other stuff, we want to take into consideration that parent element’s offset.

Let me visualise the process.

To achieve this calculation, we’ll use el.offsetLeft & el.offsetTop:

const buttons = document.querySelectorAll("button");

buttons.forEach(el => {
  el.addEventListener("mousemove", (event) => {
      const {pageX, pageY} = event;
      el.style.setProperty("--x", pageX - el.offsetLeft); // taking into consideration element offset
      el.style.setProperty("--y", pageY - el.offsetTop);
  });
});

Now each of those buttons will have var(--x) and var(--y) relative to their own positioning inside their parent. Take a look at this example of a design using this technique:

But wait, this only works sometimes?? 🀯

Yes, this is because we haven’t really properly taken into consideration the offset, just the offset to the element’s direct parent. So if your element is placed directly inside the body-element, like it often is in online playgrounds, it’ll work – but once you place that element into a real design – it stops working.

To calculate the total offset, we’ll create a function.

const getTotalOffset = (el) => {
   let a = el, offsetLeft = 0, offsetTop = 0;
   while (a) { // while "a" (element or parent element) exists
       offsetLeft += a.offsetLeft;
       offsetTop += a.offsetTop;
       a = a.offsetParent; // reassign "a" to parent element
   }
   return {offsetLeft, offsetTop}
}

So to break this code down, we are…

  • Looping through all off the given element’s offsetParents
  • Adding the offsetLeft and offsetTop of each respective parent together.
  • Finally returning those added values when there are no offsetParents left.

Now all that’s left is to implement this function into our code.

const buttons = document.querySelectorAll("button");

const getTotalOffset = (el) => {
   let a = el, offsetLeft = 0, offsetTop = 0;
   while (a) { // while "a" (element or parent element) exists
       offsetLeft += a.offsetLeft;
       offsetTop += a.offsetTop;
       a = a.offsetParent; // reassign "a" to parent element
   }
   return {offsetLeft, offsetTop}
}

const onMouseMove = (event, el) => {
    const {pageX, pageY} = event;
    const {offsetLeft, offsetTop} = getTotalOffset(el);
    el.style.setProperty("--x", pageX - offsetLeft); // taking into consideration element offset
    el.style.setProperty("--y", pageY - offsetTop);
}

buttons.forEach(el => {
    el.addEventListener("mousemove", (event) => onMouseMove(event, el));
});

πŸ‘ Now that’s a rock solid mouse-tracker πŸ‘