How to create a custom cursor using CSS and JavaScript

A custom cursor can add a unique touch to your web application making it more visually appealing and engaging, in this blog post we would explore how we can create our own custom cursor using just HTML, CSS, and javascript.

Setup

To begin navigate to an empty folder, and create an index.html, style.css, and a script.js file, next, add the following to your index.html file

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="style.css">
    <title>Document</title>
</head>
<body>
    <div class="container">
        <h1>Custom cursor Tutorial</h1>
        <h2> Using HTML CSS and Javascript</h2>
    </div>
    <script type="text/javascript" src="script.js"></script>

</body>
</html>

this is a simple html5 file, you would notice that I imported the style.css in the head of the file and the script.js file in the body below the container div, this is because we want the styles to load as soon as possible but we want the javascript to load only after the document is loaded

next, add the following code to the style.css

*{
    box-sizing: border-box;
    cursor: none;
    margin: 0;
    font-family: Montserrat, sans-serif;
}

body{
    height: 100vh;
}

.container{
    width: 100%;
    height: 100%;
    display: flex;
  flex-direction: column;
    align-items: center;
    justify-content: center;
    background: white;
    color: black;
}

The above code sets some basic styling for the page, also in the universal CSS selector * we have added cursor: none; to hide the cursor

in your script.js file add the following

const mainCursor = document.createElement('div');
mainCursor.classList.add('main-cursor');
document.body.appendChild(mainCursor);

const outerCursor = document.createElement('div');
outerCursor.classList.add('outer-cursor');
document.body.appendChild(outerCursor);

outerCursor.style.transform = `translate(${100}px, ${300}px)`;
mainCursor.style.transform = `translate(${100}px, ${300}px)`;

this creates two divs with a class of main-cursor and outer-cursor the idea is we want a large circle with a smaller circle in the center, the custom cursor we just created is at the top left of the screen let's temporarily put it in an area where we can see it, we do this by adding the style transform of a 100px to both the x and y axis of the outerCursor and mainCursor, now let's style them.

:root{
    --main-cursor-size: 12px;
    --outer-cursor-size: 40px;
}

.main-cursor{
    position: absolute;
    top: 0;
    left: 0;
    width: var(--main-cursor-size);
    height: var(--main-cursor-size);
    margin-top: calc(-1 * var(--main-cursor-size) / 2);
    margin-left: calc(-1 * var(--main-cursor-size) / 2);
    border-radius: 50%;
    background: white;
    pointer-events: none;
    transition: opacity .3s;
    mix-blend-mode: difference;
    z-index: 2;
}

.outer-cursor{
    position: absolute;
    top: 0;
    left: 0;
    transition: transform .15s, width .3s, height .3s, margin .3s;
    width: var(--outer-cursor-size);
    height: var(--outer-cursor-size);
    margin-top: calc(-1 * var(--outer-cursor-size) / 2);
    margin-left: calc(-1 * var(--outer-cursor-size) / 2);
    background: white;
    border-radius: 50%;
    pointer-events: none;
    mix-blend-mode: difference;
    z-index: 2;
}

using CSS variables we define the sizes of the main-cursor and the outer-cursor, we add a position of absolute, add the width and height using their respective root variables, and set the margin using the CSS calc method calc(-1 * var(--outer-cursor-size) / 2) calculates the negative half of the --outer-cursor-size variable value and applies it as the margin-top and margin-left values. This causes the custom cursor to be centered exactly at the mouse position.

we set the border radius to 50% to make it into a circle, we set a background color, and we disabled pointer events in the outer-cursor we add a transition property for some animation.

Adding Interactivity

now its time to make it interactive with some javascript, first we need to add an event listener to the HTML document and the event we would be listening for is the mouse-move event

// we nolonger need this part 
// outerCursor.style.transform = `translate(${100}px, ${300}px)`;
// mainCursor.style.transform = `translate(${100}px, ${300}px)`;

document.addEventListener('mousemove', function(e) {

});

next, we need to get the position of the cursor on the screen and then position our custom cursor at the exact same location, take note that even though the cursor is not visible it is still there!

document.addEventListener('mousemove', function(e) {

    const x = e.clientX;
    const y = e.clientY;

        outerCursor.style.transform = `translate(${x}px, ${y}px)`;
    mainCursor.style.transform = `translate(${x}px, ${y}px)`;

});

now we have our own working custom cursor, but wouldn't it be nice if we could add some form of interaction on hover? let's do that.

first, we add a hoverable class to the h2 element

<h2 class="hoverable"> Using HTML CSS and Javascript</h2>

next, we add a cursor-hide and a cursor-scale to our CSS

.cursor-hide{
    opacity: 0;
}

.cursor-scale{
    width: calc(var(--outer-cursor-size) * 2);
    height: calc(var(--outer-cursor-size) * 2);
    margin-top: calc(-1 * var(--outer-cursor-size));
    margin-left: calc(-1 * var(--outer-cursor-size));
}

In our javascript file, we need to get all elements with a hoverable class to do this we use the querySelectorAll, which returns an array of elements, next we loop over them and add event listeners of mouseover and mouseout

    const hoverables = document.querySelectorAll('.hoverable');

    Array.from(hoverables).forEach((hoverEl) => {
        hoverEl.addEventListener('mouseover', (e) => {
            mainCursor.classList.add('cursor-hide');
            outerCursor.classList.add('cursor-scale');
        })
    })

    Array.from(hoverables).forEach((hoverEl) => {
        hoverEl.addEventListener('mouseout', (e) => {
            mainCursor.classList.remove('cursor-hide');
            outerCursor.classList.remove('cursor-scale');
        })
    });

in the code above we add the cursor-hide class to the mainCursor and cursor-scale class to the outerCursor when the mouse moves over the element, and we also remove the classes when the mouse moves out of the element, you can find the complete javascript code below

const mainCursor = document.createElement('div');
mainCursor.classList.add('main-cursor');
document.body.appendChild(mainCursor);

const outerCursor = document.createElement('div');
outerCursor.classList.add('outer-cursor');
document.body.appendChild(outerCursor);

// outerCursor.style.transform = `translate(${100}px, ${300}px)`;
// mainCursor.style.transform = `translate(${100}px, ${300}px)`;

document.addEventListener('mousemove', function (e) {

    const x = e.clientX;
    const y = e.clientY;

    outerCursor.style.transform = `translate(${x}px, ${y}px)`;
    mainCursor.style.transform = `translate(${x}px, ${y}px)`;

    const hoverables = document.querySelectorAll('.hoverable');

    Array.from(hoverables).forEach((hoverEl) => {
        hoverEl.addEventListener('mouseover', (e) => {
            mainCursor.classList.add('cursor-hide');
            outerCursor.classList.add('cursor-scale');
        })
    })

    Array.from(hoverables).forEach((hoverEl) => {
        hoverEl.addEventListener('mouseout', (e) => {
            mainCursor.classList.remove('cursor-hide');
            outerCursor.classList.remove('cursor-scale');
        })
    });

});

conclusion:

In this blog post, we learned how to create a unique cursor for web applications using HTML, CSS, and JavaScript. We carefully set up the necessary files and made sure they load quickly. With JavaScript, we brought the cursor to life, making it move dynamically on the screen. We also made it customizable with CSS and added interactivity using mouse movement. By combining creativity and technology, we created a special cursor that improves the user experience.