// jquery.jparallax.js

// 0.9.1

// Stephen Band

//

// Dependencies:

// jQuery 1.2.6 (jquery.com)

//

// Project and documentation site:

// http://webdev.stephband.info/parallax.html





// CLOSURE



(function(jQuery) {





// PRIVATE FUNCTIONS



function stripFiletype(ref) {

  var x=ref.replace('.html', '');

  return x.replace('#', '');

}



function initOrigin(l) {

  if (l.xorigin=='left')	{l.xorigin=0;}	else if (l.xorigin=='middle' || l.xorigin=='centre' || l.xorigin=='center')	{l.xorigin=0.5;}	else if (l.xorigin=='right')	{l.xorigin=1;}

  if (l.yorigin=='top')		{l.yorigin=0;}	else if (l.yorigin=='middle' || l.yorigin=='centre' || l.yorigin=='center')	{l.yorigin=0.5;}	else if (l.yorigin=='bottom')	{l.yorigin=1;}

}



function positionMouse(mouseport, localmouse, virtualmouse) {



  var difference = {x: 0, y: 0, sum: 0};

  

	// Set where the virtual mouse is, if not on target

  if (!mouseport.ontarget) {

    

    // Calculate difference

    difference.x    = virtualmouse.x - localmouse.x;

    difference.y    = virtualmouse.y - localmouse.y;

    difference.sum  = Math.sqrt(difference.x*difference.x + difference.y*difference.y);

    

    // Reset virtualmouse

    virtualmouse.x = localmouse.x + difference.x * mouseport.takeoverFactor;

    virtualmouse.y = localmouse.y + difference.y * mouseport.takeoverFactor;

    

    // If mouse is inside the takeoverThresh set ontarget to true

    if (difference.sum < mouseport.takeoverThresh && difference.sum > mouseport.takeoverThresh*-1) {

    	mouseport.ontarget=true;

    }

  }

  // Set where the layer is if on target

  else {

    virtualmouse.x = localmouse.x;

    virtualmouse.y = localmouse.y;

  }

}



function setupPorts(viewport, mouseport) {



	var offset = mouseport.element.offset();

		

  jQuery.extend(viewport, {

    width: 		viewport.element.width(),

    height: 	viewport.element.height()

  });

  

  jQuery.extend(mouseport, {

    width:		mouseport.element.width(),

    height:		mouseport.element.height(),

    top:			offset.top,

    left:			offset.left

  });

}



function parseTravel(travel, origin, dimension) {

  

  var offset;

  var cssPos;

  

  if (typeof(travel) === 'string') {

    if (travel.search(/^\d+\s?px$/) != -1) {

      travel = travel.replace('px', '');

      travel = parseInt(travel, 10);

      // Set offset constant used in moveLayers()

      offset = origin * (dimension-travel);

      // Set origin now because it won't get altered in moveLayers()

      cssPos = origin * 100 + '%';   

      return {travel: travel, travelpx: true, offset: offset, cssPos: cssPos};

    }

    else if (travel.search(/^\d+\s?%$/) != -1) {

      travel.replace('%', '');

      travel = parseInt(travel, 10) / 100;

    }

    else {

      travel=1;

    }

  }

  // Set offset constant used in moveLayers()

  offset = origin * (1 - travel);

  return {travel: travel, travelpx: false, offset: offset}

}



function setupLayer(layer, i, mouseport) {



  var xStuff;

  var yStuff;

  var cssObject = {};



  layer[i]=jQuery.extend({}, {

  	width:		layer[i].element.width(),

  	height:		layer[i].element.height()

  }, layer[i]);



  xStuff = parseTravel(layer[i].xtravel, layer[i].xorigin, layer[i].width);

  yStuff = parseTravel(layer[i].ytravel, layer[i].yorigin, layer[i].height);



  jQuery.extend(layer[i], {

  	// Used in triggerResponse

  	diffxrat:    mouseport.width / (layer[i].width - mouseport.width),

  	diffyrat:    mouseport.height / (layer[i].height - mouseport.height),

  	// Used in moveLayers

  	xtravel:     xStuff.travel,

  	ytravel:     yStuff.travel,

  	xtravelpx:   xStuff.travelpx,

  	ytravelpx:   yStuff.travelpx,

  	xoffset:     xStuff.offset,

  	yoffset:     yStuff.offset

  });

  

  // Set origin now if it won't be altered in moveLayers()

  if (xStuff.travelpx) {cssObject.left = xStuff.cssPos;}

  if (yStuff.travelpx) {cssObject.top = yStuff.cssPos;}

  if (xStuff.travelpx || yStuff.travelpx) {layer[i].element.css(cssObject);}

}



function setupLayerContents(layer, i, viewportOffset) {



  var contentOffset;



  // Give layer a content object

  jQuery.extend(layer[i], {content: []});

  // Layer content: get positions, dimensions and calculate element offsets for centering children of layers

  for (var n=0; n<layer[i].element.children().length; n++) {

	  

	  if (!layer[i].content[n])          layer[i].content[n]             = {};

	  if (!layer[i].content[n].element)  layer[i].content[n]['element']  = layer[i].element.children().eq(n);

	  

	  // Store the anchor name if one has not already been specified.  You can specify anchors in Layer Options rather than html if you want.

    if(!layer[i].content[n].anchor && layer[i].content[n].element.children('a').attr('name')) {

    	layer[i].content[n]['anchor'] = layer[i].content[n].element.children('a').attr('name');

	  }

	  

	  // Only bother to store child's dimensions if child has an anchor.  What's the point otherwise?

	  if(layer[i].content[n].anchor) {

      contentOffset = layer[i].content[n].element.offset();

	  	jQuery.extend(layer[i].content[n], {

	  		width: 		layer[i].content[n].element.width(),

	  		height:		layer[i].content[n].element.height(),

	  		x:			  contentOffset.left - viewportOffset.left,

	  		y:			  contentOffset.top - viewportOffset.top

	  	});

	  	jQuery.extend(layer[i].content[n], { 

	  	  posxrat:  (layer[i].content[n].x + layer[i].content[n].width/2) / layer[i].width,

	  	  posyrat:  (layer[i].content[n].y + layer[i].content[n].height/2) / layer[i].height

      });

	  }

  }

}



function moveLayers(layer, xratio, yratio) {



	var xpos;

	var ypos;

	var cssObject;

	

	for (var i=0; i<layer.length; i++) {

    

    // Calculate the moving factor

  	xpos = layer[i].xtravel * xratio + layer[i].xoffset;

    ypos = layer[i].ytravel * yratio + layer[i].yoffset;

    cssObject = {};

  	// Do the moving by pixels or by ratio depending on travelpx

    if (layer[i].xparallax) {

      if (layer[i].xtravelpx) {

        cssObject.marginLeft = xpos * -1 + 'px';

      } 

      else {

        cssObject.left = xpos * 100 + '%';

        cssObject.marginLeft = xpos * layer[i].width *-1 + 'px';

      }

	  }

	  if (layer[i].yparallax) {

      if (layer[i].ytravelpx) {

        cssObject.marginTop = ypos * -1 + 'px';

      }

      else {

        cssObject.top = ypos * 100 + '%';

        cssObject.marginTop = ypos * layer[i].height * -1 + 'px';

      }

    }

    layer[i].element.css(cssObject);

	}

}



// PLUGIN DEFINITION **********************************************************************



jQuery.fn.jparallax = function(options) {

	

	// Organise settings into objects (Is this a bit of a mess, or is it efficient?)

	var settings = jQuery().extend({}, jQuery.fn.jparallax.settings, options);

	var settingsLayer = {

  			xparallax:				settings.xparallax,

  			yparallax:				settings.yparallax,

  			xorigin:					settings.xorigin,

  			yorigin:					settings.yorigin,

  			xtravel:          settings.xtravel,

  			ytravel:          settings.ytravel

  		};

  var settingsMouseport = {

  			element:					settings.mouseport,

				takeoverFactor:		settings.takeoverFactor,

				takeoverThresh:		settings.takeoverThresh

			};

	if (settings.mouseport) settingsMouseport['element'] = settings.mouseport;

	

	// Populate layer array with default settings

	var layersettings = [];

	for(var a=1; a<arguments.length; a++) {

		layersettings.push( jQuery.extend( {}, settingsLayer, arguments[a]) );

	}

	

	// Iterate matched elements

	return this.each(function() {



    // VAR

    

		var localmouse = {

					x:				0.5,

					y:				0.5

		};

		

    var virtualmouse = {

					x:				0.5,

					y:				0.5

		};

		

		var timer = {

		  running:		false,

		  frame:			settings.frameDuration,

		  fire:				function(x, y) {

		  	  				  positionMouse(mouseport, localmouse, virtualmouse);

                    moveLayers(layer, virtualmouse.x, virtualmouse.y);

		  	  				  this.running = setTimeout(function() {

		  	  				  	if ( localmouse.x!=x || localmouse.y!=y || !mouseport.ontarget ) {

		  	  				  		timer.fire(localmouse.x, localmouse.y);

		  	  				  	}

		  	  				  	else if (timer.running) {

		  	  				  		timer.running=false;

		  	  				  	}

		  	  				  }, timer.frame);

		  	  				}

		};



		var viewport	=	{element: jQuery(this)};		



		var mouseport = jQuery.extend({}, {element: viewport.element}, settingsMouseport, {

		  xinside:          false,		// is the mouse inside the mouseport's dimensions?

			yinside:	        false,

			active:		        false,		// are the mouse coordinates still being read?

			ontarget:         false			// is the top layer inside the takeoverThresh?

		});

    

		var layer			= [];

    

    // FUNCTIONS

    

    function matrixSearch(layer, ref, callback) {

      for (var i=0; i<layer.length; i++) {

        var gotcha=false;

        for (var n=0; n<layer[i].content.length; n++) {

          if (layer[i].content[n].anchor==ref) {

            callback(i, n);

            return [i, n];

          }

        }

      }

      return false;

    }

    

    // RUN

    

    setupPorts(viewport, mouseport);

		

		// Cycle through and create layers

    for (var i=0; i<viewport.element.children().length; i++) {

			// Create layer from settings if it doesn't exist

			layer[i]=jQuery.extend({}, settingsLayer, layersettings[i], {

				element:	viewport.element.children('*:eq('+i+')')

			});

			

		  setupLayer(layer, i, mouseport);

      

      if (settings.triggerResponse) {

		    setupLayerContents(layer, i, viewport.element.offset());

		  }

		}

		

		

    

    // Set up layers CSS and initial position

    viewport.element.children().css('position', 'absolute');

		moveLayers(layer, 0.5, 0.5);

		

		// Mouse Response

		if (settings.mouseResponse) {

			jQuery().mousemove(function(mouse){

				// Is mouse inside?

				mouseport.xinside = (mouse.pageX >= mouseport.left && mouse.pageX < mouseport.width+mouseport.left) ? true : false;

				mouseport.yinside = (mouse.pageY >= mouseport.top  && mouse.pageY < mouseport.height+mouseport.top)  ? true : false;

				// Then switch active on.

				if (mouseport.xinside && mouseport.yinside && !mouseport.active) {

					mouseport.ontarget = false;

					mouseport.active = true;

				}

				// If active is on give localmouse coordinates

				if (mouseport.active) {

					if (mouseport.xinside) { localmouse.x = (mouse.pageX - mouseport.left) / mouseport.width; }

					else { localmouse.x = (mouse.pageX < mouseport.left) ? 0 : 1; }

					if (mouseport.yinside) { localmouse.y = (mouse.pageY - mouseport.top) / mouseport.height; } 

					else { localmouse.y = (mouse.pageY < mouseport.top) ? 0 : 1; }

				}

				

				// If mouse is inside, fire timer

				if (mouseport.xinside && mouseport.yinside)  { if (!timer.running) timer.fire(localmouse.x, localmouse.y); }

				else if (mouseport.active) { mouseport.active = false; }			

			});

		}

		

		// Trigger Response

		if (settings.triggerResponse) {

		  viewport.element.bind("jparallax", function(event, ref){

		    

		    ref = stripFiletype(ref);

		          

        matrixSearch(layer, ref, function(i, n) {

          localmouse.x = layer[i].content[n].posxrat * (layer[i].diffxrat + 1) - (0.5 * layer[i].diffxrat);

          localmouse.y = layer[i].content[n].posyrat * (layer[i].diffyrat + 1) - (0.5 * layer[i].diffyrat);

  

          if (!settings.triggerExposesEdges) {

            if (localmouse.x < 0) localmouse.x = 0;

            if (localmouse.x > 1) localmouse.x = 1;

            if (localmouse.y < 0) localmouse.y = 0;

            if (localmouse.y > 1) localmouse.y = 1;

          }

          

          mouseport.ontarget = false;

          

          if (!timer.running) timer.fire(localmouse.x, localmouse.y);

        });

		  });

		}

		

		// Window Resize Response

		jQuery(window).resize(function() {



		  setupPorts(viewport, mouseport);

		  for (var i=0; i<layer.length; i++) {

		    setupLayer(layer, i, mouseport);

      }

		});

		

		

	});

};



// END OF PLUGIN DEFINITION **********************************************************************



// PLUGIN DEFAULTS



jQuery.fn.jparallax.settings = {

	mouseResponse:		    true,						// Sets mouse response

	mouseActiveOutside:		false,					// Makes mouse affect layers from outside of the mouseport. 

	triggerResponse:	    true,					  // Sets trigger response

  triggerExposesEdges:  false,          // Sets whether the trigger pulls layer edges into view in trying to centre layer content.

	xparallax:				    true,						// Sets directions to move in

	yparallax:				    true,						//

	xorigin:					    0.5,				    // Sets default alignment - only comes into play when travel is not 1

	yorigin:					    0.5,				    //

	xtravel:              1,              // Factor by which travel is amplified

	ytravel:              1,              //

	takeoverFactor:		    0.65,						// Sets rate of decay curve for catching up with target mouse position

	takeoverThresh:		    0.002,					// Sets the distance within which virtualmouse is considered to be on target, as a multiple of mouseport width.

	frameDuration:        25							// In milliseconds

};



// RUN



initOrigin(jQuery.fn.jparallax.settings);



jQuery(function() {

	

});





// END CLOSURE



})(jQuery);
