diff options
Diffstat (limited to 'present/js/controllers/touch.js')
| -rw-r--r-- | present/js/controllers/touch.js | 263 | 
1 files changed, 263 insertions, 0 deletions
| diff --git a/present/js/controllers/touch.js b/present/js/controllers/touch.js new file mode 100644 index 0000000..5ac6a10 --- /dev/null +++ b/present/js/controllers/touch.js @@ -0,0 +1,263 @@ +import { isAndroid } from '../utils/device.js' +import { matches } from '../utils/util.js' + +const SWIPE_THRESHOLD = 40; + +/** + * Controls all touch interactions and navigations for + * a presentation. + */ +export default class Touch { + +	constructor( Reveal ) { + +		this.Reveal = Reveal; + +		// Holds information about the currently ongoing touch interaction +		this.touchStartX = 0; +		this.touchStartY = 0; +		this.touchStartCount = 0; +		this.touchCaptured = false; + +		this.onPointerDown = this.onPointerDown.bind( this ); +		this.onPointerMove = this.onPointerMove.bind( this ); +		this.onPointerUp = this.onPointerUp.bind( this ); +		this.onTouchStart = this.onTouchStart.bind( this ); +		this.onTouchMove = this.onTouchMove.bind( this ); +		this.onTouchEnd = this.onTouchEnd.bind( this ); + +	} + +	/** +	 * +	 */ +	bind() { + +		let revealElement = this.Reveal.getRevealElement(); + +		if( 'onpointerdown' in window ) { +			// Use W3C pointer events +			revealElement.addEventListener( 'pointerdown', this.onPointerDown, false ); +			revealElement.addEventListener( 'pointermove', this.onPointerMove, false ); +			revealElement.addEventListener( 'pointerup', this.onPointerUp, false ); +		} +		else if( window.navigator.msPointerEnabled ) { +			// IE 10 uses prefixed version of pointer events +			revealElement.addEventListener( 'MSPointerDown', this.onPointerDown, false ); +			revealElement.addEventListener( 'MSPointerMove', this.onPointerMove, false ); +			revealElement.addEventListener( 'MSPointerUp', this.onPointerUp, false ); +		} +		else { +			// Fall back to touch events +			revealElement.addEventListener( 'touchstart', this.onTouchStart, false ); +			revealElement.addEventListener( 'touchmove', this.onTouchMove, false ); +			revealElement.addEventListener( 'touchend', this.onTouchEnd, false ); +		} + +	} + +	/** +	 * +	 */ +	unbind() { + +		let revealElement = this.Reveal.getRevealElement(); + +		revealElement.removeEventListener( 'pointerdown', this.onPointerDown, false ); +		revealElement.removeEventListener( 'pointermove', this.onPointerMove, false ); +		revealElement.removeEventListener( 'pointerup', this.onPointerUp, false ); + +		revealElement.removeEventListener( 'MSPointerDown', this.onPointerDown, false ); +		revealElement.removeEventListener( 'MSPointerMove', this.onPointerMove, false ); +		revealElement.removeEventListener( 'MSPointerUp', this.onPointerUp, false ); + +		revealElement.removeEventListener( 'touchstart', this.onTouchStart, false ); +		revealElement.removeEventListener( 'touchmove', this.onTouchMove, false ); +		revealElement.removeEventListener( 'touchend', this.onTouchEnd, false ); + +	} + +	/** +	 * Checks if the target element prevents the triggering of +	 * swipe navigation. +	 */ +	isSwipePrevented( target ) { + +		// Prevent accidental swipes when scrubbing timelines +		if( matches( target, 'video, audio' ) ) return true; + +		while( target && typeof target.hasAttribute === 'function' ) { +			if( target.hasAttribute( 'data-prevent-swipe' ) ) return true; +			target = target.parentNode; +		} + +		return false; + +	} + +	/** +	 * Handler for the 'touchstart' event, enables support for +	 * swipe and pinch gestures. +	 * +	 * @param {object} event +	 */ +	onTouchStart( event ) { + +		if( this.isSwipePrevented( event.target ) ) return true; + +		this.touchStartX = event.touches[0].clientX; +		this.touchStartY = event.touches[0].clientY; +		this.touchStartCount = event.touches.length; + +	} + +	/** +	 * Handler for the 'touchmove' event. +	 * +	 * @param {object} event +	 */ +	onTouchMove( event ) { + +		if( this.isSwipePrevented( event.target ) ) return true; + +		let config = this.Reveal.getConfig(); + +		// Each touch should only trigger one action +		if( !this.touchCaptured ) { +			this.Reveal.onUserInput( event ); + +			let currentX = event.touches[0].clientX; +			let currentY = event.touches[0].clientY; + +			// There was only one touch point, look for a swipe +			if( event.touches.length === 1 && this.touchStartCount !== 2 ) { + +				let availableRoutes = this.Reveal.availableRoutes({ includeFragments: true }); + +				let deltaX = currentX - this.touchStartX, +					deltaY = currentY - this.touchStartY; + +				if( deltaX > SWIPE_THRESHOLD && Math.abs( deltaX ) > Math.abs( deltaY ) ) { +					this.touchCaptured = true; +					if( config.navigationMode === 'linear' ) { +						if( config.rtl ) { +							this.Reveal.next(); +						} +						else { +							this.Reveal.prev(); +						} +					} +					else { +						this.Reveal.left(); +					} +				} +				else if( deltaX < -SWIPE_THRESHOLD && Math.abs( deltaX ) > Math.abs( deltaY ) ) { +					this.touchCaptured = true; +					if( config.navigationMode === 'linear' ) { +						if( config.rtl ) { +							this.Reveal.prev(); +						} +						else { +							this.Reveal.next(); +						} +					} +					else { +						this.Reveal.right(); +					} +				} +				else if( deltaY > SWIPE_THRESHOLD && availableRoutes.up ) { +					this.touchCaptured = true; +					if( config.navigationMode === 'linear' ) { +						this.Reveal.prev(); +					} +					else { +						this.Reveal.up(); +					} +				} +				else if( deltaY < -SWIPE_THRESHOLD && availableRoutes.down ) { +					this.touchCaptured = true; +					if( config.navigationMode === 'linear' ) { +						this.Reveal.next(); +					} +					else { +						this.Reveal.down(); +					} +				} + +				// If we're embedded, only block touch events if they have +				// triggered an action +				if( config.embedded ) { +					if( this.touchCaptured || this.Reveal.isVerticalSlide() ) { +						event.preventDefault(); +					} +				} +				// Not embedded? Block them all to avoid needless tossing +				// around of the viewport in iOS +				else { +					event.preventDefault(); +				} + +			} +		} +		// There's a bug with swiping on some Android devices unless +		// the default action is always prevented +		else if( isAndroid ) { +			event.preventDefault(); +		} + +	} + +	/** +	 * Handler for the 'touchend' event. +	 * +	 * @param {object} event +	 */ +	onTouchEnd( event ) { + +		this.touchCaptured = false; + +	} + +	/** +	 * Convert pointer down to touch start. +	 * +	 * @param {object} event +	 */ +	onPointerDown( event ) { + +		if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) { +			event.touches = [{ clientX: event.clientX, clientY: event.clientY }]; +			this.onTouchStart( event ); +		} + +	} + +	/** +	 * Convert pointer move to touch move. +	 * +	 * @param {object} event +	 */ +	onPointerMove( event ) { + +		if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" )  { +			event.touches = [{ clientX: event.clientX, clientY: event.clientY }]; +			this.onTouchMove( event ); +		} + +	} + +	/** +	 * Convert pointer up to touch end. +	 * +	 * @param {object} event +	 */ +	onPointerUp( event ) { + +		if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" )  { +			event.touches = [{ clientX: event.clientX, clientY: event.clientY }]; +			this.onTouchEnd( event ); +		} + +	} + +}
\ No newline at end of file | 
