Skip to content
Snippets Groups Projects
reveal.js 119 KiB
Newer Older
  • Learn to ignore specific revisions
  • 	/**
    	 * Checks if this presentation is running inside of the
    	 * speaker notes window.
    	 */
    	function isSpeakerNotes() {
    
    		return !!window.location.search.match( /receiver/gi );
    
    	}
    
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	/**
    	 * Reads the current URL (hash) and navigates accordingly.
    	 */
    	function readURL() {
    
    		var hash = window.location.hash;
    
    		// Attempt to parse the hash as either an index or name
    		var bits = hash.slice( 2 ).split( '/' ),
    			name = hash.replace( /#|\//gi, '' );
    
    
    		// If the first bit is invalid and there is a name we can
    
    		// assume that this is a named link
    
    		if( isNaN( parseInt( bits[0], 10 ) ) && name.length ) {
    
    			// Ensure the named link is a valid HTML ID attribute
    			if( /^[a-zA-Z][\w:.-]*$/.test( name ) ) {
    				// Find the slide with the specified ID
    
    				element = document.getElementById( name );
    
    				// Find the position of the named slide and navigate to it
    
    				var indices = Reveal.getIndices( element );
    				slide( indices.h, indices.v );
    
    			}
    			// If the slide doesn't exist, navigate to the current slide
    			else {
    
    			}
    		}
    		else {
    			// Read the index components of the hash
    
    			var h = parseInt( bits[0], 10 ) || 0,
    				v = parseInt( bits[1], 10 ) || 0;
    
    			if( h !== indexh || v !== indexv ) {
    				slide( h, v );
    			}
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	}
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	/**
    	 * Updates the page URL (hash) to reflect the current
    
    	 * @param {Number} delay The time in ms to wait before
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	 */
    
    	function writeURL( delay ) {
    
    			// Make sure there's never more than one timeout running
    			clearTimeout( writeURLTimeout );
    
    			// If a delay is specified, timeout this call
    			if( typeof delay === 'number' ) {
    				writeURLTimeout = setTimeout( writeURL, delay );
    
    			else if( currentSlide ) {
    
    				// Attempt to create a named link based on the slide's ID
    				var id = currentSlide.getAttribute( 'id' );
    				if( id ) {
    					id = id.toLowerCase();
    					id = id.replace( /[^a-zA-Z0-9\-\_\:\.]/g, '' );
    				}
    
    
    				// If the current slide has an ID, use that as a named link
    
    				if( typeof id === 'string' && id.length ) {
    
    				}
    				// Otherwise use the /h/v index
    				else {
    					if( indexh > 0 || indexv > 0 ) url += indexh;
    					if( indexv > 0 ) url += '/' + indexv;
    				}
    
    				window.location.hash = url;
    			}
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	}
    
    	 * Retrieves the h/v location of the current, or specified,
    
    	 *
    	 * @param {HTMLElement} slide If specified, the returned
    	 * index will be for this slide rather than the currently
    
    	 * @return {Object} { h: <int>, v: <int>, f: <int> }
    
    		// By default, return the current indices
    		var h = indexh,
    
    
    		// If a slide is specified, return the indices of that slide
    		if( slide ) {
    
    			var isVertical = isVerticalSlide( slide );
    
    			var slideh = isVertical ? slide.parentNode : slide;
    
    			// Select all horizontal slides
    
    			var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
    
    
    			// Now that we know which the horizontal slide is, get its index
    			h = Math.max( horizontalSlides.indexOf( slideh ), 0 );
    
    
    			// Assume we're not vertical
    
    			// If this is a vertical slide, grab the vertical index
    			if( isVertical ) {
    
    				v = Math.max( toArray( slide.parentNode.querySelectorAll( 'section' ) ).indexOf( slide ), 0 );
    
    			var hasFragments = currentSlide.querySelectorAll( '.fragment' ).length > 0;
    			if( hasFragments ) {
    
    				var currentFragment = currentSlide.querySelector( '.current-fragment' );
    				if( currentFragment && currentFragment.hasAttribute( 'data-fragment-index' ) ) {
    					f = parseInt( currentFragment.getAttribute( 'data-fragment-index' ), 10 );
    				}
    				else {
    					f = currentSlide.querySelectorAll( '.fragment.visible' ).length - 1;
    				}
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	/**
    	 * Retrieves the total number of slides in this presentation.
    	 */
    	function getTotalSlides() {
    
    
    		return dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ).length;
    
    	/**
    	 * Returns the slide element matching the specified index.
    	 */
    
    	function getSlide( x, y ) {
    
    
    		var horizontalSlide = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR )[ x ];
    
    		var verticalSlides = horizontalSlide && horizontalSlide.querySelectorAll( 'section' );
    
    
    		if( verticalSlides && verticalSlides.length && typeof y === 'number' ) {
    
    			return verticalSlides ? verticalSlides[ y ] : undefined;
    		}
    
    		return horizontalSlide;
    
    	}
    
    	/**
    	 * Returns the background element for the given slide.
    	 * All slides, even the ones with no background properties
    
    	 * defined, have a background element so as long as the
    	 * index is valid an element will be returned.
    
    	 */
    	function getSlideBackground( x, y ) {
    
    
    		// When printing to PDF the slide backgrounds are nested
    		// inside of the slides
    		if( isPrintingPDF() ) {
    			var slide = getSlide( x, y );
    
    			if( slide ) {
    				var background = slide.querySelector( '.slide-background' );
    				if( background && background.parentNode === slide ) {
    					return background;
    				}
    			}
    
    			return undefined;
    
    		var horizontalBackground = dom.wrapper.querySelectorAll( '.backgrounds>.slide-background' )[ x ];
    
    		var verticalBackgrounds = horizontalBackground && horizontalBackground.querySelectorAll( '.slide-background' );
    
    
    		if( verticalBackgrounds && verticalBackgrounds.length && typeof y === 'number' ) {
    
    			return verticalBackgrounds ? verticalBackgrounds[ y ] : undefined;
    		}
    
    		return horizontalBackground;
    
    	}
    
    
    	/**
    	 * Retrieves the current state of the presentation as
    	 * an object. This state can then be restored at any
    	 * time.
    	 */
    	function getState() {
    
    		var indices = getIndices();
    
    		return {
    			indexh: indices.h,
    			indexv: indices.v,
    			indexf: indices.f,
    			paused: isPaused(),
    			overview: isOverview()
    		};
    
    	}
    
    	/**
    	 * Restores the presentation to the given state.
    	 *
    	 * @param {Object} state As generated by getState()
    	 */
    	function setState( state ) {
    
    		if( typeof state === 'object' ) {
    
    			slide( deserialize( state.indexh ), deserialize( state.indexv ), deserialize( state.indexf ) );
    
    
    			var pausedFlag = deserialize( state.paused ),
    				overviewFlag = deserialize( state.overview );
    
    			if( typeof pausedFlag === 'boolean' && pausedFlag !== isPaused() ) {
    				togglePause( pausedFlag );
    			}
    
    			if( typeof overviewFlag === 'boolean' && overviewFlag !== isOverview() ) {
    				toggleOverview( overviewFlag );
    			}
    
    	/**
    	 * Return a sorted fragments list, ordered by an increasing
    	 * "data-fragment-index" attribute.
    	 *
    	 * Fragments will be revealed in the order that they are returned by
    	 * this function, so you can use the index attributes to control the
    	 * order of fragment appearance.
    	 *
    	 * To maintain a sensible default fragment order, fragments are presumed
    	 * to be passed in document order. This function adds a "fragment-index"
    	 * attribute to each node if such an attribute is not already present,
    	 * and sets that attribute to an integer value which is the position of
    	 * the fragment within the fragments list.
    	 */
    	function sortFragments( fragments ) {
    
    		fragments = toArray( fragments );
    
    		var ordered = [],
    			unordered = [],
    			sorted = [];
    
    		// Group ordered and unordered elements
    		fragments.forEach( function( fragment, i ) {
    			if( fragment.hasAttribute( 'data-fragment-index' ) ) {
    				var index = parseInt( fragment.getAttribute( 'data-fragment-index' ), 10 );
    
    				if( !ordered[index] ) {
    					ordered[index] = [];
    				}
    
    				ordered[index].push( fragment );
    			}
    			else {
    				unordered.push( [ fragment ] );
    			}
    		} );
    
    		// Append fragments without explicit indices in their
    		// DOM order
    		ordered = ordered.concat( unordered );
    
    		// Manually count the index up per group to ensure there
    		// are no gaps
    		var index = 0;
    
    		// Push all fragments in their sorted order to an array,
    		// this flattens the groups
    		ordered.forEach( function( group ) {
    			group.forEach( function( fragment ) {
    				sorted.push( fragment );
    				fragment.setAttribute( 'data-fragment-index', index );
    			} );
    
    			index ++;
    		} );
    
    		return sorted;
    
    	}
    
    
    	 * Navigate to the specified slide fragment.
    
    	 * @param {Number} index The index of the fragment that
    
    	 * should be shown, -1 means all are invisible
    
    	 * @param {Number} offset Integer offset to apply to the
    	 * fragment index
    	 *
    	 * @return {Boolean} true if a change was made in any
    	 * fragments visibility as part of this call
    
    	function navigateFragment( index, offset ) {
    
    		if( currentSlide && config.fragments ) {
    
    			var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment' ) );
    
    			if( fragments.length ) {
    
    				// If no index is specified, find the current
    
    				if( typeof index !== 'number' ) {
    					var lastVisibleFragment = sortFragments( currentSlide.querySelectorAll( '.fragment.visible' ) ).pop();
    
    					if( lastVisibleFragment ) {
    
    						index = parseInt( lastVisibleFragment.getAttribute( 'data-fragment-index' ) || 0, 10 );
    
    						index = -1;
    
    				// If an offset is specified, apply it to the index
    
    				if( typeof offset === 'number' ) {
    					index += offset;
    				}
    
    				var fragmentsShown = [],
    					fragmentsHidden = [];
    
    				toArray( fragments ).forEach( function( element, i ) {
    
    
    					if( element.hasAttribute( 'data-fragment-index' ) ) {
    						i = parseInt( element.getAttribute( 'data-fragment-index' ), 10 );
    
    					// Visible fragments
    
    						if( !element.classList.contains( 'visible' ) ) fragmentsShown.push( element );
    						element.classList.add( 'visible' );
    						element.classList.remove( 'current-fragment' );
    
    
    						// Announce the fragments one by one to the Screen Reader
    
    						dom.statusDiv.textContent = element.textContent;
    
    
    						if( i === index ) {
    							element.classList.add( 'current-fragment' );
    						}
    					}
    
    					// Hidden fragments
    					else {
    						if( element.classList.contains( 'visible' ) ) fragmentsHidden.push( element );
    						element.classList.remove( 'visible' );
    						element.classList.remove( 'current-fragment' );
    					}
    
    				if( fragmentsHidden.length ) {
    
    					dispatchEvent( 'fragmenthidden', { fragment: fragmentsHidden[0], fragments: fragmentsHidden } );
    				}
    
    
    				if( fragmentsShown.length ) {
    
    					dispatchEvent( 'fragmentshown', { fragment: fragmentsShown[0], fragments: fragmentsShown } );
    				}
    
    
    				return !!( fragmentsShown.length || fragmentsHidden.length );
    
    
    	 * Navigate to the next slide fragment.
    
    	 * @return {Boolean} true if there was a next fragment,
    
    	function nextFragment() {
    
    		return navigateFragment( null, 1 );
    
    	/**
    	 * Navigate to the previous slide fragment.
    	 *
    	 * @return {Boolean} true if there was a previous fragment,
    	 * false otherwise
    	 */
    	function previousFragment() {
    
    		return navigateFragment( null, -1 );
    
    	/**
    	 * Cues a new automated slide if enabled in the config.
    	 */
    
    			var currentFragment = currentSlide.querySelector( '.current-fragment' );
    
    			var fragmentAutoSlide = currentFragment ? currentFragment.getAttribute( 'data-autoslide' ) : null;
    
    			var parentAutoSlide = currentSlide.parentNode ? currentSlide.parentNode.getAttribute( 'data-autoslide' ) : null;
    			var slideAutoSlide = currentSlide.getAttribute( 'data-autoslide' );
    
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    			// Pick value in the following priority order:
    
    			// 1. Current fragment's data-autoslide
    			// 2. Current slide's data-autoslide
    			// 3. Parent slide's data-autoslide
    			// 4. Global autoSlide setting
    			if( fragmentAutoSlide ) {
    				autoSlide = parseInt( fragmentAutoSlide, 10 );
    			}
    			else if( slideAutoSlide ) {
    
    				autoSlide = parseInt( slideAutoSlide, 10 );
    			}
    
    			else if( parentAutoSlide ) {
    				autoSlide = parseInt( parentAutoSlide, 10 );
    			}
    
    			// If there are media elements with data-autoplay,
    			// automatically set the autoSlide duration to the
    			// length of that media
    			toArray( currentSlide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
    				if( el.hasAttribute( 'data-autoplay' ) ) {
    					if( autoSlide && el.duration * 1000 > autoSlide ) {
    						autoSlide = ( el.duration * 1000 ) + 1000;
    					}
    				}
    			} );
    
    
    			// Cue the next auto-slide if:
    			// - There is an autoSlide value
    			// - Auto-sliding isn't paused by the user
    			// - The presentation isn't paused
    			// - The overview isn't active
    			// - The presentation isn't over
    
    			if( autoSlide && !autoSlidePaused && !isPaused() && !isOverview() && ( !Reveal.isLastSlide() || availableFragments().next || config.loop === true ) ) {
    
    				autoSlideTimeout = setTimeout( navigateNext, autoSlide );
    				autoSlideStartTime = Date.now();
    			}
    
    			if( autoSlidePlayer ) {
    				autoSlidePlayer.setPlaying( autoSlideTimeout !== -1 );
    			}
    
    	/**
    	 * Cancels any ongoing request to auto-slide.
    	 */
    	function cancelAutoSlide() {
    
    		clearTimeout( autoSlideTimeout );
    
    	function pauseAutoSlide() {
    
    
    		if( autoSlide && !autoSlidePaused ) {
    			autoSlidePaused = true;
    			dispatchEvent( 'autoslidepaused' );
    			clearTimeout( autoSlideTimeout );
    
    			if( autoSlidePlayer ) {
    				autoSlidePlayer.setPlaying( false );
    			}
    
    		if( autoSlide && autoSlidePaused ) {
    			autoSlidePaused = false;
    			dispatchEvent( 'autoslideresumed' );
    			cueAutoSlide();
    		}
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	function navigateLeft() {
    
    		// Reverse for RTL
    		if( config.rtl ) {
    			if( ( isOverview() || nextFragment() === false ) && availableRoutes().left ) {
    				slide( indexh + 1 );
    			}
    		}
    		// Normal navigation
    		else if( ( isOverview() || previousFragment() === false ) && availableRoutes().left ) {
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	}
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	function navigateRight() {
    
    		// Reverse for RTL
    		if( config.rtl ) {
    			if( ( isOverview() || previousFragment() === false ) && availableRoutes().right ) {
    				slide( indexh - 1 );
    			}
    		}
    		// Normal navigation
    		else if( ( isOverview() || nextFragment() === false ) && availableRoutes().right ) {
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	}
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	function navigateUp() {
    
    		// Prioritize hiding fragments
    
    		if( ( isOverview() || previousFragment() === false ) && availableRoutes().up ) {
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	}
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	function navigateDown() {
    
    		// Prioritize revealing fragments
    
    		if( ( isOverview() || nextFragment() === false ) && availableRoutes().down ) {
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	}
    
    
    	/**
    	 * Navigates backwards, prioritized in the following order:
    	 * 1) Previous fragment
    	 * 2) Previous vertical slide
    	 * 3) Previous horizontal slide
    	 */
    	function navigatePrev() {
    
    		// Prioritize revealing fragments
    		if( previousFragment() === false ) {
    			if( availableRoutes().up ) {
    				navigateUp();
    			}
    			else {
    
    				// Fetch the previous horizontal slide, if there is one
    				var previousSlide;
    
    
    					previousSlide = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.future' ) ).pop();
    				}
    				else {
    					previousSlide = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.past' ) ).pop();
    				}
    
    				if( previousSlide ) {
    					var v = ( previousSlide.querySelectorAll( 'section' ).length - 1 ) || undefined;
    					var h = indexh - 1;
    					slide( h, v );
    
    	 * The reverse of #navigatePrev().
    
    		// Prioritize revealing fragments
    		if( nextFragment() === false ) {
    
    			if( availableRoutes().down ) {
    				navigateDown();
    			}
    			else if( config.rtl ) {
    				navigateLeft();
    			}
    			else {
    				navigateRight();
    
    		// If auto-sliding is enabled we need to cue up
    
    	// --------------------------------------------------------------------//
    	// ----------------------------- EVENTS -------------------------------//
    	// --------------------------------------------------------------------//
    
    
    	/**
    	 * Called by all event handlers that are based on user
    	 * input.
    	 */
    	function onUserInput( event ) {
    
    		if( config.autoSlideStoppable ) {
    
    	/**
    	 * Handler for the document level 'keypress' event.
    	 */
    	function onDocumentKeyPress( event ) {
    
    		// Check if the pressed key is question mark
    
    		if( event.shiftKey && event.charCode === 63 ) {
    
    			if( dom.overlay ) {
    				closeOverlay();
    			}
    			else {
    				showHelp( true );
    			}
    
    	/**
    	 * Handler for the document level 'keydown' event.
    	 */
    	function onDocumentKeyDown( event ) {
    
    		// If there's a condition specified and it returns false,
    		// ignore this event
    		if( typeof config.keyboardCondition === 'function' && config.keyboardCondition() === false ) {
    			return true;
    		}
    
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    		// Remember if auto-sliding was paused so we can toggle it
    		var autoSlideWasPaused = autoSlidePaused;
    
    
    		// Check if there's a focused element that could be using
    
    		var activeElementIsCE = document.activeElement && document.activeElement.contentEditable !== 'inherit';
    		var activeElementIsInput = document.activeElement && document.activeElement.tagName && /input|textarea/i.test( document.activeElement.tagName );
    
    		// Disregard the event if there's a focused element or a
    
    		// keyboard modifier key is present
    
    		if( activeElementIsCE || activeElementIsInput || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
    
    		// While paused only allow "unpausing" keyboard events (b and .)
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    		if( isPaused() && [66,190,191].indexOf( event.keyCode ) === -1 ) {
    
    		var triggered = false;
    
    		// 1. User defined key bindings
    		if( typeof config.keyboard === 'object' ) {
    
    			for( var key in config.keyboard ) {
    
    				// Check if this binding matches the pressed key
    				if( parseInt( key, 10 ) === event.keyCode ) {
    
    					var value = config.keyboard[ key ];
    
    
    					// Callback function
    
    					if( typeof value === 'function' ) {
    						value.apply( null, [ event ] );
    					}
    					// String shortcuts to reveal.js API
    					else if( typeof value === 'string' && typeof Reveal[ value ] === 'function' ) {
    						Reveal[ value ].call();
    					}
    
    					triggered = true;
    
    				}
    
    			}
    
    		}
    
    		// 2. System defined key bindings
    		if( triggered === false ) {
    
    			// Assume true and try to prove false
    			triggered = true;
    
    			switch( event.keyCode ) {
    				// p, page up
    				case 80: case 33: navigatePrev(); break;
    				// n, page down
    				case 78: case 34: navigateNext(); break;
    				// h, left
    				case 72: case 37: navigateLeft(); break;
    				// l, right
    				case 76: case 39: navigateRight(); break;
    				// k, up
    				case 75: case 38: navigateUp(); break;
    				// j, down
    				case 74: case 40: navigateDown(); break;
    				// home
    				case 36: slide( 0 ); break;
    				// end
    				case 35: slide( Number.MAX_VALUE ); break;
    				// space
    				case 32: isOverview() ? deactivateOverview() : event.shiftKey ? navigatePrev() : navigateNext(); break;
    				// return
    				case 13: isOverview() ? deactivateOverview() : triggered = false; break;
    
    				// two-spot, semicolon, b, period, Logitech presenter tools "black screen" button
    
    Armand Abric's avatar
    Armand Abric committed
    				case 58: case 59: case 66: case 190: case 191: togglePause(); break;
    
    				// f
    				case 70: enterFullscreen(); break;
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    				case 65: if ( config.autoSlideStoppable ) toggleAutoSlide( autoSlideWasPaused ); break;
    
    				default:
    					triggered = false;
    			}
    
    
    		// If the input resulted in a triggered action we should prevent
    
    		// the browsers default behavior
    		if( triggered ) {
    
    			event.preventDefault && event.preventDefault();
    
    		else if ( ( event.keyCode === 27 || event.keyCode === 79 ) && features.transforms3d ) {
    
    			if( dom.overlay ) {
    				closeOverlay();
    
    			event.preventDefault && event.preventDefault();
    
    		// If auto-sliding is enabled we need to cue up
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	 * Handler for the 'touchstart' event, enables support for
    
    	 * swipe and pinch gestures.
    
    	function onTouchStart( event ) {
    
    		touch.startX = event.touches[0].clientX;
    		touch.startY = event.touches[0].clientY;
    		touch.startCount = event.touches.length;
    
    
    		// If there's two touches we need to memorize the distance
    
    		// between those two points to detect pinching
    		if( event.touches.length === 2 && config.overview ) {
    			touch.startSpan = distanceBetween( {
    				x: event.touches[1].clientX,
    				y: event.touches[1].clientY
    			}, {
    				x: touch.startX,
    				y: touch.startY
    			} );
    		}
    
    	 * Handler for the 'touchmove' event.
    
    	function onTouchMove( event ) {
    
    		// Each touch should only trigger one action
    
    		if( !touch.captured ) {
    
    			var currentX = event.touches[0].clientX;
    			var currentY = event.touches[0].clientY;
    
    
    			// If the touch started with two points and still has
    
    			// two active touches; test for the pinch gesture
    			if( event.touches.length === 2 && touch.startCount === 2 && config.overview ) {
    
    				// The current distance in pixels between the two touch points
    				var currentSpan = distanceBetween( {
    					x: event.touches[1].clientX,
    					y: event.touches[1].clientY
    				}, {
    					x: touch.startX,
    					y: touch.startY
    				} );
    
    
    				// If the span is larger than the desire amount we've got
    
    				// ourselves a pinch
    				if( Math.abs( touch.startSpan - currentSpan ) > touch.threshold ) {
    
    					touch.captured = true;
    
    
    					if( currentSpan < touch.startSpan ) {
    						activateOverview();
    					}
    					else {
    						deactivateOverview();
    					}
    				}
    
    				event.preventDefault();
    
    			}
    			// There was only one touch point, look for a swipe
    			else if( event.touches.length === 1 && touch.startCount !== 2 ) {
    
    				var deltaX = currentX - touch.startX,
    					deltaY = currentY - touch.startY;
    
    				if( deltaX > touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
    
    					touch.captured = true;
    
    				else if( deltaX < -touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
    
    					touch.captured = true;
    
    					touch.captured = true;
    
    					touch.captured = true;
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    				// If we're embedded, only block touch events if they have
    				// triggered an action
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    					if( touch.captured || isVerticalSlide( currentSlide ) ) {
    						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( navigator.userAgent.match( /android/gi ) ) {
    			event.preventDefault();
    		}
    
    	 * Handler for the 'touchend' event.
    
    	function onTouchEnd( event ) {
    
    		touch.captured = false;
    
    	/**
    	 * Convert pointer down to touch start.
    	 */
    	function onPointerDown( event ) {
    
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    		if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
    
    			event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    			onTouchStart( event );
    
    		}
    
    	}
    
    	/**
    	 * Convert pointer move to touch move.
    	 */
    	function onPointerMove( event ) {
    
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    		if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" )  {
    
    			event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    			onTouchMove( event );
    
    		}
    
    	}
    
    	/**
    	 * Convert pointer up to touch end.
    	 */
    	function onPointerUp( event ) {
    
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    		if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" )  {
    
    			event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    			onTouchEnd( event );
    
    	 * Handles mouse wheel scrolling, throttled to avoid skipping
    
    	function onDocumentMouseScroll( event ) {
    
    
    		if( Date.now() - lastMouseWheelStep > 600 ) {
    
    			lastMouseWheelStep = Date.now();
    
    
    			var delta = event.detail || -event.wheelDelta;
    			if( delta > 0 ) {
    				navigateNext();
    			}
    			else {
    				navigatePrev();
    			}
    
    	 * Clicking on the progress bar results in a navigation to the
    
    	 * closest approximate horizontal slide using this equation:
    	 *
    	 * ( clickX / presentationWidth ) * numberOfSlides
    	 */
    
    		var slidesTotal = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
    
    		var slideIndex = Math.floor( ( event.clientX / dom.wrapper.offsetWidth ) * slidesTotal );
    
    		slide( slideIndex );
    
    	 * Event handler for navigation control buttons.
    
    	function onNavigateLeftClicked( event ) { event.preventDefault(); onUserInput(); navigateLeft(); }
    	function onNavigateRightClicked( event ) { event.preventDefault(); onUserInput(); navigateRight(); }
    	function onNavigateUpClicked( event ) { event.preventDefault(); onUserInput(); navigateUp(); }
    	function onNavigateDownClicked( event ) { event.preventDefault(); onUserInput(); navigateDown(); }
    	function onNavigatePrevClicked( event ) { event.preventDefault(); onUserInput(); navigatePrev(); }
    	function onNavigateNextClicked( event ) { event.preventDefault(); onUserInput(); navigateNext(); }
    
    	/**
    	 * Handler for the window level 'hashchange' event.
    	 */
    	function onWindowHashChange( event ) {