Categories

jQuery wrapper for iScroll

Matteo Spinelli did a terrific job writing a Javascript library that allows scrolling the content of a DIV element on iPhone and Android web browsers. The library is well documented in his site http://cubiq.org/iscroll. I don’t think that it is a must-have but I find very comfortable to implement functionality in a consistent way. Since I am a big jQuery fan I decided to wrap Matteo’s library in a very simple jQuery plugin.

?View Code JAVASCRIPT
(function($){
    $.fn.iscroll = function(options){
		if(this.data('iScrollReady') == null){
			var that = this;
            var options =  $.extend({}, options);
				options.onScrollEnd = function(){
					that.triggerHandler('onScrollEnd', [this]);
				};
			arguments.callee.object  = new iScroll(this.get(0), options);
			// NOTE: for some reason in a complex page the plugin does not register
			// the size of the element. This will fix that in the meantime.
			setTimeout(function(scroller){
				scroller.refresh();
			}, 1000, arguments.callee.object);
			this.data('iScrollReady', true);
		}else{
			arguments.callee.object.refresh();
		}
		return arguments.callee.object;
	};
})(jQuery);

Basically you can use jQuery to select the content that you want to scroll and call ‘.iscroll()’ to add the behavior. If you need to overwrite the default parameters you pass an object with values that you wish to change. Here is an example of how to implement this:

?View Code JAVASCRIPT
$(function(){
	var elem = $('#content');
		elem.iscroll();
		elem.bind('onScrollEnd', function(e, iscroll){
			alert($(this).attr('id') +' - '+ iscroll);
		});
});

Downloads

When you call .iscroll() the library will find the parent element and make the content scrollable. This method will also return an instance of the iScroll class. If the method is called again it will refresh the content. This is convenient in case you modify the content dynamically (iScroll allows to detech DOM changes automatically).

The library dispatches an event name onScrollEnd when the scroll action is completed. In touch screen devices this library adds momentum. This means that the content will continue moving for a short time after the touchend event. By default the version 3.7 accepts a callback function that is trigger at the end of the animation. The wrapper overwrites the parameter and triggers a jQuery event instead. This approach will allow multiple callback functions.

Implementing doubletap on iPhones and iPads

When the iPhone first arrived, one of the coolest things that Apple did was to have Mobile Safari display a miniature view of an entire web site, allowing users to double tap and zoom into the portions of the site that they wanted to see. This clever solution to navigate large pages in a small screen came with a price.

If your site takes advantage of the double click functionality, when it is displayed on Mobile Safari you loose that capability, your clicks become touch events and the double click (double-tap) now belongs to the browser.

If you want to get the double click functionality back, the first thing you should know is that you can prevent the default browser behavior that takes your double click away. Once you have done that, you can easily re-implement the double click/touch behavior you lost. In your HTML page start with adding a meta tag to indicate the users are not allow to scale the page:

var isiOS = false;
var agent = navigator.userAgent.toLowerCase();
if(agent.indexOf('iphone') >= 0 || agent.indexOf('ipad') >= 0){
       isiOS = true;
}

Now you should be able to write code for iOS devices and for regular browsers

?View Code JAVASCRIPT
if(isiOS){
       // implement double-tap
}else{
       // implement double click
}

Adding double double tap is bit trickier. We need to measure the time between two consecutive touch events. If a user taps twice within 500 milliseconds (half a second), I can assume that the user meant double tap. Now keep in mind that two click/touch events are also fired. The trick is that we have to wait before we can fire a click/touch event and call our doubletap handler only if users click/tap very quickly.

Let’s go one step at the time. I will use jQuery to bind a touchend event to an element selector:

?View Code JAVASCRIPT
$(selector).bind('touchend', function(event){
       var now = new Date().getTime();
       var lastTouch = $(this).data('lastTouch') || now + 1 /** the first time this will make delta a negative number */;
       var delta = now - lastTouch;
       if(delta <500 && delta>0){
               // the second touchend event happened within half a second. Here is where we invoke the double tap code
       }else{
               // A click/touch action could be invoked here but wee need to wait half a second before doing so.
       }
       $(this).data('lastTouch', now);
}

What I have done so far, is to determine the difference between the last touch and the current touch events. The first time that this code runs, both the current touch time and the last touch time are the same, so the delta is zero. At the end of the function I want to make sure that I update the value of the last touch to be equal to the current touch.

Now that we know where to write the double tap code, let’s figure out how to add some logic to help us know when to invoke the double tap code:

?View Code JAVASCRIPT
var action;
$(selector).bind('touchend', function(event){
       var now = new Date().getTime();
       var lastTouch = $(this).data('lastTouch') || now + 1 /** the first time this will make delta a negative number */;
       var delta = now - lastTouch;
       clearTimeout(action);
       if(delta<500 && delta>0){
               // the second touchend event happened within half a second. Here is where we invoke the double tap code
       }else{
               $(this).data('lastTouch', now);
               action = setTimeout(function(e){
                       // If this runs you can invoke your 'click/touchend' code
                       clearTimeout(action);   // clear the timeout
               }, 500, [event]);
       }
       $(this).data('lastTouch', now);
}

To determine how long we have to wait before we can tell that the user only tapped once, I added a timeout call declared outside of the touchend event handler. Every time that the touchend event is fired, I clear the action timeout which prevents the click/touch from happening. If the value of delta is less than 500 milliseconds (half a second) we can definitely invoke the doubletap code, but if delta is higher that 500 milliseconds the timeout is defined again and our click/touch code will be invoked in half a second.

Let’s put everything together and write it as a jQuery extension:

?View Code JAVASCRIPT
/*!
 * jQuery Double Tap Plugin.
 *
 * Copyright (c) 2010 Raul Sanchez (http://www.sanraul.com)
 *
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 */
 
(function($){
	// Determine if we on iPhone or iPad
	var isiOS = false;
	var agent = navigator.userAgent.toLowerCase();
	if(agent.indexOf('iphone') >= 0 || agent.indexOf('ipad') >= 0){
	       isiOS = true;
	}
 
	$.fn.doubletap = function(onDoubleTapCallback, onTapCallback, delay){
		var eventName, action;
		delay = delay == null? 500 : delay;
		eventName = isiOS == true? 'touchend' : 'click';
 
		$(this).bind(eventName, function(event){
			var now = new Date().getTime();
			var lastTouch = $(this).data('lastTouch') || now + 1 /** the first time this will make delta a negative number */;
			var delta = now - lastTouch;
			clearTimeout(action);
			if(delta0){
				if(onDoubleTapCallback != null && typeof onDoubleTapCallback == 'function'){
					onDoubleTapCallback(event);
				}
			}else{
				$(this).data('lastTouch', now);
				action = setTimeout(function(evt){
					if(onTapCallback != null && typeof onTapCallback == 'function'){
						onTapCallback(evt);
					}
					clearTimeout(action);   // clear the timeout
				}, delay, [event]);
			}
			$(this).data('lastTouch', now);
		});
	};
})(jQuery);

Usage:

?View Code JAVASCRIPT
$(selector).doubletap(
    /** doubletap-dblclick callback */
    function(event){
        alert('double-tap');
    },
    /** touch-click callback (touch) */
    function(event){
        alert('single-tap');
    },
    /** doubletap-dblclick delay (default is 500 ms) */
    400
);

This plugin will work on Desktop Browser as well as Mobile Safari. Click HERE to download the source code and basic examples. You can also visit the DEMO page.