Skip to content
Snippets Groups Projects
reveal.js 96.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • 		// 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( document.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 ) {
    
    	/**
    	 * Handler for the window level 'resize' event.
    	 */
    	function onWindowResize( event ) {
    
    	/**
    	 * Handle for the window level 'visibilitychange' event.
    	 */
    	function onPageVisibilityChange( event ) {
    
    		var isHidden =  document.webkitHidden ||
    						document.msHidden ||
    						document.hidden;
    
    		// If, after clicking a link or similar and we're coming back,
    		// focus the document.body to ensure we can use keyboard shortcuts
    		if( isHidden === false && document.activeElement !== document.body ) {
    			document.activeElement.blur();
    			document.body.focus();
    		}
    
    	}
    
    
    	/**
    	 * 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( eventsAreBound && isOverview() ) {
    
    			var element = event.target;
    
    			while( element && !element.nodeName.match( /section/gi ) ) {
    				element = element.parentNode;
    			}
    
    
    			if( element && !element.classList.contains( 'disabled' ) ) {
    
    				deactivateOverview();
    
    				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 );
    				}
    
    	/**
    	 * Handles clicks on links that are set to preview in the
    	 * iframe overlay.
    	 */
    	function onPreviewLinkClicked( event ) {
    
    		var url = event.target.getAttribute( 'href' );
    		if( url ) {
    			openPreview( url );
    			event.preventDefault();
    		}
    
    	}
    
    
    	/**
    	 * Handles click on the auto-sliding controls element.
    	 */
    	function onAutoSlidePlayerClick( event ) {
    
    		// Replay
    		if( Reveal.isLastSlide() && config.loop === false ) {
    			slide( 0, 0 );
    			resumeAutoSlide();
    		}
    		// Resume
    		else if( autoSlidePaused ) {
    			resumeAutoSlide();
    		}
    		// Pause
    		else {
    			pauseAutoSlide();
    		}
    
    	}
    
    
    	// --------------------------------------------------------------------//
    	// ------------------------ PLAYBACK COMPONENT ------------------------//
    	// --------------------------------------------------------------------//
    
    
    
    	/**
    	 * Constructor for the playback component, which displays
    	 * play/pause/progress controls.
    	 *
    	 * @param {HTMLElement} container The component will append
    	 * itself to this
    	 * @param {Function} progressCheck A method which will be
    	 * called frequently to get the current progress on a range
    	 * of 0-1
    	 */
    
    	function Playback( container, progressCheck ) {
    
    
    		this.thickness = 3;
    
    		this.playing = false;
    
    		this.progress = 0;
    
    
    		// Used to loop the animation smoothly
    		this.progressOffset = 1;
    
    
    		this.container = container;
    		this.progressCheck = progressCheck;
    
    		this.canvas = document.createElement( 'canvas' );
    		this.canvas.className = 'playback';
    
    		this.canvas.width = this.diameter;
    		this.canvas.height = this.diameter;
    
    		this.context = this.canvas.getContext( '2d' );
    
    		this.container.appendChild( this.canvas );
    
    		this.render();
    
    	}
    
    	Playback.prototype.setPlaying = function( value ) {
    
    
    		// Start repainting if we weren't already
    		if( !wasPlaying && this.playing ) {
    
    	Playback.prototype.animate = function() {
    
    		this.progress = this.progressCheck();
    
    
    		// When we loop, offset the progress so that it eases
    		// smoothly rather than immediately resetting
    		if( progressBefore > 0.8 && this.progress < 0.2 ) {
    			this.progressOffset = this.progress;
    		}
    
    
    		this.render();
    
    		if( this.playing ) {
    
    			features.requestAnimationFrameMethod.call( window, this.animate.bind( this ) );
    
    	/**
    	 * Renders the current progress and playback state.
    	 */
    
    	Playback.prototype.render = function() {
    
    
    		var progress = this.playing ? this.progress : 0,
    			radius = ( this.diameter / 2 ) - this.thickness,
    			x = this.diameter / 2,
    			y = this.diameter / 2,
    			iconSize = 14;
    
    		// Ease towards 1
    		this.progressOffset += ( 1 - this.progressOffset ) * 0.1;
    
    		var endAngle = ( - Math.PI / 2 ) + ( progress * ( Math.PI * 2 ) );
    		var startAngle = ( - Math.PI / 2 ) + ( this.progressOffset * ( Math.PI * 2 ) );
    
    
    		this.context.save();
    
    		this.context.clearRect( 0, 0, this.diameter, this.diameter );
    
    
    		// Solid background color
    		this.context.beginPath();
    
    		this.context.arc( x, y, radius + 2, 0, Math.PI * 2, false );
    		this.context.fillStyle = 'rgba( 0, 0, 0, 0.4 )';
    
    		this.context.fill();
    
    		// Draw progress track
    		this.context.beginPath();
    		this.context.arc( x, y, radius, 0, Math.PI * 2, false );
    		this.context.lineWidth = this.thickness;
    		this.context.strokeStyle = '#666';
    		this.context.stroke();
    
    
    		if( this.playing ) {
    			// Draw progress on top of track
    			this.context.beginPath();
    			this.context.arc( x, y, radius, startAngle, endAngle, false );
    			this.context.lineWidth = this.thickness;
    			this.context.strokeStyle = '#fff';
    			this.context.stroke();
    		}
    
    		this.context.translate( x - ( iconSize / 2 ), y - ( iconSize / 2 ) );
    
    		// Draw play/pause icons
    		if( this.playing ) {
    			this.context.fillStyle = '#fff';
    			this.context.fillRect( 0, 0, iconSize / 2 - 2, iconSize );
    			this.context.fillRect( iconSize / 2 + 2, 0, iconSize / 2 - 2, iconSize );
    		}
    		else {
    			this.context.beginPath();
    			this.context.translate( 2, 0 );
    			this.context.moveTo( 0, 0 );
    			this.context.lineTo( iconSize - 2, iconSize / 2 );
    			this.context.lineTo( 0, iconSize );
    			this.context.fillStyle = '#fff';
    			this.context.fill();
    		}
    
    
    		this.context.restore();
    
    	};
    
    
    	Playback.prototype.on = function( type, listener ) {
    		this.canvas.addEventListener( type, listener, false );
    	};
    
    	Playback.prototype.off = function( type, listener ) {
    		this.canvas.removeEventListener( type, listener, false );
    	};
    
    
    	Playback.prototype.destroy = function() {
    
    		this.playing = false;
    
    		if( this.canvas.parentNode ) {
    			this.container.removeChild( this.canvas );
    		}
    
    	};
    
    
    	// --------------------------------------------------------------------//
    	// ------------------------------- API --------------------------------//
    	// --------------------------------------------------------------------//
    
    
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    	return {
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    		sync: sync,
    
    		// Navigation methods
    		slide: slide,
    		left: navigateLeft,
    		right: navigateRight,
    		up: navigateUp,
    		down: navigateDown,
    		prev: navigatePrev,
    		next: navigateNext,
    
    
    		// Fragment methods
    		navigateFragment: navigateFragment,
    
    		prevFragment: previousFragment,
    		nextFragment: nextFragment,
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    		navigateLeft: navigateLeft,
    		navigateRight: navigateRight,
    		navigateUp: navigateUp,
    
    		navigatePrev: navigatePrev,
    		navigateNext: navigateNext,
    
    		// Forces an update in slide layout
    		layout: layout,
    
    
    		// Returns an object with the available routes as booleans (left/right/top/bottom)
    		availableRoutes: availableRoutes,
    
    
    		// Returns an object with the available fragments as booleans (prev/next)
    		availableFragments: availableFragments,
    
    
    		toggleOverview: toggleOverview,
    
    		// Toggles the "black screen" mode on/off
    		togglePause: togglePause,
    
    
    		// Toggles the auto slide mode on/off
    		toggleAutoSlide: toggleAutoSlide,
    
    
    		// State checks
    		isOverview: isOverview,
    		isPaused: isPaused,
    
    		isAutoSliding: isAutoSliding,
    
    		// Adds or removes all internal event listeners (such as keyboard)
    
    		addEventListeners: addEventListeners,
    		removeEventListeners: removeEventListeners,
    
    
    		// Facility for persisting and restoring the presentation state
    		getState: getState,
    		setState: setState,
    
    
    		// Presentation progress on range of 0-1
    		getProgress: getProgress,
    
    
    		// Returns the indices of the current, or specified, slide
    
    Hakim El Hattab's avatar
    Hakim El Hattab committed
    		getTotalSlides: getTotalSlides,
    
    
    		// Returns the slide at the specified index, y is optional
    		getSlide: function( x, y ) {
    			var horizontalSlide = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR )[ x ];
    			var verticalSlides = horizontalSlide && horizontalSlide.querySelectorAll( 'section' );
    
    			if( typeof y !== 'undefined' ) {
    				return verticalSlides ? verticalSlides[ y ] : undefined;
    			}
    
    			return horizontalSlide;
    		},
    
    
    		// Returns the previous slide element, may be null
    		getPreviousSlide: function() {
    
    		},
    
    		// Returns the current slide element
    		getCurrentSlide: function() {
    
    		// Returns the current scale of the presentation content
    		getScale: function() {
    			return scale;
    		},
    
    
    		// Returns the current configuration object
    		getConfig: function() {
    			return config;
    		},
    
    
    		// 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();
    			} );
    
    
    			// Basic deserialization
    			for( var i in query ) {
    				var value = query[ i ];
    
    				query[ i ] = deserialize( unescape( value ) );
    
    		// Returns true if we're currently on the first slide
    		isFirstSlide: function() {
    			return document.querySelector( SLIDES_SELECTOR + '.past' ) == null ? true : false;
    		},
    
    		// Returns true if we're currently on the last slide
    		isLastSlide: function() {
    
    			if( currentSlide ) {
    				// Does this slide has next a sibling?
    				if( currentSlide.nextElementSibling ) return false;
    
    				// If it's vertical, does its parent have a next sibling?
    				if( isVerticalSlide( currentSlide ) && currentSlide.parentNode.nextElementSibling ) return false;
    
    				return true;
    
    		// Checks if reveal.js has been loaded and is ready for use
    		isReady: function() {
    			return loaded;
    		},
    
    
    		// 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
    	};