DEV Community

anomaly3108
anomaly3108

Posted on

Make SVG follow cursor using CSS and JS

In this article, we are going to make an SVG Eye that will follow the mouse pointer with a clean UI and smooth transition. First, As always let's see what are we building.

PREVIEW

Image description

HTML

<img src="face-with-rolling-eyes.png" class="image">
  <div class="container">
    <svg width="100" height="100" class="eye">
        <circle cx="50" cy="50" r="50" fill="white" class="eyeball_left" />
        <circle cx="50" cy="50" r="20" fill="#0D0D20" class="pupil_left" />
    </svg>
    <svg width="100" height="100" class="eye">
      <circle cx="50" cy="50" r="50" fill="white" class="eyeball_right" />
      <circle cx="50" cy="50" r="20" fill="#0D0D20" class="pupil_right" />
    </svg>
  </div>
Enter fullscreen mode Exit fullscreen mode

We will have an outer div with class .container. It will have two separate children SVG which will be the eyes of our character.
Inside SVG we create 2 circles one for eyeball and one for pupil.
The img tag will be the face of the character

I guess now you have an overview of what are we doing. Now let's get into the CSS.

CSS

body{
    margin:0;
    padding:0;
    background: #282631;
    display: flex;
    width: 100%;
    height:100vh;
  }
  .container{
    margin: auto;
  }
  .image{
    position: absolute;
    top: 250px;
    left: 620px;
    z-index: -1;
  }
  .pupil_left{
    position:relative;
  }
  .pupil_right{
    position:relative;
  }
Enter fullscreen mode Exit fullscreen mode

Everything above is self-explanatory but If you have any queries then comment down.

JAVASCRIPT

This is where the fun begins. Let's see from the scratch.
First, we need to find elements with an "eyeball_left" and "pupil_left" class

    let eyeball_left = document.querySelector(".eyeball_left"),
    pupil_left = document.querySelector(".pupil_left"),

Enter fullscreen mode Exit fullscreen mode

Now, We will get the radius of the circles to find the center of the circles. The getBoundingClientRect returns a DOMRect object with eight properties: left, top, right, bottom, x, y, width, height.

    eyeArea_left = eyeball_left.getBoundingClientRect(),
    pupil_leftArea = pupil_left.getBoundingClientRect(),
    R_left = eyeArea_left.width/2,
    r_left = pupil_leftArea.width/2,
    centerX_left = eyeArea_left.left + R_left,
    centerY_left = eyeArea_left.top + R_left;

Enter fullscreen mode Exit fullscreen mode

Copy the same code for right eye. Just change the variable names to ###_right for right Eye.

    let eyeball_right = document.querySelector(".eyeball_right"),
    pupil_right = document.querySelector(".pupil_right"),
    eyeArea_right = eyeball_right.getBoundingClientRect(),
    pupil_rightArea = pupil_right.getBoundingClientRect(),
    R_right = eyeArea_right.width/2,
    r_right = pupil_rightArea.width/2,
    centerX_right = eyeArea_right.left + R_right,
    centerY_right = eyeArea_right.top + R_right;

Enter fullscreen mode Exit fullscreen mode

Now, let us create a mouse event. Through which, we will find the distance between the pointer and center of the eyeball. Math.atan2 will return the angle in radians between the two points. By using the formula, we can convert radian to degree.
Using this angle we will position the pupil inside the eyeball

document.addEventListener("mousemove", (e)=>{
  let x_left = e.clientX - centerX_left,
      y_left = e.clientY - centerY_left,
      theta_left = Math.atan2(y_left,x_left),
      angle_left = theta_left*180/Math.PI + 360;

Enter fullscreen mode Exit fullscreen mode

Create a same for the right eye

  let x_right = e.clientX - centerX_right,
      y_right = e.clientY - centerY_right,
      theta_right = Math.atan2(y_right,x_right),
      angle_right = theta_right*180/Math.PI + 360;



Enter fullscreen mode Exit fullscreen mode

Finally, we will use JS style property to move and rotate the pupil inside the Eye to follow the cursor

  pupil_left.style.transform = `translateX(${R_left - r_left +"px"}) rotate(${angle_left + "deg"})`;
  pupil_left.style.transformOrigin = `${r_left +"px"} center`;

  pupil_right.style.transform = `translateX(${R_right - r_right +"px"}) rotate(${angle_right + "deg"})`;
  pupil_right.style.transformOrigin = `${r_right +"px"} center`;

});
Enter fullscreen mode Exit fullscreen mode

Now we have covered all the aspects of this now let's see the full Javascript code.

<script>
    let eyeball_left = document.querySelector(".eyeball_left"),
    pupil_left = document.querySelector(".pupil_left"),
    eyeArea_left = eyeball_left.getBoundingClientRect(),
    pupil_leftArea = pupil_left.getBoundingClientRect(),
    R_left = eyeArea_left.width/2,
    r_left = pupil_leftArea.width/2,
    centerX_left = eyeArea_left.left + R_left,
    centerY_left = eyeArea_left.top + R_left;

    let eyeball_right = document.querySelector(".eyeball_right"),
    pupil_right = document.querySelector(".pupil_right"),
    eyeArea_right = eyeball_right.getBoundingClientRect(),
    pupil_rightArea = pupil_right.getBoundingClientRect(),
    R_right = eyeArea_right.width/2,
    r_right = pupil_rightArea.width/2,
    centerX_right = eyeArea_right.left + R_right,
    centerY_right = eyeArea_right.top + R_right;

document.addEventListener("mousemove", (e)=>{
  let x_left = e.clientX - centerX_left,
      y_left = e.clientY - centerY_left,
      theta_left = Math.atan2(y_left,x_left),
      angle_left = theta_left*180/Math.PI + 360;

  let x_right = e.clientX - centerX_right,
      y_right = e.clientY - centerY_right,
      theta_right = Math.atan2(y_right,x_right),
      angle_right = theta_right*180/Math.PI + 360;


  pupil_left.style.transform = `translateX(${R_left - r_left +"px"}) rotate(${angle_left + "deg"})`;
  pupil_left.style.transformOrigin = `${r_left +"px"} center`;

  pupil_right.style.transform = `translateX(${R_right - r_right +"px"}) rotate(${angle_right + "deg"})`;
  pupil_right.style.transformOrigin = `${r_right +"px"} center`;

});
</script>
Enter fullscreen mode Exit fullscreen mode

The final product will look like:-

Image description

You can use the following CSS in body selector to change the cursor by any image

cursor: url("heart.png"), auto;
Enter fullscreen mode Exit fullscreen mode

Wrapping Up

I hope you enjoyed the article, if yes then don't forget to press ❀️. You can also bookmark it for later use. It was fun to make this Project and If you have any queries or suggestions don't hesitate to drop them. See you again.

Discussion (3)

Collapse
lukeshiru profile image
LUKESHIRU

Is better to use CSS custom properties and make all the calculations of position in CSS itself. Take a look at my website where I did just that. On mousemove I just set an --x and a --y, and everything else is done by CSS. The good thing about this approach (besides the performance gains) is that it makes changes way easier. For example after a while I decided I wanted to make the avatar look to the front if the app looses focus (blur event), so I just set --x and --y to 0 when that even happens, and tada, everything works ☺️

Collapse
nkuwakwe profile image
Nelson Uwakwe

Wooww. nice site!

Collapse
anomaly3108 profile image
anomaly3108 Author

Hi, thanks for the suggestion.. This is also a great approach :)