Sunday, March 20, 2011

Richard III

When I was first learning jQuery, what immediately excited me was the ease of creating cool animations. Anyone who has tried to animate in JavaScript using setInterval can appreciate the elegance of $.animate(). One plugin that really caught my eye was a paralax plugin. It handled image layers: stacking them on top of each other and shifted them horizontally and vertically to simulate depth in a scene. With the right scene the effect is pretty cool.

Art
I'd like to thank my friend  Nolan Tredway for letting me use some of his work for this demo. The graphics in this scene come from a piece that depicts the nursery rhyme 'Humpty Dumpty' and its roots in the story of King Richard III of England and the Battle of Bosworth Field.


HTML/CSS
The html and css is pretty simple. Each layer of the scene is a png image with transparency. A relatively positioned div wraps the list of layer images from back to front in the scene. Each <img />  tag has a rel value corresponding to the z value read in by the plugin.

JavaScript
Once the images a lined up on top of each other, a JavaScript plugin tracks the mouse cursor and adjusts the position of the layers accordingly.
To do the plugin must:
  • keep track of mouse position
  • keep track of window size
  • update layer position on interval

Keep track of mouse position and window size
With jQuery tracking the mouse position is trivial.
var methods = {
     init : function(options){
          ...
          //listen for mouse move
          $(document).mousemove(methods.onMouseMove);
          ...
     },
     onMouseMove : function(event){
               mouseX = event.clientX;
               mouseY = event.clientY;
     }
}
...
var mouseX, mouseY;
As is tracking the mouse position.
var methods = {
     init : function(options){
          ...
          //get and keep window size
          $window.resize(methods.onWindowResize).resize();
          ...
     },
     onWindowResize : function(event){onWindowResize : function(event){
               wHeight = $window.height();
               wWidth = $window.width();
      }
}
...
var wHeight, wWidth;

}

Updating layer position
The meat of the plugin is responsible for shifting each layer based on its z index and mouse position.
var methods = {

var settings = {
        magnitudeX: 100, //control baseline of x movement
        magnitudeY: 50,  //control baseline of y movement
        paralaxFactor : 10 //sets baseline zindex for paralax movement
    }, $frames, originalPosition; //list of frames and original position

var methods = {
      init : function(options) {          
         if(options){
              $.extend(settings, options);
         }
            
         settings = $.extend(settings, options);
          ...
          $t = $(this);
          $frames = $t.children(".frame");
          $frames.each(function(){
               //store paralax index as float
               $.data(this, "paralaxLayer", parseFloat($(this).attr('rel')));
          });
          originalPosition = $frames.position();
          ...
          //reposition on interval rather than mouse move
          setInterval(methods.calculatePosition, 100);
          ...
     },
     calculatePosition : function(){
          //calculate baseline offset for this mouse position
          var offsetX = (settings.magnitudeX)*(mouseX/wWidth);
                      - (settings.magnitudeX/2),
          offsetY = (settings.magnitudeY)*(mouseY/wHeight);
                      - (settings.magnitudeY/2),
          frame, paralaxLayer, paralaxFactorX, paralaxFactorY;
          
          //go through and reposition each frame
          for(i=0; i<$frames.length; i++){
              $frame = $($frames[i]);
              paralaxLayer = $.data($frames[i], "paralaxLayer")/
              settings.paralaxFactor;
              paralaxFactorX = -paralaxLayer*offsetX,
              paralaxFactorY = -paralaxLayer*offsetY;
                   
              //use animate for smooth transition
              $frame.clearQueue().animate({
                  top: originalPosition.top+paralaxFactorY,
                  left: originalPosition.left+paralaxFactorX,
              }, 300);
          }
     }
}

There were a couple of steps I took to increase the performance of the position calculation. In the init function the $.each() function is used to loop through frames for ease of implementation, but the calculatePosition function needs to run a bit faster so I used a for loop. Secondly, I decided to calculate the new position of the frames on an interval, rather than on mouse move. Mouse move occurs too often to perform any significant calculation and translation, so a interval and the jQuery animate plugin was utilized.


Possible Improvements
  • load layer data into object or array instead of using $.data plugin
  • integrate text shadow into plugin

No comments:

Post a Comment