touch.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. // Zepto.js
  2. // (c) 2010-2014 Thomas Fuchs
  3. // Zepto.js may be freely distributed under the MIT license.
  4. ;(function($){
  5. var touch = {},
  6. touchTimeout, tapTimeout, swipeTimeout, longTapTimeout,
  7. longTapDelay = 750,
  8. gesture
  9. function swipeDirection(x1, x2, y1, y2) {
  10. return Math.abs(x1 - x2) >=
  11. Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : (y1 - y2 > 0 ? 'Up' : 'Down')
  12. }
  13. function longTap() {
  14. longTapTimeout = null
  15. if (touch.last) {
  16. touch.el.trigger('longTap')
  17. touch = {}
  18. }
  19. }
  20. function cancelLongTap() {
  21. if (longTapTimeout) clearTimeout(longTapTimeout)
  22. longTapTimeout = null
  23. }
  24. function cancelAll() {
  25. if (touchTimeout) clearTimeout(touchTimeout)
  26. if (tapTimeout) clearTimeout(tapTimeout)
  27. if (swipeTimeout) clearTimeout(swipeTimeout)
  28. if (longTapTimeout) clearTimeout(longTapTimeout)
  29. touchTimeout = tapTimeout = swipeTimeout = longTapTimeout = null
  30. touch = {}
  31. }
  32. function isPrimaryTouch(event){
  33. return (event.pointerType == 'touch' ||
  34. event.pointerType == event.MSPOINTER_TYPE_TOUCH)
  35. && event.isPrimary
  36. }
  37. function isPointerEventType(e, type){
  38. return (e.type == 'pointer'+type ||
  39. e.type.toLowerCase() == 'mspointer'+type)
  40. }
  41. $(document).ready(function(){
  42. var now, delta, deltaX = 0, deltaY = 0, firstTouch, _isPointerType
  43. if ('MSGesture' in window) {
  44. gesture = new MSGesture()
  45. gesture.target = document.body
  46. }
  47. $(document)
  48. .bind('MSGestureEnd', function(e){
  49. var swipeDirectionFromVelocity =
  50. e.velocityX > 1 ? 'Right' : e.velocityX < -1 ? 'Left' : e.velocityY > 1 ? 'Down' : e.velocityY < -1 ? 'Up' : null;
  51. if (swipeDirectionFromVelocity) {
  52. touch.el.trigger('swipe')
  53. touch.el.trigger('swipe'+ swipeDirectionFromVelocity)
  54. }
  55. })
  56. .on('touchstart MSPointerDown pointerdown', function(e){
  57. if((_isPointerType = isPointerEventType(e, 'down')) &&
  58. !isPrimaryTouch(e)) return
  59. firstTouch = _isPointerType ? e : e.touches[0]
  60. if (e.touches && e.touches.length === 1 && touch.x2) {
  61. // Clear out touch movement data if we have it sticking around
  62. // This can occur if touchcancel doesn't fire due to preventDefault, etc.
  63. touch.x2 = undefined
  64. touch.y2 = undefined
  65. }
  66. now = Date.now()
  67. delta = now - (touch.last || now)
  68. touch.el = $('tagName' in firstTouch.target ?
  69. firstTouch.target : firstTouch.target.parentNode)
  70. touchTimeout && clearTimeout(touchTimeout)
  71. touch.x1 = firstTouch.pageX
  72. touch.y1 = firstTouch.pageY
  73. if (delta > 0 && delta <= 250) touch.isDoubleTap = true
  74. touch.last = now
  75. longTapTimeout = setTimeout(longTap, longTapDelay)
  76. // adds the current touch contact for IE gesture recognition
  77. if (gesture && _isPointerType) gesture.addPointer(e.pointerId);
  78. })
  79. .on('touchmove MSPointerMove pointermove', function(e){
  80. if((_isPointerType = isPointerEventType(e, 'move')) &&
  81. !isPrimaryTouch(e)) return
  82. firstTouch = _isPointerType ? e : e.touches[0]
  83. cancelLongTap()
  84. touch.x2 = firstTouch.pageX
  85. touch.y2 = firstTouch.pageY
  86. deltaX += Math.abs(touch.x1 - touch.x2)
  87. deltaY += Math.abs(touch.y1 - touch.y2)
  88. })
  89. .on('touchend MSPointerUp pointerup', function(e){
  90. if((_isPointerType = isPointerEventType(e, 'up')) &&
  91. !isPrimaryTouch(e)) return
  92. cancelLongTap()
  93. // swipe
  94. if ((touch.x2 && Math.abs(touch.x1 - touch.x2) > 30) ||
  95. (touch.y2 && Math.abs(touch.y1 - touch.y2) > 30))
  96. swipeTimeout = setTimeout(function() {
  97. touch.el.trigger('swipe')
  98. touch.el.trigger('swipe' + (swipeDirection(touch.x1, touch.x2, touch.y1, touch.y2)))
  99. touch = {}
  100. }, 0)
  101. // normal tap
  102. else if ('last' in touch)
  103. // don't fire tap when delta position changed by more than 30 pixels,
  104. // for instance when moving to a point and back to origin
  105. if (deltaX < 30 && deltaY < 30) {
  106. // delay by one tick so we can cancel the 'tap' event if 'scroll' fires
  107. // ('tap' fires before 'scroll')
  108. tapTimeout = setTimeout(function() {
  109. // trigger universal 'tap' with the option to cancelTouch()
  110. // (cancelTouch cancels processing of single vs double taps for faster 'tap' response)
  111. var event = $.Event('tap')
  112. event.cancelTouch = cancelAll
  113. touch.el.trigger(event)
  114. // trigger double tap immediately
  115. if (touch.isDoubleTap) {
  116. if (touch.el) touch.el.trigger('doubleTap')
  117. touch = {}
  118. }
  119. // trigger single tap after 250ms of inactivity
  120. else {
  121. touchTimeout = setTimeout(function(){
  122. touchTimeout = null
  123. if (touch.el) touch.el.trigger('singleTap')
  124. touch = {}
  125. }, 250)
  126. }
  127. }, 0)
  128. } else {
  129. touch = {}
  130. }
  131. deltaX = deltaY = 0
  132. })
  133. // when the browser window loses focus,
  134. // for example when a modal dialog is shown,
  135. // cancel all ongoing events
  136. .on('touchcancel MSPointerCancel pointercancel', cancelAll)
  137. // scrolling the window indicates intention of the user
  138. // to scroll, not tap or swipe, so cancel all ongoing events
  139. $(window).on('scroll', cancelAll)
  140. })
  141. ;['swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown',
  142. 'doubleTap', 'tap', 'singleTap', 'longTap'].forEach(function(eventName){
  143. $.fn[eventName] = function(callback){ return this.on(eventName, callback) }
  144. })
  145. })(Zepto)