Skip to content
Snippets Groups Projects
reveal.js 47.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • 			// Since there are no slides we can't be anywhere beyond the
    
    	 * Updates the progress bar to reflect the current slide.
    	 */
    	function updateProgress() {
    		// Update progress if enabled
    		if( config.progress && dom.progress ) {
    
    			var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
    
    			// The number of past and total slides
    			var totalCount = document.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ).length;
    			var pastCount = 0;
    
    			// Step through all slides and count the past ones
    			mainLoop: for( var i = 0; i < horizontalSlides.length; i++ ) {
    
    				var horizontalSlide = horizontalSlides[i];
    				var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
    
    				for( var j = 0; j < verticalSlides.length; j++ ) {
    
    					// Stop as soon as we arrive at the present
    
    					if( verticalSlides[j].classList.contains( 'present' ) ) {
    						break mainLoop;
    					}
    
    
    				}
    
    				// Stop as soon as we arrive at the present
    
    				if( horizontalSlide.classList.contains( 'present' ) ) {
    					break;
    				}
    
    
    				// Don't count the wrapping section for vertical slides
    
    				if( horizontalSlide.classList.contains( 'stack' ) === false ) {
    					pastCount++;
    				}
    
    
    			}
    
    			dom.progressbar.style.width = ( pastCount / ( totalCount - 1 ) ) * window.innerWidth + 'px';
    
    		}
    	}
    
    	/**
    	 * Updates the state of all control/navigation arrows.
    
    			var routes = availableRoutes();
    
    			// Remove the 'enabled' class from all directions
    
    			dom.controlsLeft.concat( dom.controlsRight )
    							.concat( dom.controlsUp )
    							.concat( dom.controlsDown )
    							.concat( dom.controlsPrev )
    							.concat( dom.controlsNext ).forEach( function( node ) {
    
    				node.classList.remove( 'enabled' );
    			} );
    
    			// Add the 'enabled' class to the available routes
    
    			if( routes.left ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'enabled' );	} );
    			if( routes.right ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'enabled' ); } );
    			if( routes.up ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'enabled' );	} );
    			if( routes.down ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'enabled' ); } );
    
    			if( routes.left || routes.up ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'enabled' ); } );
    			if( routes.right || routes.down ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'enabled' ); } );
    
    danielmitd's avatar
    danielmitd committed
    		}
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	 * Determine what available routes there are for navigation.
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	 * @return {Object} containing four booleans: left/right/up/down
    
    		var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
    			verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
    
    
    		return {
    			left: indexh > 0,
    			right: indexh < horizontalSlides.length - 1,
    			up: indexv > 0,
    			down: indexv < verticalSlides.length - 1
    		};
    	}
    
    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 ) {
    
    			// Find the slide with the specified name
    
    			var element = document.querySelector( '#' + 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;
    
    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 
    	 * writing the hash
    
    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 );
    
    				// If the current slide has an ID, use that as a named link
    				if( currentSlide && typeof currentSlide.getAttribute( 'id' ) === 'string' ) {
    					url = '/' + currentSlide.getAttribute( 'id' );
    				}
    				// 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> }
    	 */
    	function getIndices( slide ) {
    		// By default, return the current indices
    		var h = indexh,
    			v = indexv;
    
    		// If a slide is specified, return the indices of that slide
    		if( slide ) {
    			var isVertical = !!slide.parentNode.nodeName.match( /section/gi );
    			var slideh = isVertical ? slide.parentNode : slide;
    
    			// Select all horizontal slides
    
    			var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
    
    
    			// Now that we know which the horizontal slide is, get its index
    			h = Math.max( horizontalSlides.indexOf( slideh ), 0 );
    
    			// If this is a vertical slide, grab the vertical index
    			if( isVertical ) {
    
    				v = Math.max( toArray( slide.parentNode.querySelectorAll( 'section' ) ).indexOf( slide ), 0 );
    
    	 * Navigate to the next slide fragment.
    
    	 * @return {Boolean} true if there was a next fragment,
    	 * false otherwise
    	 */
    
    	function nextFragment() {
    		// Vertical slides:
    
    		if( document.querySelector( VERTICAL_SLIDES_SELECTOR + '.present' ) ) {
    			var verticalFragments = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR + '.present .fragment:not(.visible)' );
    			if( verticalFragments.length ) {
    
    				verticalFragments[0].classList.add( 'visible' );
    
    
    				// Notify subscribers of the change
    				dispatchEvent( 'fragmentshown', { fragment: verticalFragments[0] } );
    
    		// Horizontal slides:
    
    		else {
    			var horizontalFragments = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.present .fragment:not(.visible)' );
    			if( horizontalFragments.length ) {
    
    				horizontalFragments[0].classList.add( 'visible' );
    
    
    				// Notify subscribers of the change
    				dispatchEvent( 'fragmentshown', { fragment: horizontalFragments[0] } );
    
    		}
    
    		return false;
    	}
    
    	/**
    	 * Navigate to the previous slide fragment.
    
    	 * @return {Boolean} true if there was a previous fragment,
    	 * false otherwise
    	 */
    	function previousFragment() {
    
    		// Vertical slides:
    
    		if( document.querySelector( VERTICAL_SLIDES_SELECTOR + '.present' ) ) {
    			var verticalFragments = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR + '.present .fragment.visible' );
    			if( verticalFragments.length ) {
    
    				verticalFragments[ verticalFragments.length - 1 ].classList.remove( 'visible' );
    
    				dispatchEvent( 'fragmenthidden', { fragment: verticalFragments[ verticalFragments.length - 1 ] } );
    
    		// Horizontal slides:
    
    		else {
    			var horizontalFragments = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.present .fragment.visible' );
    			if( horizontalFragments.length ) {
    
    				horizontalFragments[ horizontalFragments.length - 1 ].classList.remove( 'visible' );
    
    				dispatchEvent( 'fragmenthidden', { fragment: horizontalFragments[ horizontalFragments.length - 1 ] } );
    
    	/**
    	 * Cues a new automated slide if enabled in the config.
    	 */
    
    	function cueAutoSlide() {
    		clearTimeout( autoSlideTimeout );
    
    		// Cue the next auto-slide if enabled
    
    		if( autoSlide ) {
    			autoSlideTimeout = setTimeout( navigateNext, autoSlide );
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	function navigateLeft() {
    
    		// Prioritize hiding fragments
    
    		if( availableRoutes().left && isOverviewActive() || previousFragment() === false ) {
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	}
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	function navigateRight() {
    
    		// Prioritize revealing fragments
    
    		if( availableRoutes().right && isOverviewActive() || nextFragment() === false ) {
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	}
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	function navigateUp() {
    
    		// Prioritize hiding fragments
    
    		if( availableRoutes().up && isOverviewActive() || previousFragment() === false ) {
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	}
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	function navigateDown() {
    
    		// Prioritize revealing fragments
    
    		if( availableRoutes().down && isOverviewActive() || nextFragment() === false ) {
    
    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 = document.querySelector( HORIZONTAL_SLIDES_SELECTOR + '.past:nth-child(' + indexh + ')' );
    
    					indexv = ( previousSlide.querySelectorAll( 'section' ).length + 1 ) || undefined;
    
    					indexh --;
    					slide();
    				}
    			}
    		}
    	}
    
    	/**
    	 * Same as #navigatePrev() but navigates forwards.
    	 */
    	function navigateNext() {
    		// Prioritize revealing fragments
    		if( nextFragment() === false ) {
    			availableRoutes().down ? navigateDown() : navigateRight();
    		}
    
    		// If auto-sliding is enabled we need to cue up
    
    
    	// --------------------------------------------------------------------//
    	// ----------------------------- EVENTS -------------------------------//
    	// --------------------------------------------------------------------//
    
    
    	/**
    	 * Handler for the document level 'keydown' event.
    
    	 * @param {Object} event
    	 */
    	function onDocumentKeyDown( event ) {
    
    		// Check if there's a focused element that could be using 
    		// the keyboard
    		var activeElement = document.activeElement;
    
    		var hasFocus = !!( document.activeElement && ( document.activeElement.type || document.activeElement.href || document.activeElement.contentEditable !== 'inherit' ) );
    
    
    		// Disregard the event if there's a focused element or a 
    		// keyboard modifier key is present
    		if ( hasFocus || event.shiftKey || event.altKey || event.ctrlKey || event.metaKey ) return;
    
    
    		var 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: isOverviewActive() ? deactivateOverview() : navigateNext(); break;
    			// return
    			case 13: isOverviewActive() ? deactivateOverview() : triggered = false; break;
    
    			// b, period, Logitech presenter tools "black screen" button
    			case 66: case 190: case 191: togglePause(); break;
    
    			// f
    			case 70: enterFullscreen(); break;
    			default:
    				triggered = false;
    		}
    
    
    		// If the input resulted in a triggered action we should prevent
    
    		// the browsers default behavior
    		if( triggered ) {
    			event.preventDefault();
    		}
    		else if ( event.keyCode === 27 && supports3DTransforms ) {
    			toggleOverview();
    
    		// If auto-sliding is enabled we need to cue up
    
    		// another timeout
    		cueAutoSlide();
    
    	}
    
    	/**
    	 * Handler for the document level 'touchstart' event,
    	 * enables support for swipe and pinch gestures.
    	 */
    	function onDocumentTouchStart( 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 document level 'touchmove' event.
    	 */
    	function onDocumentTouchMove( event ) {
    		// Each touch should only trigger one action
    		if( !touch.handled ) {
    			var currentX = event.touches[0].clientX;
    			var currentY = event.touches[0].clientY;
    
    
    			// If the touch started off 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.handled = 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.handled = true;
    					navigateLeft();
    
    				else if( deltaX < -touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
    					touch.handled = true;
    					navigateRight();
    
    				else if( deltaY > touch.threshold ) {
    					touch.handled = true;
    					navigateUp();
    
    				else if( deltaY < -touch.threshold ) {
    					touch.handled = true;
    					navigateDown();
    				}
    
    				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 document level 'touchend' event.
    	 */
    	function onDocumentTouchEnd( event ) {
    		touch.handled = false;
    	}
    
    	/**
    
    	 * Handles mouse wheel scrolling, throttled to avoid skipping
    
    	 */
    	function onDocumentMouseScroll( event ){
    		clearTimeout( mouseWheelTimeout );
    
    		mouseWheelTimeout = setTimeout( function() {
    			var delta = event.detail || -event.wheelDelta;
    			if( delta > 0 ) {
    				navigateNext();
    			}
    			else {
    				navigatePrev();
    			}
    		}, 100 );
    	}
    
    	 * Clicking on the progress bar results in a navigation to the
    
    	 * closest approximate horizontal slide using this equation:
    	 *
    	 * ( clickX / presentationWidth ) * numberOfSlides
    	 */
    	function onProgressClick( event ) {
    
    		var slidesTotal = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
    
    		var slideIndex = Math.floor( ( event.clientX / dom.wrapper.offsetWidth ) * slidesTotal );
    
    		slide( slideIndex );
    	}
    
    	/**
    	 * Handler for the window level 'hashchange' event.
    	 */
    	function onWindowHashChange( event ) {
    		readURL();
    	}
    
    
    	/**
    	 * Handler for the window level 'resize' event.
    	 */
    	function onWindowResize( event ) {
    		layout();
    	}
    
    
    	/**
    	 * Invoked when a slide is and we're in the overview.
    	 */
    	function onOverviewSlideClicked( event ) {
    
    		// TODO There's a bug here where the event listeners are not
    
    		// removed after deactivating the overview.
    		if( isOverviewActive() ) {
    			event.preventDefault();
    
    			deactivateOverview();
    
    
    			var element = event.target;
    
    			while( element && !element.nodeName.match( /section/gi ) ) {
    				element = element.parentNode;
    			}
    
    			if( element.nodeName.match( /section/gi ) ) {
    				var h = parseInt( element.getAttribute( 'data-index-h' ), 10 ),
    					v = parseInt( element.getAttribute( 'data-index-v' ), 10 );
    
    				slide( h, v );
    			}
    
    	// --------------------------------------------------------------------//
    	// ------------------------------- API --------------------------------//
    	// --------------------------------------------------------------------//
    
    
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	return {
    
    		// Navigation methods
    		slide: slide,
    		left: navigateLeft,
    		right: navigateRight,
    		up: navigateUp,
    		down: navigateDown,
    		prev: navigatePrev,
    		next: navigateNext,
    
    		prevFragment: previousFragment,
    		nextFragment: nextFragment,
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    		navigateLeft: navigateLeft,
    		navigateRight: navigateRight,
    		navigateUp: navigateUp,
    
    		navigatePrev: navigatePrev,
    		navigateNext: navigateNext,
    
    		toggleOverview: toggleOverview,
    
    		// Toggles the "black screen" mode on/off
    		togglePause: togglePause,
    
    
    		// Adds or removes all internal event listeners (such as keyboard)
    
    		addEventListeners: addEventListeners,
    		removeEventListeners: removeEventListeners,
    
    
    		// Returns the indices of the current, or specified, slide
    
    
    		// Returns the previous slide element, may be null
    		getPreviousSlide: function() {
    
    		},
    
    		// Returns the current slide element
    		getCurrentSlide: function() {
    
    		// Helper method, retrieves query string as a key/value hash
    		getQueryHash: function() {
    			var query = {};
    
    			location.search.replace( /[A-Z0-9]+?=(\w*)/gi, function(a) {
    				query[ a.split( '=' ).shift() ] = a.split( '=' ).pop();
    			} );
    
    			return query;
    		},
    
    
    		// Forward event binding to the reveal DOM element
    		addEventListener: function( type, listener, useCapture ) {
    
    hakimel's avatar
    hakimel committed
    			if( 'addEventListener' in window ) {
    				( dom.wrapper || document.querySelector( '.reveal' ) ).addEventListener( type, listener, useCapture );
    			}
    
    		},
    		removeEventListener: function( type, listener, useCapture ) {
    
    hakimel's avatar
    hakimel committed
    			if( 'addEventListener' in window ) {
    				( dom.wrapper || document.querySelector( '.reveal' ) ).removeEventListener( type, listener, useCapture );
    			}
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	};