﻿// 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);
