Our Writing

Create a mouse tracking eye using Vue 3, VueUse & CSS.


Talie /

In this blog, I will be showing you how to make a mouse-tracking eye component using Vue 3, VueUse and a sprinkle of CSS. This eye-catching component will make a quirky addition to your future projects.

Firstly let's break the eyes, my name is Taliesin, and I work at Pixelhop. I made this for our Halloween special project we at Pixelhop made called trick-or-treat.

If you would like to get your eyes on the whole code example, please find it here.

Readers are recommended to have a basic understanding of Vue 3 using the composition API, also not recommended for anyone with Ommetaphobia or if you have anything against terrible eye puns. So if we see eye to eye, let's crack on.


So, to summer-eyes, for this to work, we will need to have an SVG of an eye with the pupil to one side. We are then just going to set the transform rotate property to point the pupil in the direction of the mouse as it moves.

Project setup

If you already have a project and an eye SVG ready, you can roll your eyes on to the next section. But if you're like the alien called Alen and missing an eye, here I will just be setting up a basic Vue 3 project and setting the foundation of what we are making. So first, we want to set up a basic Vue 3 project. The easiest way to do this is using npx and the Vue CLI by running the following command.

npx @vue/cli create mouse-tracking-eye

Select the default vue 3 preset

Choose your dependancies manager (I'm using npm)

cd into the folder and install the dependancies

cd mouse-tracking-eye/ && npm i

We can now remove all the bits the vue cli gives us that we don't need. First, remove the components and assets folders. Then in the App.vue, we should remove all the base stuff it gives us. You are just left with the template, script and style tag.

I am using the <script setup> syntax, which you can read about here and typescript because why not.

<!-- App.js -->

<script lang="ts" setup>


Now we need to eye up the template, I'm using the SVG I used for our Halloween project. Add a div and give it a class of container, then paste the SVG inside the container div.

<!-- App.ts > temaplate -->
<div class="container">
    viewBox="0 0 33 33"

Centre the eye and add a black background, so the eye stands out with a bit of CSS.

/* App.js > style */

.conatainer {
  background-color: black;
  height: 100vh;
  width: 100vw;
  display: flex;
  align-items: center;
  justify-content: center;
.eye {
  height: 3rem;
body {
  margin: 0;
  height: 100vh;

Spec-tacular! Now, if you run your project, you should have an eye in the middle of your screen and a black background.

Mouse tracking functionality

This section will focus on getting the eye to follow the mouse.

As previously mentioned, we will be using the vueuse library. Vueuse is a super helpful function library for Vue 3 with a few functions that will simplify this. So let's install it:

npm i @vueuse/core

Import the functions we need into our app and we might as well import the function we need from Vue as well.

<!-- App.vue > script -->
import {
  useMouse, useWindowSize, debouncedWatch, throttledWatch,
} from '@vueuse/core';
import {
  ref, onMounted,
} from 'vue';

Eye eye, now we got those imported, we can start using them. The first two we will use are useMouse and useWindowSize. useMouse returns the x and y of the mouse position, and useWindowSize returns... You guessed it, the window size width and height. So just under the import, add the following:

// App.vue > script
const { x: mouseX, y: mouseY } = useMouse();
const { width, height } = useWindowSize();

Next we need to get the eye location on the screen, to do this add a ref to the SVG in the template. So it will be something like this.

<!-- App.vue > template -->
  viewBox="0 0 33 33"

and now we can reference it in the script, we just need to add a ref variable with null as its starting value.

// App.vue > script
const eye = ref(null as Element | null);

Side note: If you are using a setup function inside a regular script tag, make sure you add the ref to the return object, or it will not work.

Now define the eye location reference

// App.vue > script
const eyeLocation = ref<DOMRect | undefined>(undefined);

Here I am setting the eye location inside the onMounted function we import from Vue earlier.

// App.vue > script
onMounted(() => {
  eyeLocation.value = eye?.value?.getBoundingClientRect();

We also want to set the eye's location when the screen size is changed because depending on where it is this might move the eye. To achieve this we can use the debounceWatch To summarise whats happening here; we are watching for a change in the window height and width then running a debounce function when it does.

// App.vue > script
debouncedWatch([width, height], () => {
  eyeLocation.value = eye?.value?.getBoundingClientRect();
}, { debounce: 200 });

Great, now we've now we've got an eye and its location, let's get the ball rolling and actually make the eye move.

// App.vue > script
const rotationDegrees = ref(0);

throttledWatch([mouseX, mouseY], ([x, y]) => {
  if (eyeLocation.value) {
    const radianDegrees = Math.atan2(x - eyeLocation.value.right, y - eyeLocation.value.top);
    rotationDegrees.value = radianDegrees * (180 / Math.PI) * -1 + 180;
}, { throttle: 1000 / 60 });

So if you got an eye for this sort of thing, then you'll be able to understand it, but if you're like a blind Bambi and have no-eye-deer. Don't worry; I'll give a quick summary of what's happening;

Firstly, we define the ref rotationDegrees which will be the number of degrees we need to rotate our eye. Next, we are using the throttledWatch function to watch the mouse location, then set the rotationDegrees.

First, it gets the radianDegrees using the Math.atan2 function; read more here. Basically, it receives the radian between the eye location and the mouse. I am using the top and right locations of the eye, but depending on where your eye's pupil is pointing, you may need to use a different location. Then we convert the radian into degrees we can use to rotate the eye.

This function is then throttled to 60 times per second as we don't need to run this more than that as most screens only run a 60 hertz anyway.

Now all we need to do is set the transform rotate property to the SVG and you'll really be turning some eyes.

<!-- App.vue > temaplate -->
  :style="`transform: rotate(${rotationDegrees - 40}deg) translateZ(0)`"
  viewBox="0 0 33 33"

Side note: You may need to do what I did by adding or subtracting a few degrees if your eye is not pointing precisely left or right.


Because we are getting the eye location on mounted and screen size change, you can place your eye anywhere on the screen, and it will still work.

I hope you enjoyed this mini tutorial, and it helps you create some fun projects. Feel free to send us your creations. I would love to eye them up. You can find our contact details at https://www.pixelhop.io/contact/.

If you are interested to see the original eye, I made and our Halloween special project, check it out here: https://trick-or-treat.pixelhop.io/.

Please keep your eyes peeled for our new blogs at https://www.pixelhop.io/writing/ or sign up for our newsletter.

See you later 👋


Like what you’re reading? Sign up to recieve our updates straight to your inbox. No spam, unsubscribe anytime!