6

I have a custom image cursor for my body and links.

What I would like to achieve is hovering the link the cursor should transition into the link's cursor image, rather than it just changing straight away.

Currently, I have this code:

html {
  height:100%;
}
body {
  cursor:url(https://i.imgur.com/odlAwsz.png), auto  !important;
  width:100%;
  height:100%;
  background:red;
}

a {
  cursor:url(https://i.imgur.com/yxX4Snm.png), auto !important;
}
<a href="#">I'm a link</a>

As you can see above there is no transition between the two circle icons when hovering <a>.

I tried achieving this with CSS, but with no success. How can this be achieved using JavaScript?

Ivan
  • 34,531
  • 8
  • 55
  • 100
probablybest
  • 1,403
  • 2
  • 24
  • 47
  • 1
    This isn't answered in the previous question of how to achieve this with JS? – probablybest Jul 11 '18 at 09:33
  • For anyone else who struggles with this issue here is a fiddle showing how to animate the cursor on links with JS. https://jsfiddle.net/je6oxk1u/4/ . I would personally use an SVG rather than an .PNG – probablybest Jul 11 '18 at 11:56
  • I came up with a similar solution but for HTML cursors. I'm voting to reopen as the duplicate link doesn't answer the question. When the question comes back open and if it does, I'll post the answer below. – Ivan Jul 11 '18 at 16:45
  • @Temani, could you remove the duplicate? I don't believe it is. As OP saidit is about solving the issue with JavaScript, so [CSS - Cursor Transition](https://stackoverflow.com/questions/38767322/css-cursor-transition) won't be of any help here. – Ivan Jul 11 '18 at 17:12

1 Answers1

10

Here's a way you can achieve: the following solution allows you to have custom HTML cursors that can transition from one state to another when hovering specific tags.

  1. Let's first create our custom HTML cursor:

#cursor {
  width: 20px;
  height: 20px;
  position: absolute;
  top: 0;
  left: 0;
  background: blue;
  border-radius: 10px;
}
<div id="cursor"></div>
  1. Then we need to make this element track the position of the actual cursor:

$(document).mousemove(function(e) {

  const cursor = $('#cursor');
  const target = $(event.target);
  
  // update position of cursor
  cursor.css('left', e.clientX-10).css('top', e.clientY-10);
 
});
* {
  cursor: none;
}

#cursor {
  width: 20px;
  height: 20px;
  position: absolute;
  top: 0;
  left: 0;
  background: blue;
  border-radius: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="cursor"></div>
  1. When #cursor will be hovering <a> we will add a class (.hoveredCursor) that will change #cursor's initial properties (e.g. width and height). In order to not unnecessarily add or remove a class to the cursor on mousemove we can check for two things:

  2. the target is a <a> element, can be checked with jQuery's .is method:

      const isLinkTag = target.is('a');
    
  3. whether or not #cursor has the class .hoveredCursor i.e. #cursor is already hovering. With the method .hasClass:

       const isHovered = cursor.hasClass('hoveredCursor');
    
  4. You can set any property to .hoveredCursor, when hovering these will be added to #cursor's initial property (you might need to use !important to overwrite styles), for example:

    .hoveredCursor {
      width: 10px !important;
      height: 10px !important;
    }
    

Then set the transition property of #cursor to make it smooth:

    #cursor {
      transition: linear height 0.2s, linear width 0.2s;
    } 
  1. one issue you might come across is having #cursor get in the way of the event.target meaning target will be #cursor. This results in some bad behavior (#cursor will be switching back and forth between the two states...)

Setting none to #cursor's pointer-events will solve that (the event will simply ignore #cursor).


Here is the final code:

$(document).mousemove(function(e) {

  const cursor = $('#cursor');
  const target = $(event.target);
  
  // update position of cursor
  cursor.css('left', e.clientX-10).css('top', e.clientY-10);
  
  const isLinkTag = target.is('a');
  const isHovered = cursor.hasClass('hoveredCursor');
  
  // toggle the cursor class if necessary 
  if(isLinkTag && !isHovered) {
  
    cursor.addClass('hoveredCursor');

  } else if(!isLinkTag && isHovered) {
  
    cursor.removeClass('hoveredCursor');
  
  }
  
});

$(document).mouseleave(function(e) {

  const cursor = $('#cursor');
  cursor.hide()

});

$(document).mouseenter(function(e) {

  const cursor = $('#cursor');
  cursor.show()

});
* {
  cursor: none;
}

#cursor {
  pointer-events: none;
  width: 20px;
  height: 20px;
  position: absolute;
  top: 0;
  left: 0;
  display: none;
  background: blue;
  border-radius: 10px;
  transition: linear height 0.2s, linear width 0.2s;
}

.hoveredCursor {
  width: 10px !important;
  height: 10px !important;
}

a {
  font-size: 20px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="cursor"></div>

<a href="#">This is a link</a>

Note: I have added a mouseenter and mouseleave to the document too so that the custom cursor hides or shows accordingly.


The advantage of using such method is it allows you to transition between two sets of properties for any given elements (by tags - here <a> - or even by selector).

Ivan
  • 34,531
  • 8
  • 55
  • 100
  • only problem with this is when you scroll page it stays on same x and not update. To fix you use pagex & pageY instead of clientX/Y – flakerimi Nov 06 '19 at 14:40