Remove onClick delay on webkit for iPhone

Developing on the webkit for iPhone I encountered a curious delay on onClick events. It seems that the click is triggered with about 300 milliseconds delay. While this is unnoticeable on a standard web page, it can be annoying on a web application. Fortunately the click event can be overridden thus eliminating the delay.

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 Anchor) 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?

68 thoughts on “Remove onClick delay on webkit for iPhone”

  1. Please keep up the good work! Each of your iPhone-related posts has really helped to put things in perspective, and has given me a fresh way to look at iPhone web development. I agree, this platform is really not being taken advantage of, and it really gives us a wide-open playing field to set ourselves apart. Very inspirational. Thanks! I look forward to many more posts like this in the future.

  2. matt, hey
    Could you show me how to exacty add that class?? i am developing
    http://iwebkit.net and it would be awsome to have hover effects
    ontouch instad of when you release. Thanks a lot in advance for your
    tips
    p.s. i don’t know any javascript, well, a little so please explain a
    bit 😉
    Thanks!

  3. @Cristopher, I added the code that assigns a style to the clicked element.

    @punkassjim, thank you Jim, really appreciated. I’m working on a spinning wheel (slot-machine alike) element for iPhone. I bet you’ll love it 🙂

  4. I already know I do!!

    Hey are you maybe interested in working together on iwebkit. Currently we are 2 people. Me as an iPhone web designer and johan as a graphics designer. I do some photoshoping too but he does tv webdtsign for the main website.

    Our only “weakness” against the other packs is the lack of effects like an Ajax sliding effect, load 25 more elements… Etc… And that’s because we do not know javascript.

    I see you are really interesting in webkit development and like me see the fantastic possibilities of it. I touchy you might of liked using your knowledge and passion for the pack since it has been downloaded over 10000 times and I get around 500 to 1000 visitors per day.

    If you are interested and want to share your abilities with people from all over the world I think this might be a great opportunity for us both.

    Thanks a lot for your work and please contact me 🙂
    Chris

    Ps. Sorry for the spelling :p I’m typing this on my iPod 😉

  5. Thank’s for the great tutorial.

    I added it to my latest site and it works very well, the whole interface becomes much more responsive.

    However I added it to a website which uses iUi, and something weird happens, when there is a long list to scroll, everytime I flick my finger, instead of scrolling down the list it moves to the topmost part of the slide.

    I think this has something to do with having overridden the touchmome event handler.

    I tried to fix this myself but unsuccessfully. Have you looked into this and found a solution?

    BTW, using your class together with iUi makes for really native looking webApps.

  6. Thanks so much for posting this! This really improves the responsiveness of WebKit based apps on the iPhone… I love the idea of building hybrid apps…. Apps that are native executables, but essentially run a HTML/JS/CSS bundle… and although I was going forward with it, I felt that the UI wasn’t quite as responsive as I’d like… But your posts on decreasing the reaction time for clicks, and how to lock a header and footer for a scrollable area have made a big difference… In case I haven’t been clear enough 😉

    Kudos! You Rock!

    Brad.

  7. Hi, thanks for this awesome script. This is super helpful as it does provide native speed button clicking on iphone web apps.

    I was wondering (because I am not a javascript guru) how I would use this, but still allow scrolling when the mousedown is going on?

    If i use this on pages taller than the screen height…you can no longer scroll…I understand the basics of why this happens, but I am not savvy enough to understand how to allow scrolling with the mouse down.

    Thanks, and keep up the amazing work.

  8. On my ipod touch v2, there are no such big difference in you demo between onClickEvent (about 200ms) and the touchStartEvent (about 130ms).
    But thanks anyway, that’s still 70ms !!

  9. I’d like to do something like this in a jQTouch — but Safari manually fires a second click after I trigger the first one… I’ve tried every combination of preventDefault/stopPropagation/etc. to no avail. It seems that if the user taps quickly, it fires that second click no matter what…

    Any thoughts?

    1. I was having the same problem. It’s due to adding a second (or third, fourth, etc) touchstart event handler to existing objects. This fixes it by adding a flag to the objects that have already had the event handler added.

      var buttons = document.getElementsByClassName(“button”);
      for (i=0;i<buttons.length;i++) {
      if (!buttons[i].clickDelayRemoved) {
      new NoClickDelay(buttons[i]);
      buttons[i].clickDelayRemoved = true;
      }
      }

      I suppose you could add the check to the original code like so, but the above is fine for my needs:
      if( window.Touch && !this.element.clickDelayRemoved) {
      this.element.addEventListener('touchstart', this, false);
      }

  10. hi David,
    the first solution that comes to my mind is to place a timeout just after the first click which prevents the second to occur.

    Even easier: on the first click set a variable with the target of the click and the timestamp of the event. On the second click
    IF firstClickTarget == secondClickTarget AND secondClickTimestamp-firstClickTimestamp<200ms THEN RETURN FALSE ELSE doTheSecondClick
    A piece of cake :)

    1. I’m about 2 years late responding to this. I just noticed that I had this problem using jQuery’s element.click() and couldn’t get Matteo’s solution to work without adding in lots of code. For any other readers, I decided to do a quick workaround and specify ‘click’ and ‘touchstart’ depending on the viewer’s device. Using touchstart won’t cause a repeat event, but won’t work with a mouse, naturally.

      Loosely:

      IF (window.Touch) clickTrigger = ‘click’
      ELSE clickTrigger = ‘touchstart’

      element.bind(clickTrigger, function () { … })

      Matteo, love your work—keep it up, not convinced about dropping jQuery just yet though 🙂

  11. This was a great site. I needed to find something for my Homework and This site helped me out so much! Thanx alot!!!!

  12. Wanted to quote Joe and ask if anyone has found a solution:

    ——————-

    I was wondering (because I am not a javascript guru) how I would use this, but still allow scrolling when the mousedown is going on?

    If i use this on pages taller than the screen height…you can no longer scroll…I understand the basics of why this happens, but I am not savvy enough to understand how to allow scrolling with the mouse down.

    Thanks, and keep up the amazing work.

    ——————-

    Cheers!

  13. Simplified cross-browser version:

    Object.prototype.addOnClick = function(func){
    	if( window.Touch ){
    		this.addEventListener('touchstart', function(e){
    			e.preventDefault();
    			this.moved = false;
    			this.addEventListener('touchmove', function(){
    				this.moved = true;
    			}, false);
    			this.addEventListener('touchend', function(){
    				this.removeEventListener('touchmove', this, false);
    				this.removeEventListener('touchend', this, false);
    				if( !this.moved ) func();
    			}, false);
    
    		}, false);
    	} else{
    		this.onclick = func;
    	}
    }

    Usage:

    some_element.addOnclick( function(){ alert(‘hello world’); } );

  14. Dear Matteo,
    I was very excited when i found your script, but after i used i realized that it breaks scrolling alltogether. Is there something that it can be done for this?

  15. Using this script has given me fantastic results on a highly interactive site. I have one addition that allows the X and Y coordinates to be passed along (necessary on my site) that you may consider adding.

    var theEvent = document.createEvent(‘MouseEvents’);
    //theEvent.initEvent(‘click’, true, true);
    theEvent.initMouseEvent( ‘click’, true, true, window, 1, e.changedTouches[0].clientX, e.changedTouches[0].clientY, e.changedTouches[0].clientX, e.changedTouches[0].clientY, false, false, true, false, 0, null );

    1. the “perfect” click I think is:
      theEvent.initMouseEvent("click", true, true, e.view, 1, e.changedTouches[0].screenX, e.changedTouches[0].screenY, e.changedTouches[0].clientX, e.changedTouches[0].clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,0, null)

      I think it’s time to update the script 🙂

  16. Ciao, non so se ti ricordi della mail e della richiesta che ti avevo fatto.
    Ho aspettato a richiedertelo via mail perchè immaginavo che avesti aggiornato lo script.
    Comunque ti ricordo i due “buchi” che avevo riscontrato (io ho usato e uso il primo script e non il secondo script relativo a come applicarlo ad una precisa classe css):
    -lo scroll non funziona, ma l’ho risolto spostato il preventdefault nel touchend.
    -Ora c’è il problema che lo scroll funziona ma quando scrolli e passi il dito su dei menu in tali menu si evidenzia l’effetto hover del menu anche se non li vuoi cliccare ma ci passi il dito sopra solo per scrollare.
    -Ho un altro problema: il select del forms non funziona, o meglio, togliendo il tuo script funziona, lasciandolo funziona ma soltanto se quando ci clicco tengo premuto per un po’ più di tempo il dito.

    Aspetto questo aggiornamento, grazie.

  17. Great to have – was beating my head against the wall over this. It appears that safari for iphone/ipad does even worse than suspected: we’ve encountered the kink that if you move the touched element in the window between touchend and click (that 300ms delay), safari fires the event – at the screen coordinate, not on the original target. This means you could clickjack just by swapping elements during that delay window.

  18. Wow… just realized, if the browser is using the more specific initMouseEvent (to fake/substitute a ‘late’ click event), it is specifying the x and y, but (perhaps) assuming the target will be the same… a completely artificial click with properties carried over from the touchend event.

  19. I have one issue that scroll will be killed if preventDefault is called in onTouchStart. So If I also want scroll is supported, what should be the appropriate solution? Thank you!

    1. And I tried fire the click event in onTouchEnd, it seems that two click event (self-created click event and the original click event) happens. PreventDefault and StopPropagation are all called in onTouchEnd, but there are still two click event happens:(

      1. I experienced the two-click problem as well. It only happens occasionally, and it may depend on how long one holds the button.

  20. Quick question : is there any way to make this work for form fields? I still see noticeable lag on form fields unless I’m doin it wrong.

  21. One issue I encountered is that it breaks native scrolling.

    So if I have a ul tag with the id of “main-menu” and apply the NoClickDelay() to that ul#main-menu container I basically can’t scroll to the bottom of my list.

    Is there a way to allow the view to scroll if you start your touch on an element inside a NoClickDelay() container and your touch moves? This would be ideal in most scenarios.

    I did try removing e.preventDefault(); from inside onTouchStart in your example code but the elements will still act as if they were highlighted.

    So if I go that route, removing e.preventDefault() how would you go about removing the “pressed” state immediately when the user begins to scroll?

    Hope that makes sense! 🙂

    Thanks for a wonderful head start!

  22. Android has same problem with laggy onClicks. Your demo doesn’t work on Android, unless I comment out window.Touch below, so I believe that DOM property is only visible on iOS.

    function NoClickDelay(el) {
    this.element = el;
    // if (window.Touch) not available on android
    this.element.addEventListener(‘touchstart’, this, false);
    }

    With the above change Android gets non-laggy touch event! Here I come phonegap!

  23. Hi,

    You should also handle the the event “touchcancel”.

    In some cases touchcancel event if fired after a touchstart and not a touchend.

    If that happen, with your current implementation, the element will have the class “pressed” for ever.

    .-

  24. Pingback: Varcade
  25. For me, waiting to call e.preventDefault until the onTouchEnd function allows the native scrolling to still work while still removing the delay when tapping buttons/links.

  26. Great script.

    A slight modification would allow for some wiggle room if the movement occurs inside the boundaries of the “button” (the element that has the onClick event attached to it).

    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();
    //e.stopPropagation();

    //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.checkTarget = this.theTarget;

    this.theTarget.className+= ‘ pressed’;

    this.element.addEventListener(‘touchmove’, this, false);
    this.element.addEventListener(‘touchend’, this, false);
    },

    onTouchMove: function(e) {
    //this.moved = true;
    this.checkTarget = document.elementFromPoint(e.targetTouches[0].clientX, e.targetTouches[0].clientY);
    if(this.checkTarget.nodeType == 3) this.checkTarget = this.checkTarget.parentNode;
    },

    onTouchEnd: function(e) {
    this.element.removeEventListener(‘touchmove’, this, false);
    this.element.removeEventListener(‘touchend’, this, false);

    if(this.checkTarget == this.theTarget ) {
    //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;
    }
    };

  27. Is it possible to replace all “onclick” keywords inside the HTML and other loaded scripts’ with the “onTouchEnd” before the DOM content is not loaded; (if the browser supports onTouchEnd functionality) ?

    I think it would be more simple and generic solution.

  28. Has anyone had problems with this working on tags?

    I am using it on a table full of various elements and it seems to render my tags unusable.

    🙁

  29. Matteo, this is great work as usual. With the update from 2009/02/27 that includes the “pressed” state class, does the class get added automatically or do I need to do anything else in addition to “new NoClickDelay(document.getElementById(‘element’));”?

  30. I’m using this with a twist. I use modernizr because I want to target as many devices as possible, and use the ‘touch’ property to determine if it should act on click or on touchstart.

    var CLICK_EVENT = "click";
    if(Modernizr.touch)
    {
    CLICK_EVENT = "ontouchstart";
    }

    function doStuff()
    {
    $('#selector').on(CLICK_EVENT, function() { //stuff });
    }

    Hope it helps.

  31. Here’s my version. It’s complete with everything:

    – Responsive click
    – If finger is moved more than 10px the click is canceled
    – ghostclick is prevented
    – works with touch device and common browser (touch and click events)

    how to use:

    to set all elements responsive (the entire body):

    var bdy = document.getElementsByTagName('body');
    new FastClick(bdy[0]);

    to a specific element:

    var el1 = document.getElementById("elementId");
    new FastClick(el1);

    the code:

    function FastClick(el) {
    
    		this.el = el || document.getElementById(el);
    		this.moved = false;
    		this.startX = 0;
    		this.startY = 0;
    		this.coordinates = [];
    
    //		if (window.Touch)
    		this.el.addEventListener('touchstart', this, false);
    
    		this.el.addEventListener('click', this, false);
    
    	}
    
    FastClick.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;
    		case 'click': this.onMouseClick(e); break;
    
    		}
    	},
    
    	onTouchStart: function(e) {
    
    		e.stopPropagation();
    
    		this.moved = false;
    
    		this.target = document.elementFromPoint(
    				e.changedTouches[0].clientX,
    				e.changedTouches[0].clientY);
    		if (this.target.nodeType == 3) this.target = this.target.parentNode;
    
    		this.startX = e.touches[0].clientX;
    		this.startY = e.touches[0].clientY;
    
    		this.el.addEventListener('touchmove', this, false);
    		this.el.addEventListener('touchend', this, false);
    
    	},
    
    	onTouchMove: function(e) {
    
    		//if finger moves more than 10px flag to cancel
            //code.google.com/mobile/articles/fast_buttons.html
    		if (Math.abs(e.touches[0].clientX - this.startX) &gt; 10 ||
    				Math.abs(e.touches[0].clientY - this.startY) &gt; 10) {
    			this.moved = true;
    		}
    
    	},
    
    	onTouchEnd: function(e) {
    
    		e.preventDefault();
    		e.stopPropagation();
    
    		 this.preventGhostClick(this.startX, this.startY);
    
    		 this.el.removeEventListener('touchmove', this, false);
    		 this.el.removeEventListener('touchend', this, false);
    
    		if(!this.moved &amp;&amp; this.target){
    			var evt = document.createEvent('MouseEvents');
    			evt.initMouseEvent('click', true, true);
    			this.target.dispatchEvent(evt);
    		}
    
    		//reset
    		this.target = undefined;
    		this.startX = 0;
    		this.startY = 0;
    		this.moved = false;
    
    	},
    
    	onTouchCancel: function(e) {
    
    		//reset
    		this.target = undefined;
    		this.startX = 0;
    		this.startY = 0;
    		this.moved = false;
    	},
    
    
    	preventGhostClick: function(x, y) {
    
    		 this.coordinates.push(x, y);
    		 var self = this;
    		 setTimeout(self.popArr, 2500);
    	},
    
    
    	popArr: function() {
    		 this.coordinates.splice(0, 2);
    	},
    
    
    	onMouseClick: function(e) {
    
    		for (var i = 0; i &lt;  this.coordinates.length; i += 2) {
    			var x =  this.coordinates[i];
    			var y =  this.coordinates[i + 1];
    			if (Math.abs(e.clientX - x) &lt; 25 &amp;&amp; Math.abs(e.clientY - y) &lt; 25) {
    				e.stopPropagation();
    				e.preventDefault();
    			}
    		}
    	}
    };
    

    Hope it helps!

      1. and in the end jQuery and Zepto are Javascript… The class can be easily imported in both frameworks for all users but please bare in mind that when working on web development, it’s better to know what’s behind a framework, what’s using and how’s working. I’m not saying is bad attitude to use a js framework but imo is better to use/study ‘original’ js examples 🙂

  32. Is this a de facto solution still or is there a better approach with jquery now a days?

    If so, is there any reason not to attach this to the body once, or does it make more sense to attach it to the elements (or maybe one level up?)

    Thanks!
    Andrew

  33. Thanks for an awesome script Matteo.

    The code needed some changes in my use-case, so here is a take on it to get the default behavior back like scrolling, pinching, etc. without getting the second click event (ghost click).

    function NoClickDelay(el) {
    	if ('ontouchstart' in window) {
    		this.element = typeof el === "object" ? el : document.getElementById(el);
    		this.element.addEventListener('touchstart', this, false);
    		this.element.addEventListener('click', this, true);
    	}
    }
    
    NoClickDelay.prototype = {
    	handleEvent: function(e) {
    		this[e.type](e);
    	},
    
    	touchstart: function(e) {
    		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);
    	},
    
    	touchmove: function(e) {
    		this.moved = true;
    		this.theTarget.className = this.theTarget.className.replace(/ ?pressed/gi, '');
    	},
    
    	touchend: function(e) {
    		this.element.removeEventListener('touchmove', this, false);
    		this.element.removeEventListener('touchend', this, false);
    
    		if (!this.moved &amp;&amp; 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;
    	},
    
    	click: function(e) {
    		if (this.theTarget === undefined) {
    			e.stopPropagation();
    			e.preventDefault();
    		}
    	}
    };
  34. Just noticed that e.stopPropagation(); should be e.stopImmediatePropagation(); to prevent the ghost click from happening in every case.

  35. Thanks for this script. I am a JS n00b, so, just wondering why the script only works on an id and doesn’t work when I try and target a class? Is there a way to do this?

    var theTap = document.getElementsByClassName(‘button’);
    new NoClickDelay(theTap);

  36. Hi.

    I have just tried it on my iPhone 4 and I see no difference when pressing the two buttons.

    Both buttons have delays between 150 and 200 ms.

    On my Desire HD the delays are also equally for the two buttons, but the delay is between 450 and 500 ms 🙁

    I guess the above is no longer relevant for iPhone since there is no difference between the two buttons or have I missed something?

  37. hi,

    I’ve tried your code to improve the behavior of the JQM events and the delay which they have after clicking a button element, but I have a problem: after clicking the class ui-btn-active is still on the element and it isn’t removed. I’m not a totally newbie on JQ but, but I’m learning about the events handler, so, I’ll appreciate a little help.

    Thanks in advance!
    Luis Miguel.

  38. Great bit of code.

    I modified it a bit to allow the finger to move up to 50 pixels before canceling the click.

    I am confused with .prototype. It seems that somehow handleEvent gets called. How?


    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.x = e.targetTouches[0].clientX;
    this.y = e.targetTouches[0].clientY;
    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) {
    var x = e.targetTouches[0].clientX;
    var y = e.targetTouches[0].clientY;
    if( Math.sqrt(Math.pow(x-this.x,2)+Math.pow(y-this.y,2))>50){
    this.moved = true;
    this.theTarget.className = this.theTarget.className.replace(/ ?pressed/gi, '');
    this.theTarget.className = this.theTarget.className.replace(/ ?active/gi, '');
    } else {
    if(this.moved==true){
    this.moved=false;
    this.theTarget.className+= ' pressed';
    }
    }
    },
    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, '');
    this.theTarget.className+= ' active';
    var theEvent = document.createEvent('MouseEvents');
    theEvent.initEvent('click', true, true);
    this.theTarget.dispatchEvent(theEvent);
    }
    this.theTarget = undefined;
    }
    };

  39. Nice script! I made it into a jQuery plug-in and added a function to make the script set focus on the element selected after firing the click event. I found this fixes an issue where input fields are not properly selected on an iPhone if they are within the NoClickDelay element.

    Code:

    (function( $ ) {
    $.fn.noClickDelay = function() {

    var $wrapper = this;
    var $target = this;
    var moved = false;

    $wrapper.bind('touchstart mousedown',function(e) {
    e.preventDefault();
    moved = false;
    $target = $(e.target);
    if($target.nodeType == 3) {
    $target = $($target.parent());
    }
    $target.addClass('pressed');

    $wrapper.bind('touchmove mousemove',function(e) {
    moved = true;
    $target.removeClass('pressed');
    });

    $wrapper.bind('touchend mouseup',function(e) {
    $wrapper.unbind('mousemove touchmove');
    $wrapper.unbind('mouseup touchend');
    if(!moved && $target.length) {
    $target.removeClass('pressed');
    $target.trigger('click');
    $target.focus();
    }
    });
    });

    };
    })( jQuery );

    Use as such:
    $('#wrapperElement').noClickDelay();

  40. I my iphone4 I get a click delay of 560ms!!

    What are apple smoking?

    Even with your improvement its still 153ms seconds… better than 560.

Leave a Reply

Your email address will not be published. Required fields are marked *