In the previous article I demonstrated how to make an element rotate to follow the position of a cursor or a touch on the screen. At the time, I explained that the technique was not necessarily limited to a single element: in this article, I'll show how to apply the code to two elements simultaneously, in the form of eyes that follow the mouse / touch position; you can also see the code at the associated CodePen demo.
Making Faces
First, we need to create the smiley face itself. While we could use SVG to create the shapes, it's about the same amount of effort to do so in HTML & CSS… and since the pupils will be HTML elements anyway, it makes sense to create the rest of the face in markup:
<div id="face">
<div class="left eye">
<div class="pupil"></div>
</div>
<div class="right eye">
<div class="pupil"></div>
</div>
<div id="mouth"></div>
</div>
To make the face responsive, I've measured it using vw
units. (For the demo above, I've also restricted the max height and width).
div#face {
width: 40vw;
height: 40vw;
max-width: 400px;
max-height: 400px;
margin: 0 auto;
position: relative;
background: yellow;
margin-top: 2rem;
}
All the elements are rounded using border-radius
:
div#face, div#face * { border-radius: 50%; }
The eyes are absolute
positioned inside the face:
.eye {
width: 25%;
height: 25%;
background: #fff;
position: absolute;
top: 20%;
}
.left.eye { left: 20%; }
.right.eye { right: 20%; }
The pupils are positioned as classes inside the eyes, with their transform-origin
set at the bottom center of each circle:
.pupil {
background: #000;
width: 50%;
height: 50%;
position: relative;
left: 25%;
transform-origin: bottom;
}
The “mouth” is an oval with a border only on the bottom edge:
#mouth {
position: relative;
width: 60%;
left: 20%;
height: 25%;
border: 1vw solid transparent;
border-radius: 50%;
top: 50%;
border-bottom-color: #000;
}
Making Things Go Googly
The pupil elements are gathered together in a nodelist, and their centers of rotation written as properties of each:
var pupils = document.querySelectorAll(".pupil");
for (var i = 0; i < pupils.length; i++) {
var offset = pupils[i].getBoundingClientRect();
pupils[i]['centerX'] = offset.left + offset.width/2,
pupils[i]['centerY'] = offset.bottom;
}
Three events will trigger goGoogly
, the function that moves the eyes:
window.addEventListener('mousemove', goGoogly);
window.addEventListener('touchstart', goGoogly);
window.addEventListener('touchmove', goGoogly);
The function itself:
function goGoogly(e) {
var pointerEvent = e;
if (e.targetTouches && e.targetTouches[0]) {
e.preventDefault();
pointerEvent = e.targetTouches[0];
mouseX = pointerEvent.pageX + window.pageXOffset;
mouseY = pointerEvent.pageY + window.pageYOffset;
} else {
mouseX = e.clientX + window.pageXOffset,
mouseY = e.clientY + window.pageYOffset;
}
for (var i = 0; i < pupils.length; i++) {
pupils[i]['radians'] = Math.atan2(mouseX - pupils[i]['centerX'], mouseY - pupils[i]['centerY']),
pupils[i]['degree'] = (pupils[i]['radians'] * (180 / Math.PI) * -1);
pupils[i].style.transform = 'rotate('+ (pupils[i]['degree'] + 180) + 'deg)';
}
}
This code is similar to the previous example, with the following exceptions:
- the code loops through the
pupils
array to affect each element. - the calculation used for a single element in the previous example is now applied to
multiple elements
.
As you can see, the code works to rotate the individual eyes to their own orientation, each pointing to the current position of the cursor.
Taking Things Further

It's entirely possible to take this much further, as demonstrated in this wonderful demo by Adult Swim on CodePen, which uses xEyes to rotate the pupils into position.
Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/rLAKXz