DEV Community

Lens
Lens

Posted on

How to make a cursor image Hover effect with CSS & JS!

Custom cursors and cursor hover effects are a great way to impress users on your website, whether you're making an e-commerce website or a portfolio. A good example is a cursor image hover effect, where when the cursor hovers over something it shows an image, here's my way of making it!

Setting up the example

I'll make a short and simple vertical nav with the nav element for this example. There will be six div elements in it, and outside the nav is a div with the class of "cursor", that'll be our custom cursor.

    <nav>
      <div>Lofi</div>
      <div>Motivation</div>
      <div>Profiles</div>
      <div>Goals</div>
      <div>Academics</div>
    </nav>
    <div class="cursor"></div>
Enter fullscreen mode Exit fullscreen mode

Now we add the CSS, that tells the font size, the alignment, and a border on each nav item except the last one.


html, body {
    width: 99%;
    height: 99%
}

body {
    display: flex;
    align-items: center;
}

nav {
    font-size: 6rem;
    display: grid;
}

nav > div:not(div:nth-child(5)) {
    border-bottom: solid ;
}

@media (max-width: 480px) {
    nav {
        font-size: 4.8rem;
    }
}
Enter fullscreen mode Exit fullscreen mode

Making and controlling the custom cursor

First, we must remove the visibility of the default cursor by setting the website's (*) cursor set to none. Then we create our custom cursor, which is the cursor div we made before. It'll have a width and height of 20px and its border-radius will be set to 50% so it becomes a circle. You can add whatever background-color you'd like for it, I chose black so it can be easily visible. The most important part is that you set it's position to fixed so the cursor always stays in view and pointer-events to none so the default actions of how a default cursor would work don't effect the custom one. Finally you set the background-size to cover and a scale transition, these will both be important for the hover effects later on.

* {
    cursor: none;
}

.cursor {
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background-color: black;
    /*Keeps our custom cursor on the user's screen*/
    position: fixed;
    pointer-events: none;
    /*makes sure the background images will sort of fit the cursor*/
    background-size: cover; 
    transition: scale 300ms;
}
Enter fullscreen mode Exit fullscreen mode

Note: I said the visibility of the cursor and not the cursor itself because the computer still does the actions of the cursor such as selecting text even when the cursor isn't shown

Now in Javascript, we make a variable that stores the cursor.

let cursor = document.querySelector(".cursor");
Enter fullscreen mode Exit fullscreen mode

Next, we'll make a function called cursorControl with an e parameter that stands for "event". In it we make an X and Y variable that use e.clientX and e.clientY to find the coordinates of where the cursor should be based on the movements of the mouse. Then, we place our custom cursor based on our coordinates by setting it's top position to the y and left position to the x. Finally, we just call the function in our mousemove event listener.

function cursorControl(e) {
//tracks the cursors coordinates
  let x = e.clientX;
  let y = e.clientY;
//places the custom cursor onto the cursor coordinates
  cursor.style.top = `${y}px`;
  cursor.style.left = `${x}px`;
}

document.addEventListener("mousemove", cursorControl);
Enter fullscreen mode Exit fullscreen mode

There you have it! A custom cursor, now we just need to add the hover effects.


Cursor growth hover effect

Before we go into the Javascript part of things, we need to make a new class called cursorGrow with a scale property of 8. This class will be added to the cursor whenever it hovers over a nav div.

.cursorGrow {
scale: 8;
}
Enter fullscreen mode Exit fullscreen mode

Now, in JS we save all of the nav divs into a variable.

let divs = document.querySelectorAll("div:not(.cursor)");
Enter fullscreen mode Exit fullscreen mode

Furthermore, we make a forEach method. Where for each div in our nav, when the cursor enters/hovers over them the cursorGrow CSS class will be added causing the scale of the cursor to go up by 8, but when the cursor leaves the class is removed and the size goes back to normal.

divs.forEach((div) => {
  div.addEventListener("mouseenter", function () {
    cursor.classList.add("cursorGrow");
  });
  div.addEventListener("mouseleave", function () {
    cursor.classList.remove("cursorGrow");
  });
});
Enter fullscreen mode Exit fullscreen mode

Now, because of what we've done so far, the code should create this (make sure to add a transition to the cursor if you hadn't already):


Cursor Image hover effect

Last but not least, we finally add the image hover effect! To add each image, we'll make a CSS classes based on each div an a corresponding image for them.

/*Background image for each div based on their text*/
.lofi {
    background-image: url('https://images.unsplash.com/photo-1585477281005-b83d4b47eba4?q=80&w=1887&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D');
}

.motivation {
    background-image: url('https://images.unsplash.com/photo-1556711905-4bd1b6603275?q=80&w=1887&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D');
}

.webDev {
    background-image: url('https://images.unsplash.com/photo-1498050108023-c5249f4df085?q=80&w=1772&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D');
}

.goals {
    background-image: url('https://i.pinimg.com/564x/4d/ee/43/4dee438cb40321df2cb0cfb14a3c7811.jpg');
}

.academics {
    background-image: url('https://i.pinimg.com/564x/f8/3d/8b/f83d8b8d17e4315caa647dab88e6addc.jpg');
}

Enter fullscreen mode Exit fullscreen mode

Now we could do this similar to our growth hover effect, where we continue to write mouseenter and mouseleave event listeners, but that would be too much code. Instead we could make a loop! But first we need to store the names of each image class in an array and the divs variable we had before.

let divs = document.querySelectorAll("div:not(.cursor)");
const imageClassNames = ["lofi", "motivation", "webDev", "goals", "academics"];
Enter fullscreen mode Exit fullscreen mode

Now for the loop. How it works is, in the loop there are the mouseenter and mouseleave event listeners, and they add and remove classes from the image class array. Whenever a class is added to the div, the div is given the properties of the class, which is the background image of it, but when removed the properties from the class are removed as well, that's how classList.add() and classList.remove() work.

To make sure the right class corresponds with the right div, the loop goes from 0 to the length of the array so as long as the order in the divs array and the imageClassNames array the correct div and the correct array value always match. So, for example it'll start with the 0th value of the divs array which is the lofi div, and it'll add the class that's named 0th value of imageClassNames which is lofi whenever the mouse enters the div, but removes it when it leaves the div. The loop does it for every value in the image class and divs array, and stops us from writing constant event listeners in our code.

const ImageClassNames = ["lofi", "motivation", "webDev", "goals", "academics"];

for (let i = 0; i < divs.length; i++) {
  divs[i].addEventListener("mouseenter", () => {
    cursor.classList.add(imageClassNames[i]);
  });

  divs[i].addEventListener("mouseleave", () => {
    cursor.classList.remove(imageClassNames[i]);
  });
}

Enter fullscreen mode Exit fullscreen mode

Now, we should finally have our cursor image hover effect!


Conclusion

That's really it! It took me a bit of trial and error to make this cursor hover effect out of my mind, but in the end, it was worth it, which is why I wanted to make a blog about it! I show more of my web dev, game development, and even 3D blender creations on my twitter. Follow for more and have an awesome day/night👋.

Top comments (0)