Add Gesture Support to Your Web Application via Hammer.js

Posted on Apr 03, 2012

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, which was created by the team at Eight Media. Hammer.js 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="hammer.js"></script>
   <script src="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.

Comments

William from Lagos Demo link is broken

Posted By William from Lagos / Posted on 04/15/2012 at 7:00 AM


Brian Rinaldi Thanks. They moved it to http://eightmedia.github.com/hammer.js/drag/

Posted By Brian Rinaldi / Posted on 04/15/2012 at 4:03 PM


Greg The drag demo does not work properly on a MacBook w/ Safari 5.1. Seems that the drag event 'jumps' target elements as one is dragged over another.

I am looking at hammer as a cross-mobile and cross-browser solution. But, I am not so sure that it's up to the job. Any thoughts?

Thanks, Greg.

Posted By Greg / Posted on 05/18/2012 at 5:41 PM


Brian Rinaldi Interesting. Have you filed a bug on the GitHub repo?

Posted By Brian Rinaldi / Posted on 05/21/2012 at 5:37 AM


Greg Nope. That's about it.
Simply switches target when dragging one object over another.

Greg

Posted By Greg / Posted on 05/21/2012 at 7:48 AM


Christopher Hethrington Hi Brian, sorry if this is a bit of a noob problem.
I modified the above a bit to fadeIn when you drag on the iPad (substituting what are otherwise rollovers on a computer). This worked fine but when I try to execute a click() it doesn't work.

if (ev.direction == 'up') {
   //$(this).children('a').children('span').fadeOut(500);
   $(this).children('a').click();
}
Commented out line works fine but not .click(); nor trigger.('click')

Here's a link to the stackoverflow question in greater detail if you have a chance. http://bit.ly/NqL9ql
It's asking a lot so you can totally ignore this if your busy and thanks for the great tutorial, it's helped a lot so far.

Posted By Christopher Hethrington / Posted on 07/05/2012 at 6:10 PM


Write your comment



(it will not be displayed)







About

My name is Brian Rinaldi and I am the Web Community Manager for Flash Platform at Adobe. I am a regular blogger, speaker and author. I also founded RIA Unleashed conference in Boston. The views expressed on this site are my own & not those of my employer.