Wednesday, December 07, 2011

Double-tap on Mobile Safari

I've been working on a client-side map (think Google Maps) that has to work in IE7 and other browsers. It also has to work on mobile Safari, specifically the iPad.

This map is zoom-able to 4 levels deep and contains pins for specific locations. Tapping/clicking on these pins will either bring up a list of options (if pins overlap) or go directly to another page. As you can tell, I'm also using jQuery.

Here's what I came up with:


$('#map').bind('touchstart', function(e) {
  if ($(e.target).hasClass('disclosure') || $(e.target).parent().hasClass('disclosure') || (e.target.src && e.target.src.toLowerCase().indexOf('disclosure') > 0)) {
    map.jumpToPage(e);
      return false;
  }
  $('#flyover').hide();
  var oe = e.originalEvent;
  if (oe.touches.length == 1) {
    var tap = { at: new Date().getTime(), x: oe.touches[0].pageX, y: oe.touches[0].pageY, shortDelay: $(e.target).hasClass('pin') };
    if (!$('#map').data('firstTap') || ((tap.at - $('#map').data('firstTap').at) > 500)) {
      $('#map').data('firstTap', tap);
    } else {
      var firstTap = $('#map').data('firstTap');
      if ((tap.at - firstTap.at) <= (tap.shortDelay ? 300 : 500)) {
        $('#map').data('lastTap', tap);
      }
    }
  }
  e.preventDefault();
}).bind('touchend', function(e) {
  var f = $('#map').data('firstTap');
  var l = $('#map').data('lastTap');
  setTimeout(function() {
    var f1 = $('#map').data('firstTap');
    var l1 = $('#map').data('lastTap');
    if (f1 && !l1 || f1 && l1 && (f1.at > l1.at)) {
      map.showPopUp({ pageX: f1.x, pageY: f1.y, target: e.target, stopPropagation: function() {} });
    }
  }, 301);
  if ((f && l && (l.at > f.at)) && map.getZoom() < 4 && !map.isZooming()) {
    map.zoomIn((l.x - $('#map').offset().left), (l.y - $('#map').offset().top));
  }
});


#map is the main map element, #flyover is the list of items to display if necessary.

Here's how the code works:

For "touchstart":
  • When the map is first tapped, the first thing we check for is if it was a list item (has a class of disclosure). If it is, go to that page.
  • Make sure the #flyover is hidden. This is the root element for the pop-up.
  • Get originalEvent from jQuery since e doesn't contain the actual touch events.
  • Only continue if it was a single-touch event.
  • Create tap object that contains all the pertinent data we want to keep.
  • If this was the first time we've tapped on the map or it's been more than a half a second, save firstTap.
  • Otherwise, we compare firstTap to the current tap and if it's been less than 300ms for a pin or 500ms for the map, we save tap as lastTap.
For "touchend":
  • Retrieve firstTap and lastTap.
  • In setTimeout, if l1 doesn't exist or is older than f1, we run the showPopUp after 301ms. This is a specific scenario for when a user taps on a pin. A single-tap should show a pop up, but a double-tap will zoom in.
  • Finally, we zoom in on the map if firstTap is before lastTap and the map isn't zoomed all the way in or in the midst of zooming-in.

No comments: