touch.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. const SWIPE_THRESHOLD = 40;
  2. /**
  3. * Controls all touch interactions and navigations for
  4. * a presentation.
  5. */
  6. export default class Touch {
  7. constructor( Reveal ) {
  8. this.Reveal = Reveal;
  9. // Holds information about the currently ongoing touch interaction
  10. this.touchStartX = 0;
  11. this.touchStartY = 0;
  12. this.touchStartCount = 0;
  13. this.touchCaptured = false;
  14. this.onPointerDown = this.onPointerDown.bind( this );
  15. this.onPointerMove = this.onPointerMove.bind( this );
  16. this.onPointerUp = this.onPointerUp.bind( this );
  17. this.onTouchStart = this.onTouchStart.bind( this );
  18. this.onTouchMove = this.onTouchMove.bind( this );
  19. this.onTouchEnd = this.onTouchEnd.bind( this );
  20. }
  21. /**
  22. *
  23. */
  24. bind() {
  25. var revealElement = this.Reveal.getRevealElement();
  26. if( 'onpointerdown' in window ) {
  27. // Use W3C pointer events
  28. revealElement.addEventListener( 'pointerdown', this.onPointerDown, false );
  29. revealElement.addEventListener( 'pointermove', this.onPointerMove, false );
  30. revealElement.addEventListener( 'pointerup', this.onPointerUp, false );
  31. }
  32. else if( window.navigator.msPointerEnabled ) {
  33. // IE 10 uses prefixed version of pointer events
  34. revealElement.addEventListener( 'MSPointerDown', this.onPointerDown, false );
  35. revealElement.addEventListener( 'MSPointerMove', this.onPointerMove, false );
  36. revealElement.addEventListener( 'MSPointerUp', this.onPointerUp, false );
  37. }
  38. else {
  39. // Fall back to touch events
  40. revealElement.addEventListener( 'touchstart', this.onTouchStart, false );
  41. revealElement.addEventListener( 'touchmove', this.onTouchMove, false );
  42. revealElement.addEventListener( 'touchend', this.onTouchEnd, false );
  43. }
  44. }
  45. /**
  46. *
  47. */
  48. unbind() {
  49. var revealElement = this.Reveal.getRevealElement();
  50. revealElement.removeEventListener( 'pointerdown', this.onPointerDown, false );
  51. revealElement.removeEventListener( 'pointermove', this.onPointerMove, false );
  52. revealElement.removeEventListener( 'pointerup', this.onPointerUp, false );
  53. revealElement.removeEventListener( 'MSPointerDown', this.onPointerDown, false );
  54. revealElement.removeEventListener( 'MSPointerMove', this.onPointerMove, false );
  55. revealElement.removeEventListener( 'MSPointerUp', this.onPointerUp, false );
  56. revealElement.removeEventListener( 'touchstart', this.onTouchStart, false );
  57. revealElement.removeEventListener( 'touchmove', this.onTouchMove, false );
  58. revealElement.removeEventListener( 'touchend', this.onTouchEnd, false );
  59. }
  60. /**
  61. * Checks if the target element prevents the triggering of
  62. * swipe navigation.
  63. */
  64. isSwipePrevented( target ) {
  65. while( target && typeof target.hasAttribute === 'function' ) {
  66. if( target.hasAttribute( 'data-prevent-swipe' ) ) return true;
  67. target = target.parentNode;
  68. }
  69. return false;
  70. }
  71. /**
  72. * Handler for the 'touchstart' event, enables support for
  73. * swipe and pinch gestures.
  74. *
  75. * @param {object} event
  76. */
  77. onTouchStart( event ) {
  78. if( this.isSwipePrevented( event.target ) ) return true;
  79. this.touchStartX = event.touches[0].clientX;
  80. this.touchStartY = event.touches[0].clientY;
  81. this.touchStartCount = event.touches.length;
  82. }
  83. /**
  84. * Handler for the 'touchmove' event.
  85. *
  86. * @param {object} event
  87. */
  88. onTouchMove( event ) {
  89. if( this.isSwipePrevented( event.target ) ) return true;
  90. let config = this.Reveal.getConfig();
  91. // Each touch should only trigger one action
  92. if( !this.touchCaptured ) {
  93. this.Reveal.onUserInput( event );
  94. let currentX = event.touches[0].clientX;
  95. let currentY = event.touches[0].clientY;
  96. // There was only one touch point, look for a swipe
  97. if( event.touches.length === 1 && this.touchStartCount !== 2 ) {
  98. let deltaX = currentX - this.touchStartX,
  99. deltaY = currentY - this.touchStartY;
  100. if( deltaX > SWIPE_THRESHOLD && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
  101. this.touchCaptured = true;
  102. if( config.navigationMode === 'linear' ) {
  103. if( config.rtl ) {
  104. this.Reveal.next();
  105. }
  106. else {
  107. this.Reveal.prev()();
  108. }
  109. }
  110. else {
  111. this.Reveal.left();
  112. }
  113. }
  114. else if( deltaX < -SWIPE_THRESHOLD && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
  115. this.touchCaptured = true;
  116. if( config.navigationMode === 'linear' ) {
  117. if( config.rtl ) {
  118. this.Reveal.prev()();
  119. }
  120. else {
  121. this.Reveal.next();
  122. }
  123. }
  124. else {
  125. this.Reveal.right();
  126. }
  127. }
  128. else if( deltaY > SWIPE_THRESHOLD ) {
  129. this.touchCaptured = true;
  130. if( config.navigationMode === 'linear' ) {
  131. this.Reveal.prev()();
  132. }
  133. else {
  134. this.Reveal.up();
  135. }
  136. }
  137. else if( deltaY < -SWIPE_THRESHOLD ) {
  138. this.touchCaptured = true;
  139. if( config.navigationMode === 'linear' ) {
  140. this.Reveal.next();
  141. }
  142. else {
  143. this.Reveal.down();
  144. }
  145. }
  146. // If we're embedded, only block touch events if they have
  147. // triggered an action
  148. if( config.embedded ) {
  149. if( this.touchCaptured || this.Reveal.isVerticalSlide( currentSlide ) ) {
  150. event.preventDefault();
  151. }
  152. }
  153. // Not embedded? Block them all to avoid needless tossing
  154. // around of the viewport in iOS
  155. else {
  156. event.preventDefault();
  157. }
  158. }
  159. }
  160. // There's a bug with swiping on some Android devices unless
  161. // the default action is always prevented
  162. else if( isAndroid ) {
  163. event.preventDefault();
  164. }
  165. }
  166. /**
  167. * Handler for the 'touchend' event.
  168. *
  169. * @param {object} event
  170. */
  171. onTouchEnd( event ) {
  172. this.touchCaptured = false;
  173. }
  174. /**
  175. * Convert pointer down to touch start.
  176. *
  177. * @param {object} event
  178. */
  179. onPointerDown( event ) {
  180. if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
  181. event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
  182. this.onTouchStart( event );
  183. }
  184. }
  185. /**
  186. * Convert pointer move to touch move.
  187. *
  188. * @param {object} event
  189. */
  190. onPointerMove( event ) {
  191. if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
  192. event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
  193. this.onTouchMove( event );
  194. }
  195. }
  196. /**
  197. * Convert pointer up to touch end.
  198. *
  199. * @param {object} event
  200. */
  201. onPointerUp( event ) {
  202. if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
  203. event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
  204. this.onTouchEnd( event );
  205. }
  206. }
  207. }