Last week on the code challenge #4, we delved into mouse tracking with JavaScript and simple animations with CSS. The goal of the challenge was to implement a mouse tracking feature on an Alien and have its eye simply track the position and movement of the mouse cursor. We shall proceed to discuss the solution to this challenge.

The Base

The base of our challenge consisted of HTML and CSS which constructed the Alien body.

HTML

Divs were used to create individual parts of the Alien including the monster, body, ears, mouth, tooth, and eye.

<div class="ufo">
  <div class="monster" style="color: #7cb342">
    <div class="body">
      <div class="ear"></div>
      <div class="ear"></div>
      <div class="vampi-mouth">
        <div class="vampi-tooth"></div>
      </div>
    </div>
    <div class="eye-lid">
      <div class="eyes">
        <div class="eye"></div>
      </div>
    </div>
  </div>
</div>

CSS

CSS was used to style the individual elements. See the codepen demo for the base CSS used.

The Eye which is of particular concern to us was split into three parts - eye-lid, eyes, and eye. We needed to gain control of each part of the eye to control it thereby splitting it into various parts. The eye-lid is the larger white pear-shaped part and is styled with:

.eye-lid {
  text-align: center;
  display: flex;
  font-size: 0.65em;
  width: 1em;
  height: 1em;
  position: absolute;
  left: 0.25em;
  top: 0.3em;
  background-color: white;
  border-radius: 0.5em 0.5em 0.5em 0.5em / 0.6em 0.6em 0.4em 0.4em;
  box-shadow: 0.03em 0.14em rgba(0,0,0,0.1);
}

To isolate the eye-lid from the rotating part of the eyes, we created a div partly similar to the eye-lid which serves as the parent element of the rotating part. We name this, eyes and it's styled with:

.eyes{
  text-align: center;
  display: flex;
  font-size: 0.65em;
  width: 1em;
  height: 1em;
  position: absolute;
  left: 0.25em;
  top: 0.3em;
}

The eyes element will be controlled by JavaScript to implement the rotating feature.

The black pupil was created with the eye element whereas the white shadow on the pupil was created using the pseudo-class :after. These two elements were styled with:

.eye {
  position: relative;
  display: inline-block;
  border-radius: 50%;
  width: 75%;
  height: 75%;
  background-color: black;
  border-radius: 50%;
}

.eye:after { /*white shadow*/
  --pupil-size: 0.2em;
  position: absolute;
  top: 0.05em;
  left: 0.3em;
  width: var(--pupil-size);
  height: var(--pupil-size);
  background: white;
  border-radius: 50%;
  content: " ";
}

The Technique

Mouse Tracking

On implementing the mouse tracking feature, JavaScript was simply used to track the position of the mouse cursor relative to the eyes div and the div is rotated in the direction of the cursor using the CSS transform property.

Fetching Element Position

Since the whole page is to be tracked for cursor movement, we assigned the parent ufo div to a variable called ufo. An addeventListener() method was used to track the mousemove event. Hence, once the mouse is moved over the selected area (ufo) the event function triggers.

let ufo = document.querySelector('.ufo');

ufo.addEventListener('mousemove', (e) => {

});

Next, the eyes element is selected from the DOM and assigned to a variable - eye. The getBoundingClientRect() method is used to get the top and left position of the eye element relative to the viewport. This comes in handy when we would like to determine the relative position of our element with respect to our viewport.

ufo.addEventListener('mousemove', (e) => {
  let eye = document.querySelector('.eyes');
  let mouseX = (eye.getBoundingClientRect().left); 
  let mouseY = (eye.getBoundingClientRect().top);
});

Fetching Rotation Degrees

To achieve this, we employ the Math.atan2() function to calculate the arctangent of the mouse position at any given point. The pageX property returns the horizontal coordinates of the mouse on triggering the mousemove event and the corresponding pageY property also returns the vertical coordinates, all relative to the left and top of the listened area - ufo, repectively. We now have:

let ufo = document.querySelector('.ufo');

ufo.addEventListener('mousemove', (e) => {
  let eye = document.querySelector('.eyes');
  let mouseX = (eye.getBoundingClientRect().left); 
  let mouseY = (eye.getBoundingClientRect().top);
  let radianDegrees = Math.atan2(e.pageX - mouseX, e.pageY - mouseY);
});

We employ a formula to calculate the angle of the mouse cursor in degrees, relative to the eyes element. This converts radianDegrees into a rotation degree with a range from 0 - 360degrees depending on the position of the cursor.

let rotationDegrees = (radianDegrees * (180/ Math.PI) * -1) + 180;

Rotate the Eye

The value of rotationDegrees is the angle of the cursor position relative to the eyes element. Therefore, by setting the CSS transform property with JavaScript, we simply rotate the eyes element to the cursor on mousemove using the value of rotationDegrees. We now have:

let ufo = document.querySelector('.ufo');

ufo.addEventListener('mousemove', (e) => {
  let eyes = document.querySelector('.eyes');
  let mouseX = (eyes.getBoundingClientRect().left); 
  let mouseY = (eyes.getBoundingClientRect().top);
  let radianDegrees = Math.atan2(e.pageX - mouseX, e.pageY - mouseY);
  let rotationDegrees = (radianDegrees * (180/ Math.PI) * -1) + 180;
  eyes.style.transform = `rotate(${rotationDegrees}deg)`
});

Bonus: Adding The Eye Blink Feature

To achieve this, we used CSS animations. The animation name - blink, solves our challenge. In the eye-lid class, we include these animation properties and their values:

  animation-name: blink;
  animation-timing-function: ease-in-out;
  animation-direction: forwards;
  animation-iteration-count: infinite;
  animation-duration: 10s;

The property names clearly explain what each property does. However, we shall use the shorthand animation property. We concatenate the animation properties into:

  animation: blink forwards infinite 10s ease-in-out;

This solves the problem.

Here is the final product.

Conclusion

So far I believe we have learned a number of things from this awesome challenge including cursor tracking and simple CSS animations, let's look forward to the next challenge on Monday. Happy coding!