DEV Community

Cover image for Coding an Interactive (and Damn Satisfying) Cursor: 7 Simple Steps + 2kb of Code

Coding an Interactive (and Damn Satisfying) Cursor: 7 Simple Steps + 2kb of Code

Ksenia Kondrashova on September 28, 2023

I made this cursor animation recently, and people seem to like it :) It is a nice-looking piece, but it's also quite simple and takes only 2KB of ...
Collapse
 
the_riz profile image
Rich Winter • Edited

Nicely done. Especially with the smooth quadratic curve.

You could consider moving a lot of this into a class in order to keep it out of global scope. Also, though it works, I think it's a little awkward to call functions before they are defined. I like to place my event listeners all together at the end of a code block.

Collapse
 
uuuuuulala profile image
Ksenia Kondrashova

Hey Rich! It all makes perfect sense, thank you for the code review! I do have a tendency to concentrate on the visual aspect so it's always nice to have someone to take a look at my JS

Collapse
 
bishwajit2810 profile image
Bishwajit Chakraborty

Nice project

Collapse
 
arkalsekar profile image
Abdul Rehman Kalsekar

Great Project 👍

Collapse
 
uuuuuulala profile image
Ksenia Kondrashova

Thanks man

Collapse
 
hasanelsherbiny profile image
Hasan Elsherbiny

amazing

Collapse
 
atamlk profile image
AtaMLK

amazing job

Collapse
 
bytebricks profile image
ByteBricks.ai

Soooooo satisfying! I can spend hours playing around with this!

Collapse
 
gyansetu1 profile image
gyansetu

Gyansetu's Programming courses are a fantastic way to kickstart your coding journey. With experienced instructors and a variety of programming languages to choose from, it's a great platform for honing your skills and embracing the world of coding. Highly recommended!
For more info:- gyansetu.in/blogs/70-power-bi-mcq-...

Collapse
 
uuuuuulala profile image
Ksenia Kondrashova

My first spam comment here on dev.to! Taste of success 😄

Collapse
 
lorenmcclaflin profile image
Loren McClaflin

It's so icky when ppl promote themselves using 3rd-person voice.

Collapse
 
lorenmcclaflin profile image
Loren McClaflin • Edited
  "and people seem to like it :)"
Enter fullscreen mode Exit fullscreen mode

@uuuuuulala, so have very tasteful people in your life :)

I discovered your "Damn Satisfying" (and it really is) Curly Cursor creation on CodePen, then had to hunt down my DEV login info, just so I could comment on what beautiful art/code you've made. AND- thank you for also taking the time to write up the tutorial!

Collapse
 
uuuuuulala profile image
Ksenia Kondrashova

Omg @lorenmcclaflin thank you so much!
It took me a bit of time to notice your comment but I'm soooo happy to see it now :) Really made my day, thanks man

Collapse
 
artydev profile image
artydev

Very nice, thank you
Look at processing.js...

Collapse
 
blindfish3 profile image
Ben Calder

I don't think processing.js has been actively maintained for quite some time. That was replaced by p5.js but TBH I think they tried too hard to match processing's Java based syntax in js; which I found rather limiting.
For anything canvas based my preference these days is pixiJS :)

Collapse
 
artydev profile image
artydev

PixiJS is indeed a great library
May I also suggest PaperJS

Collapse
 
yoursirbee722 profile image
yorrsirbee722

this is the uprgarted version of that code in only html

<!DOCTYPE html>







Interactive Cursor Tutorial

<br>
/* Resetting default styles and adding custom styles <em>/<br>
body,<br>
html {<br>
padding: 0;<br>
margin: 0;<br>
overscroll-behavior: none;<br>
background-color: #111; /</em> Dark background color <em>/<br>
color: #fff; /</em> Text color /<br>
}</p>
<div class="highlight"><pre class="highlight plaintext"><code> /
Styling for the tutorial link */
.links {
position: fixed;
bottom: 10px;
right: 10px;
font-size: 18px;
font-family: sans-serif;
background-color: white;
padding: 10px;
}
a {
    text-decoration: none;
    color: black;
    margin-left: 1em;
}

a:hover {
    text-decoration: underline;
}

a img.icon {
    display: inline-block;
    height: 1em;
    margin: 0 0 -0.1em 0.3em;
}

/* Styling for the canvas */
canvas {
    position: absolute;
    top: 0;
    left: 0;
}
Enter fullscreen mode Exit fullscreen mode

&lt;/style&gt;
</code></pre></div>
<p></head></p>

<p><body><br>
&lt;!-- Canvas element for the interactive cursor animation --&gt;<br>
<canvas></canvas></p>
<div class="highlight"><pre class="highlight plaintext"><code>&lt;!-- Tutorial link with icon --&gt;
&lt;div class="links"&gt;
&lt;a href="dev.to/uuuuuulala/coding-an-intera..." target="_blank"&gt;tutorial&lt;img class="icon" src="https://ksenia-k.com/img/icons/link.svg"&amp;gt;&amp;lt;/a&amp;gt;
&lt;/div&gt;

&lt;!-- JavaScript for interactive cursor animation --&gt;
&lt;script&gt;
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext('2d');

// for intro motion
let mouseMoved = false;

const pointer = {
    x: .5 * window.innerWidth,
    y: .5 * window.innerHeight,
}
const params = {
    pointsNumber: 40,
    widthFactor: .3,
    mouseThreshold: .6,
    spring: .4,
    friction: .5
};

const trail = new Array(params.pointsNumber);
for (let i = 0; i &amp;lt; params.pointsNumber; i++) {
    trail[i] = {
        x: pointer.x,
        y: pointer.y,
        dx: 0,
        dy: 0,
    }
}

window.addEventListener("click", e =&amp;gt; {
    updateMousePosition(e.pageX, e.pageY);
});
window.addEventListener("mousemove", e =&amp;gt; {
    mouseMoved = true;
    updateMousePosition(e.pageX, e.pageY);
});
window.addEventListener("touchmove", e =&amp;gt; {
    mouseMoved = true;
    updateMousePosition(e.targetTouches[0].pageX, e.targetTouches[0].pageY);
});

function updateMousePosition(eX, eY) {
    pointer.x = eX;
    pointer.y = eY;
}

setupCanvas();
update(0);
window.addEventListener("resize", setupCanvas);

function update(t) {
    // for intro motion
    if (!mouseMoved) {
        pointer.x = (.5 + .3 * Math.cos(.002 * t) * (Math.sin(.005 * t))) * window.innerWidth;
        pointer.y = (.5 + .2 * (Math.cos(.005 * t)) + .1 * Math.cos(.01 * t)) * window.innerHeight;
    }

    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.strokeStyle = '#00ff00'; // Green color for the cursor trail

    trail.forEach((p, pIdx) =&amp;gt; {
        const prev = pIdx === 0 ? pointer : trail[pIdx - 1];
        const spring = pIdx === 0 ? .4 * params.spring : params.spring;
        p.dx += (prev.x - p.x) * spring;
        p.dy += (prev.y - p.y) * spring;
        p.dx *= params.friction;
        p.dy *= params.friction;
        p.x += p.dx;
        p.y += p.dy;
    });

    ctx.beginPath();
    ctx.moveTo(trail[0].x, trail[0].y);

    for (let i = 1; i &amp;lt; trail.length - 1; i++) {
        const xc = .5 * (trail[i].x + trail[i + 1].x);
        const yc = .5 * (trail[i].y + trail[i + 1].y);
        ctx.quadraticCurveTo(trail[i].x, trail[i].y, xc, yc);
        ctx.lineWidth = params.widthFactor * (params.pointsNumber - i);
        ctx.stroke();
    }
    ctx.lineTo(trail[trail.length - 1].x, trail[trail.length - 1].y);
    ctx.stroke();

    window.requestAnimationFrame(update);
}

function setupCanvas() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
}
Enter fullscreen mode Exit fullscreen mode

&lt;/script&gt;
</code></pre></div>
<p></body></p>

<p></html></p>

<p>this is only html but fully working try thanks me latter</p>

Collapse
 
tomc13909 profile image
Tom C

How would you format this so it is ready to use? I am completely new to code and don't even know where to start!

Collapse
 
rain11 profile image
rain

May I know if there is any way I can change the colour of the ink? Sorry I am a beginner

Collapse
 
uuuuuulala profile image
Ksenia Kondrashova • Edited

it's very simple to do, just add a strokeStyle property to the Canvas context, for example

function update(t) {

    ctx.strokeStyle = "red";

    // the rest of the drawing loop

}
Enter fullscreen mode Exit fullscreen mode

Learn more about styles and colors here

Collapse
 
rain11 profile image
rain

Thank you you are amazing

Collapse
 
anthonybrown profile image
Tony Brown

What a lovely effect and I really like the code explanation. It was easy to understand and make sense. You have a new follower. Thank you.

Collapse
 
catafestwork profile image
Catalin George Festila

good work , params ...

Collapse
 
meisterleise profile image
Andi

Hmm, am I doing something wrong? Tested it on a website. As soon as I scroll down, the ink line stays more and more away from the cursor.

Collapse
 
uuuuuulala profile image
Ksenia Kondrashova • Edited

I see what you mean, Andi

Your issue is not about the animation itself but rather about getting correct pointer position. pointer.x and pointer.y properties should be calculated ** relative** to the <canvas> element.

In the original demo, <canvas> has a full-screen size, and <canvas> position is set to fixed (see CSS code). In this setup, we can track cursor position simply using e.pageX and e.pageY properties (see event listeners in the JS code).

But if <canvas> position isn't fixed and there are other HTML elements above the animation, you need to take into account the top offset of <canvas> itself

There're different ways to do so, for example

canvas.addEventListener("click", e => {
    updateMousePosition(e.clientX, e.clientY);
});
canvas.addEventListener("mousemove", e => {
    mouseMoved = true;
    updateMousePosition(e.clientX, e.clientY);
});
canvas.addEventListener("touchmove", e => {
    mouseMoved = true;
    updateMousePosition(e.targetTouches[0].clientX, e.targetTouches[0].clientY);
});

function updateMousePosition(eX, eY) {
    const box = canvas.getBoundingClientRect();
    pointer.x = eX - box.left; // take into account horizontal position of canvas
    pointer.y = eY - box.top; // take into account vertical position of canvas
}
Enter fullscreen mode Exit fullscreen mode

You can google "pointer position relative to element" to learn about the topic

Collapse
 
tomc13909 profile image
Tom C

I am using Wix and have been trying to add this to my website for the past 4 hours... completely new to code and no clue where to start. How would this be formatted for HTML? Thank you!