MISCELLANEOUS TECHNICAL ARTICLES BY Dr A R COLLINS

JavaScript Xeyes

What is Xeyes

Xeyes is a small program that shows a pair of eyes whose pupils move to follow the location of the cursor about the screen. It was originally written for the NeXT operating system and was then ported to X-Windows where it became a standard graphics demo program.

face eye eye

Basic principle of Xeyes

The basic idea of xeyes is simple, every time the cursor is moved calculate the direction of the cursor from the centre of each eye, then move the pupils of the eyes in the direction of the cursor, but only as far as the radius of the eye socket. If the cursor is inside the radius of one of the eyes that pupil is moved to the location of the cursor.

Simple xeyes demonstrations often just represent the eyes as two circles and the pupils as two filled circles constrained to lie within them. More complex eye images, as shown in the Salvador Dali image above require three layers of images to achieve the realistic effect.

The bottom layer image provides the color for the 'whites' of the eyes. Over this are positioned the images of the pupils. These are gif or png images with the round pupil solid color and the rest of the image transparent to allow the 'whites' of the eyes to show around the pupils.

The top most layer is the face acting as a mask. This is again a gif so that the eyes can be transparent areas with the pupil and whites showing through. Since the eye holes in the face aren't circular, the edges of the eyes will partly obscure the pupils as they move around as is the case with real eyes.

Xeyes geometry

A schematic of the xeyes geometry is shown in the diagram to the right.

In this diagram the cursor is outside the radius of the eye socket. The cursor location is csrX, csrY measured from the top left of the page. The location of the eye centre is e1x, e1y again measured with respect to the top left of the page. The x and y displacement of the cursor from the centre of the eye is given by:

dx = csrX - e1x
dy = csrY - e1y.

The maximum radius of movement of the pupil, r, depends on the size of the eye socket rendered on the screen. It is shown in the diagram as the thin grey circle. When the cursor is outside this circle the center of the pupil is on this circle in the direction of the cursor. The offset of the pupil from the centre of the eye and the offset of the cursor from the centre of the eye, form similar triangles. So the distance to move the pupil in the x and y directions is given by:

x = (r/R)*dx
y = (r/R)*dy.

The value of R is readily calculated from Pythagoras's theorem:

R = Math.sqrt(dx*dx+dy*dy);

The function called for any cursor move is shown below:

Xeyes.prototype.eyesMove = function(e)
{
  var csrX, csrY;
  var x, y;
  var dx, dy;
  var R;
  var savThis = this;

  document.onmousemove = null;  // turn off mouseMove events
  // add any scroll to get csr location wrt to the top left of the document.
  csrX = e.clientX + document.documentElement.scrollLeft;
  csrY = e.clientY + document.documentElement.scrollTop;
  // eye 1 first
  dx = csrX - this.e1x;
  dy = csrY - this.e1y;
  R = Math.sqrt(dx*dx+dy*dy);     // distance from eye centre to csr
  x = (R < this.r1)? dx : dx*this.r1/R;
  y = (R < this.r1)? dy : dy*this.r1/R;
  this.eye1Obj.style.left = x + this.e1xLoc + "px";
  this.eye1Obj.style.top = y + this.e1yLoc + "px";
  // now for eye 2
  dx = csrX - this.e2x;
  dy = csrY - this.e2y;
  R = Math.sqrt(dx*dx+dy*dy);
  x = (R < this.r2)? dx : dx*this.r2/R;
  y = (R < this.r2)? dy : dy*this.r2/R;
  this.eye2Obj.style.left = x + this.e2xLoc + "px";
  this.eye2Obj.style.top = y + this.e2yLoc + "px";
  // set a timer to make a delayed call to setup mousemove event
  window.setTimeout(function(){
    document.onmousemove = associateObjWithEvent(savThis, "eyesMove")
    }, 100);
}

The cursor coordinates, e.clientX and e.clientY gives the cursor position relative to the top left of the screen, the browser viewport. But the coordinates of the page elements are all referred to the top left of the page. The viewport may have been scrolled so that the top left of the page is not the top left of the screen. The terms document.documentElement.scrollLeft and ...scrollTop correct for any scrolling so that csrX and csrY refer to the offset from the top left of the page.

The term em2px is the conversion factor for switching between screen dimension (pixels) and page element dimensions (em). If the user changes the font size used by their browser this must be recalculated.

Implementation details

To get the basic geometry to work in operational code, rather than just tutorial examples, some additional points should be noted.

HTML Code

The set of images required for xeyes (face, leftEye, rightEye and maybe a background image for the sockets) should all be children of a wrapper DIV element. This wrapper must have CSS style position: absolute, or position: relative to serve as a parent. The wrapper DIV should have border:0 and padding:0 to make sure the 'face' image has the same top and left position values as the wrapper. This is necessary as the position calculations are made relative to face top, left but the actual positioning of the eyes is done relative to the wrapper top, left.

Too Many MouseMove Events

The number of events generated by the movement of the mouse can be very large. Processing each interrupt can impact on other processing tasks. This is particularly noticeable if the page has animation running that requires regular interrupt servicing. If too much time is spent in mouse move event handlers then the animation may start to look jerky.

The problem is addressed here by simply turning off mouse move events each time an event is processed by the eyesMove() function then on exit from eyesMove(), a timer is set to turn back on the mouse move events in 100msec. The delay length can be tweaked to suit the particular application. Shorter times if there are few other demands will give fast, smooth eye movement. Longer intervals will allow more time for other processing but will make eye movements more sluggish.

Get Element Position

The left and top offset of several elements of this page are required. All elements are positioned using CSS styling. Cursor location is only accessible in units of pixels. To calculate the offset of an elements position from the cursor location, the position of the element with respect to the top left of the page (in units of pixels) is required.

The cross browser method for retrieving an element's top, left position written by Kirupa Chinnathambi is as follows:

function getPosition(element)
{
  var xPosition = 0;
  var yPosition = 0;

  while(element)
  {
    xPosition += (element.offsetLeft - element.scrollLeft + element.clientLeft);
    yPosition += (element.offsetTop - element.scrollTop + element.clientTop);
    element = element.offsetParent;
  }
  return { x: xPosition, y: yPosition };
}