Rss feed

JavaScript eyes that follow the cursor.

I remember one of the first tutorials I wrote back on my old thybag website was on how to get eyes that follow the cursor using Flash. I’d initially worked out how to do it in order to animate the alien in my banner a little and although the effect can be a little disconcerting at times, I kinda liked it.

For that reason I decided to set myself the challenge of getting the functionality back, only this time using JavaScript instead of flash. Rather than waste space with extra images I also decided I may as well draw the eyes with canvas while I was at it.

The aim of this little tutorial will be to show you how to create something like this.
(Though you will more likely want to use your own image.)

If your not really intested in how its done and just want the script, feel free to grab it from here. You can create eye’s by calling

//Variables are just used to make it obvious what each parameter is, 
//you can call the functions with the param's directly if you prefer.
var positionFromLeft = 200;
var positionFromTop = 50;
var eyeSize = 30;
var appendEyetoDivNamed = 'mydiv';
new alienEye(positionFromLeft,positionFromTop,eyeSize,appendEyetoDivNamed);

If your interested to see how the eyes following the cursor script works, keep reading after the jump.


The JavaScript behind the cursor following eye’s consist’s of two JavaScript objects, cursorLocation and alienEye.

CusorLocation is basically just a helper object which gives alienEye a really easy way of getting the current position of the users cusor relative to then document. Its then left to alienEye to both create and rotate the eyes in such a way that they end up pointing towards the users cursor.

Since its simpler i’ll go over how cursorLocation works first. Its complete code is as follows.

//Create Object to store and provide access to current cusor position
var cursorLocation = new function(){
	this.x = 0;
	this.y = 0;
	//This function is called onmousemove to update the stored position
	this.update = function(e){
		var w = window, b = document.body;
		this.x =  e.clientX + (w.scrollX || b.scrollLeft || b.parentNode.scrollLeft || 0);
		this.y = e.clientY + (w.scrollY || b.scrollTop || b.parentNode.scrollTop || 0);
	}
}
//Hook onmousemove up to the above update function.
document.onmousemove=function(e){ cursorLocation.update(e); };

Firstly rather than defining the function normally with a name etc, I’ve instead created an anonymous function  (functions are objects in JavaScript) and placed an instance of it straight in to the varible cursorLocation. This makes sense as I’m really only ever going to want one of these objects and having it in a nice global scoped variable makes it easy to access.
The next two lines just create two fields in the cursorLocation object to store the x and y of the cursor position.

var cursorLocation = new function(){
	this.x = 0;
	this.y = 0;

The next 5 lines set up a method inside the cursorLocation object called update. This will be called onmousemove and passed the event. The middle 3 lines just do a little basic math to work to exact cusor position taking in to account whether the user has scrolled down the page etc. The results of this math are stored in to the objects x and y fields for easy access by our alienEyes object later on. The use of the OR operator is essentially just a quick way to get the browsers JavaScript engine to work out which attributes are actually available to it (as some browsers *helpfully* don’t necessary always use the same names for them)

this.update = function(e){
		var w = window, b = document.body;
		this.x =  e.clientX + (w.scrollX || b.scrollLeft || b.parentNode.scrollLeft || 0);
		this.y = e.clientY + (w.scrollY || b.scrollTop || b.parentNode.scrollTop || 0);
	}

And the final line pretty much does what it says on the tin. It calls cursorLocation’s update method every-time the onmousemove event is fired, which ensures the the values stored in the cusorLocation objects x,y fields are always up to date.

The alienEye object is defined like a normal function as its something people are likely to want multiple instances of (each eye is an independent object). The fullcode for the alienEye function is:

/**
Alien Eye Object
Creates an eye that will follow the users cursor.
*/
function alienEye(x,y,size,append){
	var self = this;
	var i = 0;
	this.x = x;
	this.y = y;
	this.size = size;
	//Create the Eye Dom Node useing canvas.
	this.create = function create(x,y,size,append){
		//Create dom node
		var eye = document.createElement('canvas');
		eye.width=size;
		eye.height=size;
		eye.style.position = 'relative';
		eye.style.top = y+'px';
		eye.style.left = x+'px';
		document.getElementById(append).appendChild(eye);
		//Get canvas
		canvas = eye.getContext("2d")
		//draw eye
		canvas.beginPath();
		radius = size/2;
		canvas.arc(radius, radius, radius, 0, Math.PI*2, true); 
		canvas.closePath();
		canvas.fillStyle = "rgb(255,255,255)";
		canvas.fill();
		//draw pupil
		canvas.beginPath();
		canvas.arc(radius, radius/2, radius/4, 0, Math.PI*2, true); 
		canvas.closePath();
		canvas.fillStyle = "rgb(0,0,0)";
		canvas.fill();

		return eye;
	}
	//Rotate the Dom node to a given angle.
	this.rotate = function(x){
		this.node.style.MozTransform="rotate("+x+"deg)";
		this.node.style.WebkitTransform="rotate("+x+"deg)";
		this.node.style.OTransform="rotate("+x+"deg)";
		this.node.style.msTransform="rotate("+x+"deg)";
		this.node.style.Transform="rotate("+x+"deg)";

	}
	//Update every 100 miliseconds
	setInterval(function(){
		//Math!
		angleFromEye = Math.atan2((cursorLocation.y-self.my_y), cursorLocation.x-self.my_x)*(180/Math.PI)+90;
		//Rotate
		self.rotate(angleFromEye);
		//Refresh own position every 25th time (in case screen is resized)
		i++;if(i>25){self.locateSelf();i=0;}

	},100);

	this.locateSelf = function(){
		this.my_x = this.node.offsetLeft + (this.size/2);
		this.my_y = this.node.offsetTop + (this.size/2);
		//If it has offsetParent, add em up to get the objects full position.
		if (this.node.offsetParent) {
			temp = this.node;
			while(temp = temp.offsetParent){
				this.my_x +=temp.offsetLeft;
				this.my_y +=temp.offsetTop;
			}
		}
	}

	//Call the node create function when the AlienEye Object is created.
	this.node = this.create(x,y,size,append);
	this.locateSelf();

}

An instance of the alien eye object is created with the code

new alienEye(10,10,10,'someDiv');

Which pass’s 4 parameters to the alienEye object, relative position from the left, relative position from the top. How big the eye should be and then finally which div the created eye should be placed in (passed as an element ID)
The first thing a newly created instance of alienEye does is to store some of these values internally for use later. Additionally it sets a variable called self and assigns this (a reference to the current object) in to it. This is needed so we can still access the current instance of alien eye inside the function called by setInterval later in the script.

function alienEye(x,y,size,append){
	var self = this;
	var i = 0;
	this.x = x;
	this.y = y;
	this.size = size;

The first function to add to our alienEye object is that of create. This is called right at the bottom of alienEye objects code and actually creates the eye and add’s itself in to the website. To start with it creates a canvasElement to work with, assigns it a height & width (canvas doesn’t appear to respect height and widths defined in css for some reason), as well as its relative positioning. Finally it adds the canvas element in to the div with the id passed as the functions 4th parameter.

this.create = function create(x,y,size,append){
		//Create dom node
		var eye = document.createElement('canvas');
		eye.width=size;
		eye.height=size;
		eye.style.position = 'relative';
		eye.style.top = y+'px';
		eye.style.left = x+'px';
		document.getElementById(append).appendChild(eye);

The next step is to actually draw the eye, for this we grab the context of canvas element we created and use canvas.arc to draw a circle. A fillStyle is then set and finally fill is called to draw a nice white circle to work with. If your unfamiliar with canvas w3 schools a little a tutorial with a few quick examples.

		//Get canvas
		canvas = eye.getContext("2d")
		//draw eye
		canvas.beginPath();
		radius = size/2;
		canvas.arc(radius, radius, radius, 0, Math.PI*2, true); 
		canvas.closePath();
		canvas.fillStyle = "rgb(255,255,255)";
		canvas.fill();

To draw the pupil we pretty much do the same thing again, except we make it black, a quarter of the size and position it at the top of the eye. Once we have done this we end the function by returning the reference to the eye DOM node back to whoever called the create function to start with.

		//draw pupil
		canvas.beginPath();
		canvas.arc(radius, radius/2, radius/4, 0, Math.PI*2, true); 
		canvas.closePath();
		canvas.fillStyle = "rgb(0,0,0)";
		canvas.fill();

		return eye;
	}

The next method we add to our alienEye class is a rotate function. The rotate function doesn’t do a lot other than change some CSS styles though i find it useful due to the fact pretty much every browser pretty much only seems to use its own version for now (despite the fact the syntax is identical)

	//Rotate the Dom node to a given angle.
	this.rotate = function(x){
		this.node.style.MozTransform="rotate("+x+"deg)";
		this.node.style.WebkitTransform="rotate("+x+"deg)";
		this.node.style.OTransform="rotate("+x+"deg)";
		this.node.style.msTransform="rotate("+x+"deg)";
		this.node.style.Transform="rotate("+x+"deg)";

	}

The next part of the code although rather short is actually responsible for doing most of the work. An interval is created to run the contained code every milliseconds and thus update which way the eye points.
To work out what angle the eye needs to be at to point at the cursor we are forced to use a little trigonometry combined with an ability to figure out both the exact position of the cursor and exact position of the eye node relative to the document.

Once we have the angle we need to rotate the eye too, we just call our rotate function and pass it along.

The final bit of code in this section is to prevent the eyes going screwy if the location of the eye node changes (for instance when this site resizes itself.) Rather than work out the position of the eye each time (which is a bit of a waste) the final line of code ensures its only reworked out every 25th time.

	//Update every 100 miliseconds
	setInterval(function(){
		//Math!
		angleFromEye = Math.atan2((cursorLocation.y-self.my_y),cursorLocation.x-self.my_x)*(180/Math.PI)+90;
		//Rotate
		self.rotate(angleFromEye);
		//Refresh own position every 25th time (in case screen is resized)
		i++;if(i>25){self.locateSelf();i=0;}

	},100);

The final function for our alienEye object is the locateSelf method. The job of this method is to figure out the exact top and left distance from centre of the eye nodes to the document top. To do this we make use of the offsetLeft and offsetTop attributes which give us the left and top position’s relative to offsetParent of the node. To get the full top and left position we then need to pull the offset of any offsetparents we encounter till we can find no more. To do this a while loop is used to add up the offsetLeft and offsetTop of any offsetParent nodes it can find, following the chain upwards.

In addition, since the offset’s get the position from the left and top sides of our canvas’s, we add half the eye’s size to both the top and bottom so we have the centre.

	//Work out exact position of the eye's center. (relative to top)
	this.locateSelf = function(){
		//get basic position
		this.my_x = this.node.offsetLeft + (this.size/2);
		this.my_y = this.node.offsetTop + (this.size/2);
		//If it has offsetParent, add em up to get the objects full position.
		if (this.node.offsetParent) {
			temp = this.node;
			while(temp = temp.offsetParent){
				this.my_x +=temp.offsetLeft;
				this.my_y +=temp.offsetTop;
			}
		}
	}

The last two lines of the alienEye object simply call the create method to create the eye itself, which it stores a reference to it an field called node. Then finally to perform an initial locateSelf call so the eye’s know where they are.

	this.node = this.create(x,y,size,append);
	this.locateSelf();

And that pretty much concludes this tutorial. I’d be happy to answer any questions in the comments, or hear any constructive criticism aimed at the script itself. Feel free to make use of the code in your own work, a link back would be nice, but it ain’t essential.

Tags: , , , , , ,


7 Responses to “JavaScript eyes that follow the cursor.”

  1. Paul McCauley says:

    June 28th, 2011 at 6:14 pm

    Hey, I really love this tutorial, I’m only learning Jquery and Javascript now so it got a wee bit confusing when you got to the HTML 5 lark with the canvas, lol, I was wondering would you be able to point me in the right direction If I wanted to rotate a .png file to follow the mouse?

  2. Carl says:

    June 28th, 2011 at 7:21 pm

    Hello,

    Thanks for the feedback. The easiest way to get the script above to use an image for the eyes is probably to switch out the this.create function with something along the lines of this:

    this.create = function create(x,y,size,append){
    		//Create DOM node (an img this time)
    		var eye = document.createElement('img');
    
    		eye.src = "/somefolder/myEyeImage.png"; //Image URL here
    
    		eye.style.width=size;
    		eye.style.height=size;
    		eye.style.position = 'relative';
    		eye.style.top = y+'px';
    		eye.style.left = x+'px';
    		document.getElementById(append).appendChild(eye);
    		return eye;
    	}
    

    Basically rather than creating a Canvas element and drawing the eye on to it, it creates an image element then sets its src to the location of whatever image you want to be the eye itself. (Just make sure the eye image by default is looking upwards)

    Since its a bit of a lazy fix you’ll still have to set the height/width of the image via third parameter of the “new alienEye()” constructor.

    Hope that helps,

    Carl

  3. Mike says:

    March 14th, 2012 at 10:53 pm

    Great Work, when i saw your work instantly i had an idea to how implement this example with a client website redesign but in this case is a owl. Thx to share

  4. David says:

    March 28th, 2012 at 3:43 pm

    Please can you explain me step by step how to do it properly, like what where to write, upload etc. as I’m completely new at javascript. And i really want to get this done.

    Many Thanks,
    David

  5. Alex says:

    August 22nd, 2012 at 1:44 pm

    It doesn’t work on internet explorer

  6. Joe says:

    August 14th, 2013 at 1:20 am

    Very Interested.

    Where do the script tags go and what section is pasted in the body? Thanks…

  7. drgonzo says:

    December 13th, 2013 at 4:34 am

    hey man this is very awesome. i got it working within an hour and i am a beginner. thank you !

Leave a Reply