Blog of Appliness

Multitouch with Hammer.js

hammer-splash

This tutorial is featured in the free June issue of Appliness.

Download it on your tablet (iPad or Android).

Every month, you’ll find free technical tutorials in Appliness about web application developement. If you have to suggest ideas, if you want to contribute writing articles, feel free to contact us using the feedback form !

Hammer.js is a very simple but very powerful library to enable multitouch experiences
on touch devices.

Why using multitouch?

As a mobile application developer, I’m always dealing with the frontier between native apps and web apps. Tablet and mobile users are used to multitouch gestures: pinch to zoom-in, rotation, natural drag and drop… Now, these gestures are not reserved to native apps. Thanks to Hammer.js, you can easily implement touch events in your web apps: tap, double tap, hold, drag and transform gestures. It became  natural reflexes. Display a picture on a tablet and notice that the first thing that will do the users is to zoom-in.

To introduce this library, I’ve extended some Hammer samples found on GitHub. I’ve built a classic polaroid manipulation app. I’ll share with you some tips I’ve used, but of course you’re invited to download the full code of this sample and start playing with Hammer.js.

In my sample I’m using the Drag gesture, pinch to rescale the pictures and the rotation gesture using two fingers. By the way, do you know why the library is called Hammer.js? It’s a tribute to MC Hammer, the singer of U Can’t Touch This. Well guess what, now we CAN. Let me add a YO! here. Cool, now I think it’s time to start a little gangsta.

My sample application

I’m just playing with three pictures. You can drag, rescale and rotate them. The Polaroid look and feel is produced by a CSS. This is pure HTML and JavaScript powered by Hammer.js:

You can check the live sample here: http://riagora.com/mobile/hammer/

Of course, it’s better to test this sample on a touch device such as the iPad. If you want to see the sample in video:

Getting started

<head>
    <meta name=”viewport” content=”width=device-width, initial-scale=1.0, user-scalable=no”>
    <script src=”jquery-1.7.2.min.js”></script>
<script src=”hammer.js”></script>
<script src=”jquery.hammer.js”></script>
<script src=”myLogic.js”></script>
    <link href=’http://fonts.googleapis.com/css?family=Bree+Serif’ rel=’stylesheet’ type=’text/css’>
    <link href=’style.css’ rel=’stylesheet’ type=’text/css’>
</head>

Then I’ve declared three DIV to display the pictures. The pictures are contained in a “zoomwrapper” DIV element.

<body>
<div class=”welcome”>
	<p>This sample is using Hammer.js.<br/>
Drag and drop the pictures, zoom with pinch and rotate them.</p>
</div>
<div id=”zoomwrapper”>
    <div id=”zoom” class=”zoomProps” >
        <div class=”polaroid”>
            <img src=”pic1.jpg” alt=”” width=”200” height=”200” />
            <span>Coco</span>
        </div>
    </div>
    <div id=”zoom2”class=”zoomProps” >
        <div class=”polaroid”>
            <img src=”pic2.jpg” alt=”” width=”200” height=”200” />
            <span>Axo</span>
        </div>
    </div>
    <div id=”zoom3” class=”zoomProps”>
        <div class=”polaroid”>
            <img src=”pic3.jpg” alt=”” width=”200” height=”200” />
            <span>Gwo</span>
        </div>
    </div>
</div>
</body>

There is nothing particular to notice in the CSS. The zoomwrapper DIV has an overflow property set to hidden. And here is the code to imitate the “Polaroid” effect:

#zoomwrapper {
      height: 377px;
      width: 600px;
      overflow: hidden;
 }
 
.polaroid{
		text-align: center;
		padding: 10px 10px 25px 10px;
		background: #eee;
		border: 1px solid #fff;
		-moz-box-shadow: 0px 2px 15px #333;
		-webkit-box-shadow:	0px 2px 15px #333;
		border: 1px solid #dfdfdf;
    	border: 1px solid rgba(96,96,96,0.2);
		font-family:”Imitation”;
		font-size:24px;
		color:#000;
		padding-bottom:10px;
		text-shadow:#333;
}

Drag pictures

The DragView function contains the logic to handle drag events. For each picture, you must associate a DragView call and bind the drag events:

var dragview = new DragView($(container));
container.bind(“dragstart”, $.proxy(dragview.OnDragStart, dragview));
container.bind(“drag”, $.proxy(dragview.OnDrag, dragview));
container.bind(“dragend”, $.proxy(dragview.OnDragEnd, dragview));
setInterval($.proxy(dragview.WatchDrag, dragview), 10);

Let’s look at the OnDragStart method declared in DragView.

this.OnDragStart = function(event) {
	var touches = event.originalEvent.touches || [event.originalEvent];
	for(var t=0; t<touches.length; t++) {
		var el = touches[t].target.parentNode;
		if(el.className.search(‘polaroid’) > -1){
			el = touches[t].target.parentNode.parentNode;
		}
		el.style.zIndex = zIndexBackup + 1;
		zIndexBackup = zIndexBackup +1;
 
		if(el && el == this.target) {
			$(el).children().toggleClass(‘upSky’);
			this.lastDrag = {
					el: el,
					pos: event.touches[t]
			};
		return;
		}
	}
}

It stores the touch events in an array and make sure that we’re dealing with the DIV element with a “polaroid” class attached. If not, then we need to play with its parent node. It’s due to the structure of my HTML elements. If you tap the title of the picture, then you also want the picture to move. I’m also playing with the zIndex property to put the picture at the top when you drag it. Then, thanks to the toggleClass method (jQuery), I assign the ‘upSky’ class to the element: it’s just setting an intense white background.

zoom-in and zoom-out

To initialize the pinch gesture, I’m calling the ZoomView method on each picture. Then you can listen to “transformstart”, “transform” and “transformend” events on your elements.

The transformstart event lets you define what is the cssorigin point of transformation.

container.bind(“transformstart”, function(event){
 
//We save the initial midpoint of the first two touches
            e = event
tch1 = [e.touches[0].x, e.touches[0].y],
            tch2 = [e.touches[1].x, e.touches[1].y]
 
            tcX = (tch1[0]+tch2[0])/2,
            tcY = (tch1[1]+tch2[1])/2
 
            toX = tcX
            toY = tcY
 
            var left = $(element).offset().left;
            var top = $(element).offset().top;
 
						cssOrigin = (-(left) + toX)/scaleFactor +”px “+ (-(top) + toY)/
scaleFactor +”px”;
        })

The “transform” state fires the transform method, passing the transform event and setting the scaleFactor. You can specify the minimum and maximum zoom thanks to the MIN_ZOOM and MAX_ZOOM constants.

container.bind(“transform”, function(event) {
            scaleFactor = previousScaleFactor * event.scale;
 
            scaleFactor = Math.max(MIN_ZOOM, Math.min(scaleFactor,
MAX_ZOOM));
            transform(event);
        });

The transform() method uses simple CSS transformations that are hardware accelerated on mobile devices to get the best performance on touch devices. As the transform event powered by Hammer also contains information about the angle of rotation of your element, you can use the rotateZ property to enable rotation.

function transform(e) {
            //We’re going to scale the X and Y coordinates by the same amount
            var cssScale = “scaleX(+ scaleFactor +) scaleY(+ scaleFactor
 +) rotateZ(+ e.rotation +”deg);
 
            element.css({
                webkitTransform: cssScale,
                webkitTransformOrigin: cssOrigin,
 
                transform: cssScale,
                transformOrigin: cssOrigin,
            });
 
        }

That’s very efficient and easy to implement. You can download the source code of this project here:

riagora.com/mobile/hammer/hammer.zip


 

Comments

comments

Powered by Facebook Comments

11 Comments

  1. Patrick
    July 4, 2012

    Awesome tutorial! Thanks a million for building this out. I’ve learned a ton already.

    Reply
  2. peavin
    July 13, 2012

    Hi,

    Thanks a lot for your tutorial .

    But there is a problem with android tab and android phone with Zoom in and Zoom out is not working at all.

    can u plz help me What to do?

    Reply
    • Michaël Chaize
      July 16, 2012

      I’m surprised as it works on my Android devices. Which version of Android are you running ? Thx

      Reply
  3. Artem Marchenko
    July 16, 2012

    Works very well on iPhone upgraded to iOS 5, on Galaxy S upgraded to Froyo only panning works (and does it very well!), attempt to pinch-zoom results in zooming the page by the browser or nothing (when trying stand-alone example at http://riagora.com/mobile/hammer/ ). Maybe some event tracking gets messed up on old Androids.

    That said I am personally more interested in sooth panning and double tap zooming. Smooth panning is just perfect with hammer, did you think about zooming in-out on double tap? :)

    Reply
  4. Raja Krishna
    July 19, 2012

    Hi,

    May I please know the number of touches(fingers) possible at any point of time ??

    Thanks
    Raja

    Reply
  5. Kendall
    September 13, 2012

    Very nice tutorial.

    I was wondering however If you had to drag a “copy” of the photos lets say from left side of the screen to a target area to the right side of the screen…. do i implement the hammer on a copy of the element or do use the exact same element?

    Reply
  6. Kendall
    September 13, 2012

    Also this doesn’t update as fast enough in android 2.3

    Is there anyway to fix this?

    Reply
  7. Monica
    January 16, 2013

    Once i download the example code…. i build with phone gap to make the app native???
    thanks

    Reply
  8. klaus
    February 8, 2013

    nice tutorial, thank you!

    i found also a buggy behaviour:

    the zoom functionality also take place if you touch 2 different elements which results in funny circle rotations ;-)
    i guess you have to proof if booth touches are on the same container element.

    Reply
  9. PraveenGodfrey
    February 14, 2013

    Does using the hammer.js affect the scrolling option in my page ?? In my case it has locked the scrolling feature which was working well before implementing hammer.js into my code !! I needed only the zoom feature and not the drag feature..so i just commented out the drag function in “mylogic.js”..also i have applied the zoom not to an image but for the that contains my page….
    Any idea on this ??

    Reply
  10. Pjero
    March 2, 2013

    You have a lot of unnecessary stuff in your code. The rotation doesn’t work properly, if you touch a rotated object with 2 fingers again it returns leveled.

    Also a new version of Hammer.js, has been released with lots of changes.

    Reply

Leave a Reply