javascriptcsscursormouseeventmousehover

cursor Hover issues when used along with cursor moving function


I am trying to create a custom mouse cursor where the body tag gets some classes applied depending upon the status of mouse cursor. Like:

  1. cursor-moving: whenever the mouse is moved
  2. cursor-idle: whenever the mouse is idle
  3. cursor-hover: whenever the mouse is moved over an anchor or a button tag.

But this javascript code is not working when I try to hover over the link elements. I tried to do a console, and it shows cursor-hover is working, but very intermittently and that too for a tiny second.

Hint: I have also written a style which will change the background-color of body when this hover thing works.

const customCursor = document.querySelector(".cursor");
let isCursorMoving = false;
let cursorIdleTimeout;
let isCursorOverLink = false;

function updateCursor(event) {
  const x = event.clientX + "px";
  const y = event.clientY + "px";

  customCursor.style.setProperty("--cursor-left", x);
  customCursor.style.setProperty("--cursor-top", y);

  if (!isCursorMoving) {
    document.body.classList.add("cursor-moving");
    document.body.classList.remove("cursor-idle");
    clearTimeout(cursorIdleTimeout);
  }

  cursorIdleTimeout = setTimeout(() => {
    isCursorMoving = false;
    document.body.classList.remove("cursor-moving");
    document.body.classList.add("cursor-idle");
  }, 1000);
}

function handleLinkEnter(event) {
  if (event.target.tagName === "A" || event.target.tagName === "BUTTON") {
    document.body.classList.add("cursor-hover");
  }
}

function handleLinkLeave(event) {
  if (event.target.tagName === "A" || event.target.tagName === "BUTTON") {
    document.body.classList.remove("cursor-hover");
  }
}

document.addEventListener("mousemove", updateCursor);
document.addEventListener("mouseenter", handleLinkEnter);
document.addEventListener("mouseleave", handleLinkLeave);
* {
  box-sizing: border-box;
}

body {
  background: #3f3f3f;
}

:root {
  --cursor-size: 32px;
  --tail-size: 1px;
  --tail-gap: 48px;
  --tail-color: #111;
  --cursor-color: #fff;
}

.cursor {
  position: fixed;
  left: var(--cursor-left, 0);
  top: var(--cursor-top, 0);
  width: var(--cursor-width, var(--cursor-size));
  height: var(--cursor-height, var(--cursor-size));
  z-index: 999999;
}

.cursor::before,
.cursor::after {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  background: var(--cursor-color);
  transform: translate(-50%, -50%);
}

.cursor::before {
  width: 1px;
  height: var(--cursor-size);
}

.cursor::after {
  width: var(--cursor-size);
  height: 1px;
}

.cursor .tail {
  position: absolute;
  left: 0;
  top: 0;
  background: var(--tail-color);
  opacity: 0.6;
}

.cursor .tail::before {
  content: "";
  position: absolute;
  background: var(--tail-color);
}

.cursor .tail-x {
  width: 100vw;
  height: var(--tail-size);
  left: var(--tail-gap);
}

.cursor .tail-x::before {
  left: calc(-100vw - var(--tail-gap) - var(--tail-gap));
  right: 0;
  width: 100vw;
  height: var(--tail-size);
}

.cursor .tail-y {
  width: var(--tail-size);
  height: 100vh;
  top: var(--tail-gap);
}

.cursor .tail-y::before {
  top: calc(-100vw - var(--tail-gap) - var(--tail-gap));
  bottom: 0;
  height: 100vw;
  width: var(--tail-size);
}

body {
  display: grid;
  height: 100vh;
  width: 100vw;
  place-items: center;
}

body.cursor-hover {
  background: yellow;
}

body.cursor-hover a {
  color: #000;
}

a {
  display: inline-block;
  color: #fff;
  padding: 4px;
}
<div class="cursor">
  <span class="tail tail-x"></span>
  <span class="tail tail-y"></span>
</div>

<a href="#">Link</a>


Solution

  • First, you obscure the link with the .cursor, because the .cursor has a larger z-index. Therefore, the .cursor should be given pointer-events:none;.

    Second, you call the mouseenter and mouseleave events on the document and they do not affect the link in any way.

    Fixed your code a bit:

    const customCursor = document.querySelector('.cursor');
    let isCursorMoving = false;
    let cursorIdleTimeout;
    let isCursorOverLink = false;
    
    function updateCursor(event) {
      const {clientX, clientY} = event;
      customCursor.style.transform = `translate3d(${clientX}px, ${clientY}px, 0)`;
      
      const isHoverElement = event.target.tagName === 'A' || event.target.tagName === 'BUTTON'
      document.body.classList.toggle('cursor-hover', isHoverElement);
    
      if (!isCursorMoving) {
        document.body.classList.add('cursor-moving');
        document.body.classList.remove('cursor-idle');
        clearTimeout(cursorIdleTimeout);
      }
    
      cursorIdleTimeout = setTimeout(() => {
        isCursorMoving = false;
        document.body.classList.remove('cursor-moving');
        document.body.classList.add('cursor-idle');
      }, 1000);
    }
    
    window.addEventListener('mousemove', updateCursor);
    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
      cursor: none;
    }
    
    :root {
      --cursor-size: 32px;
      --tail-size: 1px;
      --tail-gap: 48px;
      --tail-color: #111;
      --cursor-color: #fff;
    }
    
    body {
      background: #3f3f3f;
      display: grid;
      height: 100dvh;
      place-items: center;
      overflow:hidden;
      transition: background-color .4s;
    }
    
    body.cursor-hover {
      background: yellow;
    }
    
    body.cursor-hover .cursor {
      color:blue;
    }
    
    body.cursor-hover a {
      color: #000;
    }
    
    a {
      color: #fff;
      padding: 4px;
    }
    
    .cursor {
      pointer-events: none;
      position: fixed;
      width: var(--cursor-size);
      height: var(--cursor-size);
      margin: calc(var(--cursor-size) / -2) 0 0 calc(var(--cursor-size) / -2);
      left: 0;
      top: 0;
      color: var(--cursor-color);
      transition: color .4s;
      z-index: 666;
    }
    
    .cursor:before,
    .cursor:after {
      content: '';
      position: absolute;
      inset: calc(50% - var(--tail-size) / 2) 0 auto 0;
      background: currentColor;
      height: var(--tail-size);
    }
    
    .cursor::after {
      transform: rotate(90deg);
    }
    
    .cursor .tail {
      position: absolute;
      inset: 0;
      opacity: 0.6;
    }
    
    .cursor .tail:before,
    .cursor .tail:after {
      content: '';
      position: absolute;
      background: var(--tail-color);
    }
    
    .cursor .tail-x:before,
    .cursor .tail-x:after {
      top:calc(50% - var(--tail-size) / 2);
      height: var(--tail-size);
      width:100vmax;
    }
    
    .cursor .tail-x:before {
      right: calc(100% + var(--tail-gap))
    }
    
    .cursor .tail-x:after {
      left: calc(100% + var(--tail-gap))
    }
    
    .cursor .tail-y:before,
    .cursor .tail-y:after {
      left: calc(50% - var(--tail-size) / 2);
      width: var(--tail-size);
      height: 100vmax;
    }
    
    .cursor .tail-y:before {
      bottom: calc(100% + var(--tail-gap))
    }
    
    .cursor .tail-y:after {
      top:calc(100% + var(--tail-gap))
    }
    <div class="cursor">
      <span class="tail tail-x"></span>
      <span class="tail tail-y"></span>
    </div>
    <a href="#">Link</a>