/* --- description: | Eye following the mouse by moving inside a circular eye socket. We assume the eye is initially positioned at the center of the circle. Its position must be absolute or relative (we're using top and left) Specifically, this class provides the behavior of a Pupil. authors: - Goutte demo: - http://jsfiddle.net/goutte/B2Nza licence: - mathematics requires: - core/1.4.5:Class - core/1.4.5:Element - core/1.4.5:Array - core/1.4.5:Options - core/1.4.5:Events provides: - Eye ... */ var Eye = new Class ({ Implements: [Options], options: { socketRadius: 5, // radius of the circle in which the eye can move stickToSocket: true, // constraint the eye to the perimeter of the circle behavior: 'follow', // or 'flee', constrained to the eye socket bindResize: true, // re-setups the spatial context of the eye on resize bindMouseMove: true, bindTouchMove: false, // should this be true by default ? eventListenerElement: window }, initialize: function (element, options) { if (options.eventListenerElement) options.eventListenerElement = document.id(options.eventListenerElement); this.setOptions(options); this.element = document.id(element); if (-1 == ['relative','absolute'].indexOf(this.element.getStyle('position'))) throw new Error("Eye must be positioned as absolute or relative."); this.initialElementTop = this.element.getStyle('top').toInt(); this.initialElementLeft = this.element.getStyle('left').toInt(); this.setupSpatialContext(); this.bindEyeMoveEvents(); this.bindResizeEvent(); }, setupSpatialContext: function () { this.coordinates = this.element.getCoordinates(); }, bindEyeMoveEvents: function () { if (this.options.bindMouseMove) { this.options.eventListenerElement.addEvent('mousemove', function(event){ this.reactToEventPosition(event.page.x, event.page.y); }.bind(this)); } if (this.options.bindTouchMove) { this.options.eventListenerElement.addEvent('touchmove', function(event){ event.preventDefault(); var touch = event.touches[0]; this.reactToEventPosition(touch.pageX, touch.pageY); }.bind(this)); } }, bindResizeEvent: function () { if (this.options.bindResize) { this.options.eventListenerElement.addEvent('resize', function(event){ this.setupSpatialContext(); }.bind(this)); } }, /** * Makes the element react to the coords specified according to configuration * while restraining itself in a circle of radius `options.socketRadius` * * @param x * @param y */ reactToEventPosition: function (x, y) { switch (this.options.behavior) { case 'flee': this.lookAway(x, y); break; case 'follow': default: this.lookAt(x, y, 1); } }, /** * Makes the element move in the direction of the coords specified * while restraining itself in a circle of radius `options.socketRadius` * * @param x * @param y * @param factor */ lookAt: function (x, y, factor) { if (typeof factor == 'undefined' || null == factor) factor = 1; var eyeX = this.coordinates.left + this.coordinates.width / 2; var eyeY = this.coordinates.top + this.coordinates.height / 2; var newPos = this.normalize(x - eyeX, y - eyeY, this.options.socketRadius * factor); newPos.x = newPos.x + this.initialElementLeft; newPos.y = newPos.y + this.initialElementTop; this.element.setPosition(newPos); }, /** * Makes the element move in the *opposite* direction of the coords specified * while restraining itself in a circle of radius `options.socketRadius` * * @param x * @param y */ lookAway: function (x, y) { this.lookAt(x, y, -1); }, normalize: function (x, y, factor) { if (typeof factor == 'undefined' || null == factor) factor = 1; var r = {x:x, y:y}; var norm = Math.sqrt(x*x + y*y); if (norm != 0 && (this.options.stickToSocket || norm > Math.abs(factor))) { r.x = x * factor / norm; r.y = y * factor / norm; } return r; } });