I'm creating a site on Safari for iPad. I need to prevent the zoom on double-tapping event but I have two problems:
- a double tap doesn’t generate any events, so I can't use "event.preventDefault();"
- I need to do this only when some conditions are fulfilled, so I can't use the tag "
<meta name = "viewport" content = "user-scalable = no">
"... If I did that, users could never zoom on my page.
How can I fix these problems?
Mobile Safari does not support the javascript ondblclick event. It's interpreted by Safari as a "zoom".
Raul Sanchez has posted a potential solution:
http://appcropolis.com/implementing-doubletap-on-iphones-and-ipads/
Here's a jQuery plugin I wrote for the same purpose - selectively disabling double-tap zoom on given page elements (in my case, navigation buttons to flip pages) I want to respond to every tap (including double-tap) as a normal click event, with no iOS "touch magic".
To use it, just run something like $('.prev,.next').nodoubletapzoom();
on the elements you care for. (Edit: now also ignores pinches)
// jQuery no-double-tap-zoom plugin
// Triple-licensed: Public Domain, MIT and WTFPL license - share and enjoy!
(function($) {
var IS_IOS = /iphone|ipad/i.test(navigator.userAgent);
$.fn.nodoubletapzoom = function() {
if (IS_IOS)
$(this).bind('touchstart', function preventZoom(e) {
var t2 = e.timeStamp
, t1 = $(this).data('lastTouch') || t2
, dt = t2 - t1
, fingers = e.originalEvent.touches.length;
$(this).data('lastTouch', t2);
if (!dt || dt > 500 || fingers > 1) return; // not double-tap
e.preventDefault(); // double tap - prevent the zoom
// also synthesize click events we just swallowed up
$(this).trigger('click').trigger('click');
});
};
})(jQuery);
try this modified code. It should work for both android and IOS devices
(function($) {
$.fn.nodoubletapzoom = function() {
$(this).bind('touchstart', function preventZoom(e){
var t2 = e.timeStamp;
var t1 = $(this).data('lastTouch') || t2;
var dt = t2 - t1;
var fingers = e.originalEvent.touches.length;
$(this).data('lastTouch', t2);
if (!dt || dt > 500 || fingers > 1){
return; // not double-tap
}
e.preventDefault(); // double tap - prevent the zoom
// also synthesize click events we just swallowed up
$(e.target).trigger('click');
});
};
})(jQuery);
Then apply the nodoubletapzoom() to the body tag
$("body").nodoubletapzoom();
I modified @ecmanaut's javascript solution to do two things.
- I use modernizr, so it'll put a .touch css class on the html node, so I can detect touch screens instead of using user agent detection. Perhaps there is a "modernizr-less" approach, but I don't know it.
- I separate each "click" so they happen separately, if you click once, it'll click once, if you rapidly click the button/trigger, it'll count multiple times.
- some minor code formatting changes, I prefer each var defined separately etc, this is more a "me" thing than anything else, I suppose you could just revert that, nothing bad will happen.
I believe these modifications make it better, cause you can increment a counter 1,2,3,4 instead of 2,4,6,8
here is the modified code:
// jQuery no-double-tap-zoom plugin
// Triple-licensed: Public Domain, MIT and WTFPL license - share and enjoy!
//
// chris.thomas@antimatter-studios.com: I modified this to
// use modernizr and the html.touch detection and also to stop counting two
// clicks at once, but count each click separately.
(function($) {
$.fn.nodoubletapzoom = function() {
if($("html.touch").length == 0) return;
$(this).bind('touchstart', function preventZoom(e){
var t2 = e.timeStamp;
var t1 = $(this).data('lastTouch') || t2;
var dt = t2 - t1;
var fingers = e.originalEvent.touches.length;
$(this).data('lastTouch', t2);
if (!dt || dt > 500 || fingers > 1){
return; // not double-tap
}
e.preventDefault(); // double tap - prevent the zoom
// also synthesize click events we just swallowed up
$(this).trigger('click');
});
};
})(jQuery);
The apply the nodoubletapzoom() to the body tag, like this
$("body").nodoubletapzoom();
your html construction, should be something like this
<body>
<div class="content">...your content and everything in your page</div>
</body>
Then in your javascript, bind your click handlers like this
$(".content")
.on(".mydomelement","click",function(){...})
.on("button","click",function(){...})
.on("input","keyup blur",function(){...});
// etc etc etc, add ALL your handlers in this way
you can use any jquery event handler and any dom node. now you dont have to do anything more than that, you have to attach all your event handlers in this way, I've not tried another way, but this way works absolutely rock solid, you can literally rap the screen 10 times a second and it doesnt zoom and registers the clicks (obviously not 10 per second, the ipad isn't that fast, what I mean is, you can't trigger the zoom)
I think this works because you're attaching the nozoom handler to the body and then attaching all your event handlers to the ".content" node, but delegating to the specific node in question, therefore jquery catches all the handlers at the last stage before the event would bubble up to the body tag.
the main benefit of this solution is you only have to assign the nodoubletapzoom() handler to the body, you only do it once, not once for each element, so it's much less work, effort and thinking required in order to get things done.
this has the additional benefit in that if you add content using AJAX, they automatically have the handlers ready and waiting, I suppose if you DIDNT want that, then this method won't work for you and you'll have to adjust it more.
I've verified this code works on ipad, it's beautiful actually, well done to @ecmanaut for the primary solution!!
** Modified on Saturday 26th May because I found what seems to be a perfect solution with absolutely minimal effort
All solutions that I have seen (on this page and also elsewhere) have a side effect that they prevent in fast rate repeated clicks. Allows one click per 500ms or similar. This can be okay in some cases, but not eg. if you have a shooter or arrow buttons to allow fast moving from item to item.
The easiest solution is this:
$('#nozoom').on('touchstart', function(e)
{
fn_start(e);
e.preventDefault();
});
This calls fn_start()
(the actual callback function) every time when touch is started, but prevents then default zoomings etc.
The working comparative example is here: http://jsbin.com/meluyevisi/1/.
Green box prevents, red box allows.
just seting viewport may not always be enough - in some cases you may have to also call
event.preventDefault();
on the touchstart handler.
The accepted 'double click' answer is to me a little 'bloated' and using a timestamp..... why? Look at this simple implementation without excessive 'bloat'.
function simulateDblClickTouchEvent(oo)
{
var $oo = !oo?{}:$(oo);
if( !$oo[0] )
{ return false; }
$oo.bind('touchend', function(e)
{
var ot = this,
ev = e.originalEvent;
if( ev && typeof ev.touches == 'object' && ev.touches.length > 1 )
{ return; }
ot.__taps = (!ot.__taps)?1:++ot.__taps;
if( !ot.__tabstm ) // don't start it twice
{
ot.__tabstm = setTimeout( function()
{
if( ot.__taps >= 2 )
{ ot.__taps = 0;
$(ot).trigger('dblclick');
}
ot.__tabstm = 0;
ot.__taps = 0;
},800);
}
});
return true;
};
Usage:
simulateDblClickTouchEvent($('#example'));
or
simulateDblClickTouchEvent($('.example'));
function returns true or false when event is binded or not.
To prevent zoom things and scroll do this:
function disableTouchScroll()
{
try {
document.addEventListener('touchmove', function(e) { e.preventDefault(); }, true );
$j('body')[0].addEventListener('touchmove', function(e) { e.preventDefault(); }, true );
}
catch(ee) { return false; }
return true;
}
Also with CSS it is easy to avoid zooming:
body * { -webkit-user-select:none; }
- = not efficient but try it, it works great.
Cheers!