Eye tracking dog
Tags: codepen, javascript, tutorial, programming
Published: 2024-02-01 15:32:09

<p>The eyes of the dog track the direction of the cursor when moved.</p>

{% codepen https://codepen.io/Pujaa-M/pen/wvOeaOK %}

In this tutorial, we'll explore the intricate process of synchronizing a virtual dog's eyes with the movement of your cursor.

## 1: Create a canvas in HTML
Using the canvas tag, create a canvas and setup the width, height and color of the canvas.

**HTML**
```
<canvas></canvas>
```
**CSS**
```
canvas {
	background-color: #A15A22;
}
```
**JS**
```
const canvas = document.querySelector("canvas");
const pen = canvas.getContext("2d");
let radius = 50;
let trackerRadius = 30;

function setupCanvas() {
	canvas.width = 500;
	canvas.height = 500;
}

setupCanvas();
```
You will achieve something like this:

![Canvas Setup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lrel3htyzajz4wcs9odz.png)

## 2: Draw eyeball(circle) at the center of the canvas
We can draw a circle on a canvas using the `arc()` method.

**JS**
```
/* ... */
let radius = 125;
/* ... */

function draw() {
	let centerX1 = canvas.width * 0.5;
	let centerY1 = canvas.height * 0.5;

	pen.beginPath();
	pen.arc(centerX1, centerY1, radius, 0, 2 * Math.PI);
	pen.fillStyle = "white";
	pen.fill();
}

draw();
```

![Eyeball](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ryt4cfhzgjefq0i0mv9o.png)

This circle that we have drawn will be the eyeball of the dog later on...

## 3: Draw iris the moves along with the cursor.
We will be using the `arc()` method again to draw a dot. Then we shall fetch the cursor position using `EventListeners` to update the position of the dot.

**JS**
```
let trackerRadius = 30;

let cursorPointer = {
	x: canvas.width * 0.5,
	y: canvas.height * 0.5
};

/* ... */

function updateCursorPointer(pointerX, pointerY) {
	let boundary = canvas.getBoundingClientRect();
	cursorPointer.x = pointerX - boundary.left;
	cursorPointer.y = pointerY - boundary.top;
}

document.addEventListener("mousemove", function(event) {
	updateCursorPointer(event.clientX, event.clientY);
});

function draw() {
	pen.clearRect(0, 0, canvas.width, canvas.height);
/* ... */

	pen.beginPath();
	pen.moveTo(cursorPointer.x, cursorPointer.y);
	pen.arc(cursorPointer.x, cursorPointer.y, trackerRadius, 0, 2 * Math.PI);
	pen.fillStyle = "#03c2fc";
	pen.fill();

/* ... */

	requestAnimationFrame(draw);
}
```

The `requestAnimationFrame()` method schedules the `draw()` method for the next frame. The `clearRect()` method is used so that the canvas is completely cleared before painting the next frame.

![Iris](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bo4431cyw391urtl8hfg.gif)

We see that the dot, which will be the iris of the eyeball, moves along with the cursor all over the canvas but not bounded within the eyeball.

##3: Bound the iris within the eyeball.

As we can observe from the image that the eyeball is actually a circle. So, in order to bound the iris within the eyeball, we need to check whether the points read from the cursor pointer is actually present within the circle or not.

Let us consider the equation of a circle:

![Equation of circle](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v8vu84z9hqonu0qyu8n1.png)

Using the above equation, we can easily check whether the point lies within the cirlce or not.

**JS**
```
function updateCursorPointer(pointerX, pointerY) {
let boundary = canvas.getBoundingClientRect();
	let tempX = pointerX - boundary.left;
	let tempY = pointerY - boundary.top;
	
	let h = canvas.width * 0.5;
	let k = canvas.height * 0.5;

	let boundaryRadius = radius - trackerRadius;
	let u = tempX - h;
	let v = tempY - k;
	let eq = (u * u) + (v * v);
	
	if(eq <= (boundaryRadius * boundaryRadius)) {
			cursorPointer.x = tempX;
			cursorPointer.y = tempY;
	}
}
```

![Iris bounded within Eyeball](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w2h4lfpibg4wktjhejav.gif)

##4: Move iris wherever the cursor is within the window

As of now, the iris moves along to the cursor only when the cursor is inside the eyeball. In order to move the iris no matter where the cursor is on the window, we will be using the method to find the point of intersection between a line and a circle.

Refer this link for detailed explanation and examples on how to find [points of intersection](https://www.superprof.co.uk/resources/academic/maths/analytical-geometry/conics/circle-line-intersection.html) between a line and a circle.

So, when substituting the _y_ obtained from the equation of line in the equation of circle, we get the following equation:

![Equation after substituting y](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yttuahb9djdlyw4he5ie.png)

In our case, the circle obviously is the eyeball and the line is obtained from two points - the center of eyeball and cursor pointer's position.

We will find the slope _m_ using the two points, find c from the equation of line _y = mx + c_ and r being the radius of the eyeball.

Let us solve the above quadratic equation using

![Equation for solving a quadratic equation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/34ntzjdebvw304qxcvi8.png)

**JS**

```
function lineEquation(x, slope, constant) {
   return (slope * x) + constant;
}

function discriminant(a, b, c) {
  return (b * b) - (4 * a * c);
}

function isPointBetween(x, y, x1, y1, x2, y2) {
  return (x >= Math.min(x1, x2) && x <= Math.max(x1, x2) &&
          y >= Math.min(y1, y2) && y <= Math.max(y1, y2));
}

function findIntersectionPoint(slope, h, k, point1X, point1Y, constant, circleRadius) {
    var a = 1 + (slope * slope);
	  var b = -2 * (h + slope * (k - constant));
	  var c = (h * h) + (k * k) - (circleRadius * circleRadius) + (constant * (constant - (2 * k)));

    var disc = discriminant(a, b, c);

    if (disc >= 0) {
      const x1 = (-b + Math.sqrt(disc)) / (2 * a);
      const x2 = (-b - Math.sqrt(disc)) / (2 * a);

      const y1 = lineEquation(x1, slope, constant);
      const y2 = lineEquation(x2, slope, constant);
			
			if(isPointBetween(x1, y1, point1X, point1Y, h, k)) {
					cursorPointer.x = x1;
					cursorPointer.y = y1;
			}
			else if(isPointBetween(x2, y2, point1X, point1Y, h, k)) {
					cursorPointer.x = x2;
					cursorPointer.y = y2;
			}
		}
}

function findCursorPointerPosition(h, k, tempX, tempY) {
	let boundaryRadius = radius - trackerRadius;
	let u = tempX - h;
	let v = tempY - k;
	let eq = (u * u) + (v * v);
	
	if(eq <= (boundaryRadius * boundaryRadius)) {
			cursorPointer.x = tempX
			cursorPointer.y = tempY;
	}
	else {
		let x1 = h;
		let y1 = k;
		let x2 = tempX;
		let y2 = tempY;
		
		let slope = (y2 - y1) / (x2 - x1);
		
		let constant = y2 - (slope * x2);
		
		findIntersectionPoint(slope, x1, y1, x2, y2, constant, boundaryRadius);
	}
}
```
Since the result gives out two points, we need to find the point that is between the center of the eyeball and the cursor pointer.

![Iris movement to cursor](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/quqyj4nz3zd32rcf9naw.gif)

##5: Create another eye

Since our dog needs two eyes to see, we will be repeating the same process for the other eye too. But this time, we will be relocating the center of the circles for the left and the right eyes.

**JS**

```
/* ... */

let radius = 50;

/* ... */

function setupCanvas() {
	canvas.width = 250;
	canvas.height = 150;
}

let cursorPointerLeft = {
	x: canvas.width * 0.25,
	y: canvas.height * 0.5
}

let cursorPointerRight = {
	x: canvas.width * 0.75,
	y: canvas.height * 0.5
}

/* ... */

function updateCursorPointer(pointerX, pointerY) {
	let boundary = canvas.getBoundingClientRect();
	let tempX = pointerX - boundary.left;
	let tempY = pointerY - boundary.top;
	
	let h1 = canvas.width * 0.25;
	let k1 = canvas.height * 0.5;
	let h2 = canvas.width * 0.75;
	let k2 = canvas.height * 0.5;

	findCursorPointerPosition(h1, k1, tempX, tempY, cursorPointerLeft);
	findCursorPointerPosition(h2, k2, tempX, tempY, cursorPointerRight); // Add an additional parameter, cursorPointer, in findCursorPointerPosition() and findIntersectionPoint()
}

/* ... */

function draw() {
	pen.clearRect(0, 0, canvas.width, canvas.height);
	let centerX1 = canvas.width * 0.25;
	let centerY1 = canvas.height * 0.5;
	let centerX2 = canvas.width * 0.75;
	let centerY2 = canvas.height * 0.5;
	
	pen.beginPath();
	pen.arc(centerX1, centerY1, radius, 0, 2 * Math.PI);
	pen.fillStyle = "white";
	pen.fill();
	
	pen.beginPath();
	pen.arc(centerX2, centerY2, radius, 0, 2 * Math.PI);
	pen.fillStyle = "white";
	pen.fill();
	
	pen.beginPath();
	pen.moveTo(cursorPointerLeft.x, cursorPointerLeft.y);
	pen.arc(cursorPointerLeft.x, cursorPointerLeft.y, trackerRadius, 0, 2 * Math.PI);
	pen.fillStyle = "#03c2fc";
	pen.fill();
	
	pen.beginPath();
	pen.moveTo(cursorPointerLeft.x, cursorPointerLeft.y);
	pen.arc(cursorPointerLeft.x, cursorPointerLeft.y, trackerRadius * 0.75, 0, 2 * Math.PI);
	pen.fillStyle = "black";
	pen.fill();
	
	pen.beginPath();
	pen.moveTo(cursorPointerRight.x, cursorPointerRight.y);
	pen.arc(cursorPointerRight.x, cursorPointerRight.y, trackerRadius, 0, 2 * Math.PI);
	pen.fillStyle = "#03c2fc";
	pen.fill();
	
	pen.beginPath();
	pen.moveTo(cursorPointerRight.x, cursorPointerRight.y);
	pen.arc(cursorPointerRight.x, cursorPointerRight.y, trackerRadius * 0.75, 0, 2 * Math.PI);
	pen.fillStyle = "black";
	pen.fill();
	requestAnimationFrame(draw);
}
```

![Two Eyeballs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6j8bich3t9vrqb3xzugn.gif)

We have almost achieved what we require but the eyes look like the dog has got its fifth shot of Benedryl in a day >_< !

##6: Correcting the eye movement

So instead of having two different centers for the eye we will gonna be having one, common for both.

![Imaginary circle](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dzvu3x239mffwbyk7uzp.png)

Let us imagine there is a circle with the same radius as the eyeballs between the two eyeballs. We will be moving the irises according to that imaginary circle. Once we calculated the position according to that circle we will adjust the x-axes of the irises according to the eyeballs.

**JS**

```
/* ... */

function findIntersectionPoint(slope, h, k, point1X, point1Y, constant, circleRadius) {
    var a = 1 + (slope * slope);
	  var b = -2 * (h + slope * (k - constant));
	  var c = (h * h) + (k * k) - (circleRadius * circleRadius) + (constant * (constant - (2 * k)));

    var disc = discriminant(a, b, c);

    if (disc >= 0) {
      const x1 = (-b + Math.sqrt(disc)) / (2 * a);
      const x2 = (-b - Math.sqrt(disc)) / (2 * a);

      const y1 = lineEquation(x1, slope, constant);
      const y2 = lineEquation(x2, slope, constant);
			
			if(isPointBetween(x1, y1, point1X, point1Y, h, k)) {
					cursorPointerLeft.x = x1 - canvas.width * 0.25;
					cursorPointerLeft.y = y1;
					cursorPointerRight.x = x1 + canvas.width * 0.25;
					cursorPointerRight.y = y1;
			}
			else if(isPointBetween(x2, y2, point1X, point1Y, h, k)) {
					cursorPointerLeft.x = x2 - canvas.width * 0.25;
					cursorPointerLeft.y = y2;
					cursorPointerRight.x = x2 + canvas.width * 0.25;
					cursorPointerRight.y = y2;
			}
		}
}

function updateCursorPointer(pointerX, pointerY) {
	let boundary = canvas.getBoundingClientRect();
	let tempX = pointerX - boundary.left;
	let tempY = pointerY - boundary.top;
	
	let h = canvas.width * 0.5;
	let k = canvas.height * 0.5;
	
	findCursorPointerPosition(h, k, tempX, tempY);
}

function findCursorPointerPosition(h, k, tempX, tempY) {
	let boundaryRadius = radius - trackerRadius;
	let u = tempX - h;
	let v = tempY - k;
	let eq = (u * u) + (v * v);
	
	if(eq <= (boundaryRadius * boundaryRadius)) {
			cursorPointerLeft.x = tempX - canvas.width * 0.25;
			cursorPointerLeft.y = tempY;
			cursorPointerRight.x = tempX + canvas.width * 0.25;
			cursorPointerRight.y = tempY;
	}
	else {
		let x1 = h;
		let y1 = k;
		let x2 = tempX;
		let y2 = tempY;
		
		let slope = (y2 - y1) / (x2 - x1);
		
		let constant = y2 - (slope * x2);
		
		findIntersectionPoint(slope, x1, y1, x2, y2, constant, boundaryRadius);
	}
}

/* ... */
```

![Corrected eyeball movement](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gvr3xdgt17enqvowv4fp.gif)

To finish off, add your dog face(in my case I designed the dog face in Figma and exported the SVG). And to give a dramatic touch, I reduced the height of the canvas.

Hope you enjoyed this tutorial. Have a nice day!!!