Tuesday, April 3, 2012

Add Gesture Support to Your Web Application via Hammer.js

Add Gesture Support to Your Web Application via Hammer.js:
As anyone who has viewed their web application on a mobile device, or developed one specifically for mobile, knows, by default your site does not understand gestures. Sure, tapping on a link acts the same as clicking with a mouse, but even that limited default gesture support doesn’t always behave the way we’d hoped. Thankfully, if you would like to add more advanced gesture support to your web application, there are JavaScript libraries out there to support this. In this post, we’ll take a look at one of them, Hammer.js, that was released recently and allows you to easily add support for tap, double-tap, hold, drag and transform gestures.

In order to take a look at this library, I whipped up a quick demo. Since my design skills are only slightly better than Ray Camden’s (i.e. they are bad ;-) ), I decided to rely on a nice demo application created by Val Head in her recent article for .net Magazine. In this demo, she was showing how to use jQuery events to trigger CSS transitions. My demo will do the same but rely on touch events via Hammer.js instead of click events in jQuery.

Using Hammer.js is pretty simple. While the documentation at the moment seems pretty limited, it does include some good examples that illustrate various uses as well as the source itself is very easy to read and discern what you need to know. Essentially, you instantiate Hammer on a DOM object and add a callback function for whatever gesture events you want to support.

You can use Hammer.js independently or you can use the jQuery plugin. In the case of my demo, I am using the jQuery plugin as it simplifies quickly adding gesture events to multiple DOM elements. Let’s take a look at the code:

<!DOCTYPE html>
<html lang="en">
<head>
   <title>CSS transitions triggered with Hammer.js demo</title>
   <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
   <script src="https://raw.github.com/EightMedia/hammer.js/master/hammer.js"></script>
   <script src="https://raw.github.com/EightMedia/hammer.js/master/jquery.hammer.js"></script>

<style>
   /* a few initial styles for our containing elements */
   body {background:#282828 url(img/texture.jpg);}
   #wrap {width:300px; height:300px; position:relative; overflow:hidden; margin:3em auto;}
   #blocks {width:300px; height:300px; position:absolute; top:0; left:0; overflow:hidden;}
  
   /* our "mask" image positions over top of the animated blocks */
   .mask {position:absolute; top:118px; left:0; z-index:500; background-image: url("img/abcmask.png"); width: 269px; height: 160px;}
  
   /*shared styles and animation definitions for all of our animated blocks*/
   .col {
      width:72px;
      height:144px;
      position:absolute;
      top:10px;
      -webkit-transition: all 225ms cubic-bezier(0.545, 0.165, 0.835, 0.425);
       -moz-transition: all 225ms cubic-bezier(0.545, 0.165, 0.835, 0.425);
       -ms-transition: all 225ms cubic-bezier(0.545, 0.165, 0.835, 0.425);
       -o-transition: all 225ms cubic-bezier(0.545, 0.165, 0.835, 0.425);
       transition: all 225ms cubic-bezier(0.545, 0.165, 0.835, 0.425); /* custom */
   }
  
   /* specific images and top positions for each of our blocks */
   .col1 {background: url(img/aslider.png); top:22px; left:26px;}
   .col2 {background:url(img/bslider.png); top:4px; left: 100px;}
   .col3 {background:url(img/cslider.png); top:30px; left:174px;}

   /* the class we'll toggle with jQuery to trigger our transitions */
   .col-anim {top:96px;}

</style>

<script>

// assign toggle the col-anim class for each of our columns when they are clicked

$(function() {  
   // bind hammer to our columns tp listen for down drag
   $(".col").hammer({prevent_default:true}).bind("drag", function(ev) {
      if (ev.direction == "down") {
         $(this).addClass("col-anim");
      }
   });

   // once the columns are down, the drag event is triggered on the mask
   $(".mask").hammer({prevent_default:true}).bind("drag", function(ev) {
      // check the position in the mask to determine which item the user attempted to drag
      if (ev.direction == "up") {
         if (ev.position.x > 26 && ev.position.x < 98) {
            $(".col1").removeClass("col-anim");
         }
         else if (ev.position.x > 100 && ev.position.x < 172) {
            $(".col2").removeClass("col-anim");
         }
         else if (ev.position.x > 174 && ev.position.x < 246) {
            $(".col3").removeClass("col-anim");
         }
      }
   });
});

</script>

</head>

<body>
   <div id="wrap">
      <div class="mask"></div>
      <div id="blocks">
         <div class="col col1" ></div>
         <div class="col col2"></div>
         <div class="col col3"></div>
      </div>
   </div>
</body>

</html>


For the most part I have left Val’s HTML and CSS intact. The only exception being that I changed the mask that was an image to be a div with an image background. The reason for this is in my desktop browser testing on Chrome, the browser actually supported dragging images by default (say, to the menu bar or desktop). Using a div with a background image prevented this unwanted behavior. Nonetheless, it also brings up a good point, which is that Hammer.js does a pretty good job of translating these gestures to your desktop mouse, allowing you to do a good deal of testing without a touch-enabled device. In fact, you can run this demo on your laptop or device to see what I mean.

Taking a closer look at the JavaScript code, you can see that within the jQuery load function I am first adding a listener for the drag function to all of the columns. I am supplying a configuration parameter, prevent_default, as this "seemed" to improve the reliability of capturing the drag event on an actual device (plus the source comments indicate behavior "might be buggy" when precent_default is left to false, which is the default value). There are a number of other configuration options for various events which you can easily find in the source. The callback function I define simply relies on the event passed back by Hammer.js to determine if you dragged the element down and, if so, it adds the style to the column triggering the CSS transition effect.

Capturing the drag up though was slightly more complicated as the column elements were now behind the image mask. This meant that my touch events would not be triggered on the columns. Therefore, I ended up binding a drag event to the entire mask looking for up drags. Since each drag event in Hammer.js provides me a position object indicating the x and y coordinates of the touch event, I relied on this, plus the fixed position of the column elements, to determine which column you are dragging up.

To be clear, I am only adding and removing CSS classes to trigger a CSS transition up and down. You could make a more advanced version of this demo by using Hammer.js to capture the drag start and end events as well as the intermediate drag coordinates to allow more direct control of the target object placement, as they do in their drag demo. Still, I hope that this illustrated how easy it is to use this library to touch-enable your web applications.

No comments:

Post a Comment

Thank's!