I assume that 300ms is the time frame Apple guesses an user needed to perform gestures, but there are situations where this delay can be really annoying. Think to a calculator application with 300ms delay each time you press a button. Unacceptable.
The simplest solution is to use onTouchStart
instead of onClick
events. Something like <div ontouchstart="doSomething()">
is perfectly logical and overrides the onClick
delay. But the action is triggered as soon as you touch the screen and may end up to undesirable results, so I tried to recreate the mouseDown/mouseUp events sequence with touchStart/touchMove/touchEnd.
Point your iPhone or simulator to my demo page. Clicking on the first button the standard click event is fired (with infamous 300ms delay), the second button instead overrides the onClick
event and the action is actually cast on touchEnd with no delay.
The code I use is the following:
function NoClickDelay(el) { this.element = el; if( window.Touch ) this.element.addEventListener(‘touchstart‘, this, false); } NoClickDelay.prototype = { handleEvent: function(e) { switch(e.type) { case ‘touchstart‘: this.onTouchStart(e); break; case ‘touchmove‘: this.onTouchMove(e); break; case ‘touchend‘: this.onTouchEnd(e); break; } }, onTouchStart: function(e) { e.preventDefault(); this.moved = false; this.element.addEventListener(‘touchmove‘, this, false); this.element.addEventListener(‘touchend‘, this, false); }, onTouchMove: function(e) { this.moved = true; }, onTouchEnd: function(e) { this.element.removeEventListener(‘touchmove‘, this, false); this.element.removeEventListener(‘touchend‘, this, false); if( !this.moved ) { // Place your code here or use the click simulation below var theTarget = document.elementFromPoint(e.changedTouches[0].clientX, e.changedTouches[0].clientY); if(theTarget.nodeType == 3) theTarget = theTarget.parentNode; var theEvent = document.createEvent(‘MouseEvents‘); theEvent.initEvent(‘click‘, true, true); theTarget.dispatchEvent(theEvent); } } };
The script creates a touchStart event and performs the click action on touchEnd which occurs 300ms before the standard click event. This is just an example to get you started, my function triggers the click event on touchEnd so you still need to add an onClick
event (or an A
nchor) somewhere if you want something to happen. You could better place directly your code on touchEnd but if you use my method your application will be compatible with both touch (the iphone) and non-touch enabled devices (the standard browser).
To activate the script all you need to do is: new NoClickDelay(document.getElementById(‘element‘));
. From now on all your clicks inside the element
will be performed with no delay.
Note that you don’t need to apply the NoClickDelay()
function to all the objects in the page, but just to a container. If for instance you have an unordered list, you don’t need to add the script to each <li>
elements, but just to the <ul>
. This has been done to reduce the number of event listeners so less resources are needed.
To closely mimic the standard UI you could add a hover class on touchStart to highlight the pressed object in someway and remove it on touchMove. (Apple places a gray rectangle over pressed elements).
Update 2009/02/27: By popular demand here follows the code that assigns the “pressed” CSS class to the clicked element.
function NoClickDelay(el) { this.element = typeof el == ‘object‘ ? el : document.getElementById(el); if( window.Touch ) this.element.addEventListener(‘touchstart‘, this, false); } NoClickDelay.prototype = { handleEvent: function(e) { switch(e.type) { case ‘touchstart‘: this.onTouchStart(e); break; case ‘touchmove‘: this.onTouchMove(e); break; case ‘touchend‘: this.onTouchEnd(e); break; } }, onTouchStart: function(e) { e.preventDefault(); this.moved = false; this.theTarget = document.elementFromPoint(e.targetTouches[0].clientX, e.targetTouches[0].clientY); if(this.theTarget.nodeType == 3) this.theTarget = theTarget.parentNode; this.theTarget.className+= ‘ pressed‘; this.element.addEventListener(‘touchmove‘, this, false); this.element.addEventListener(‘touchend‘, this, false); }, onTouchMove: function(e) { this.moved = true; this.theTarget.className = this.theTarget.className.replace(/ ?pressed/gi, ‘‘); }, onTouchEnd: function(e) { this.element.removeEventListener(‘touchmove‘, this, false); this.element.removeEventListener(‘touchend‘, this, false); if( !this.moved && this.theTarget ) { this.theTarget.className = this.theTarget.className.replace(/ ?pressed/gi, ‘‘); var theEvent = document.createEvent(‘MouseEvents‘); theEvent.initEvent(‘click‘, true, true); this.theTarget.dispatchEvent(theEvent); } this.theTarget = undefined; } };
Are you aware of any simpler solution?
https://github.com/ftlabs/fastclick