Skip to content
Snippets Groups Projects
reveal.js 43.9 KiB
Newer Older
 * reveal.js
 * http://lab.hakim.se/reveal-js
 * MIT licensed
Hakim El Hattab's avatar
Hakim El Hattab committed
 * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se
Hakim El Hattab's avatar
Hakim El Hattab committed
 */
	var SLIDES_SELECTOR = '.reveal .slides section',
		HORIZONTAL_SLIDES_SELECTOR = '.reveal .slides>section',
		VERTICAL_SLIDES_SELECTOR = '.reveal .slides>section.present>section',
Hakim El Hattab's avatar
Hakim El Hattab committed

		// Configurations defaults, can be overridden at initialization time
			// Display controls in the bottom right corner
			controls: true,

			// Display a presentation progress bar
			progress: true,

			// Push each slide change to the browser history
			// Enable keyboard shortcuts for navigation
			keyboard: true,

			// Enable the slide overview mode
			overview: true,

			// Number of milliseconds between automatically proceeding to the
			// next slide, disabled when set to 0, this value can be overwritten
			// by using a data-autoslide attribute on your slides
			autoSlide: 0,

			// Enable slide navigation via mouse wheel
			mouseWheel: true,
			rollingLinks: true,
			// Transition style (see /css/theme)
Hakim El Hattab's avatar
Hakim El Hattab committed
			transition: 'default', // default/cube/page/concave/zoom/linear/none

			// Script dependencies to load
			dependencies: []
		// Stores if the next slide should be shown automatically
		// after n milliseconds
		autoSlide = config.autoSlide,

		// The horizontal and verical index of the currently active slide
		indexh = 0,
		indexv = 0,

		// The previous and current slide HTML elements
		previousSlide,
		currentSlide,

		// Slides may hold a data-state attribute which we pick up and apply
		// as a class to the body. This list contains the combined state of
		// Cached references to DOM elements
		dom = {},

		// Detect support for CSS 3D transforms
		supports3DTransforms =  'WebkitPerspective' in document.body.style ||
								'MozPerspective' in document.body.style ||
								'msPerspective' in document.body.style ||
								'OPerspective' in document.body.style ||
								'perspective' in document.body.style,
		supports2DTransforms =  'WebkitTransform' in document.body.style ||
								'MozTransform' in document.body.style ||
								'msTransform' in document.body.style ||
								'OTransform' in document.body.style ||
								'transform' in document.body.style,
		// Throttles mouse wheel navigation
		// An interval used to automatically move on to the next slide
		autoSlideTimeout = 0,

		// Delays updates to the URL due to a Chrome thumbnailer bug
		writeURLTimeout = 0,

		// Holds information about the currently ongoing touch input
		touch = {
			startX: 0,
			startY: 0,
			startSpan: 0,
			startCount: 0,
			handled: false,
Hakim El Hattab's avatar
Hakim El Hattab committed
	/**
	 * Starts up the presentation if the client is capable.
Hakim El Hattab's avatar
Hakim El Hattab committed
	 */
	function initialize( options ) {
		if( ( !supports2DTransforms && !supports3DTransforms ) ) {
			document.body.setAttribute( 'class', 'no-transforms' );

			// If the browser doesn't support core features we won't be
			// using JavaScript to control the presentation
		// Copy options over to our config object
		extend( config, options );

		// Hide the address bar in mobile browsers
		hideAddressBar();

		// Loads the dependencies and continues to #start() once done
		load();
	 * Finds and stores references to DOM elements which are
	 * required by the presentation. If a required element is
	 * not found, it is created.
	 */
	function setupDOM() {
		// Cache references to key DOM elements
		dom.theme = document.querySelector( '#theme' );
		dom.wrapper = document.querySelector( '.reveal' );

		// Progress bar
		if( !dom.wrapper.querySelector( '.progress' ) && config.progress ) {
			var progressElement = document.createElement( 'div' );
			progressElement.classList.add( 'progress' );
			progressElement.innerHTML = '<span></span>';
			dom.wrapper.appendChild( progressElement );
		}

		// Arrow controls
		if( !dom.wrapper.querySelector( '.controls' ) && config.controls ) {
			var controlsElement = document.createElement( 'aside' );
			controlsElement.classList.add( 'controls' );
			controlsElement.innerHTML = '<div class="navigate-left"></div>' +
										'<div class="navigate-right"></div>' +
										'<div class="navigate-up"></div>' +
										'<div class="navigate-down"></div>';
			dom.wrapper.appendChild( controlsElement );
		}

		// Presentation background element
		if( !dom.wrapper.querySelector( '.state-background' ) ) {
			var backgroundElement = document.createElement( 'div' );
			backgroundElement.classList.add( 'state-background' );
			dom.wrapper.appendChild( backgroundElement );
		}

		// Overlay graphic which is displayed during the paused mode
		if( !dom.wrapper.querySelector( '.pause-overlay' ) ) {
			var pausedElement = document.createElement( 'div' );
			pausedElement.classList.add( 'pause-overlay' );
			dom.wrapper.appendChild( pausedElement );
		}

		// Cache references to elements
		dom.progress = document.querySelector( '.reveal .progress' );
		dom.progressbar = document.querySelector( '.reveal .progress span' );
danielmitd's avatar
danielmitd committed
		if ( config.controls ) {
danielmitd's avatar
danielmitd committed
			dom.controls = document.querySelector( '.reveal .controls' );

			// There can be multiple instances of controls throughout the page
			dom.controlsLeft = toArray( document.querySelectorAll( '.navigate-left' ) );
			dom.controlsRight = toArray( document.querySelectorAll( '.navigate-right' ) );
			dom.controlsUp = toArray( document.querySelectorAll( '.navigate-up' ) );
			dom.controlsDown = toArray( document.querySelectorAll( '.navigate-down' ) );
			dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) );
			dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) );
	/**
	 * Hides the address bar if we're on a mobile device.
	 */
	function hideAddressBar() {
		if( navigator.userAgent.match( /(iphone|ipod)/i ) ) {
			// Give the page some scrollable overflow
			document.documentElement.style.overflow = 'scroll';
			document.body.style.height = '120%';

			// Events that should trigger the address bar to hide
			window.addEventListener( 'load', removeAddressBar, false );
			window.addEventListener( 'orientationchange', removeAddressBar, false );
		}
	}

	 * Loads the dependencies of reveal.js. Dependencies are
	 * defined via the configuration option 'dependencies'
	 * and will be loaded prior to starting/binding reveal.js.
	 * Some dependencies may have an 'async' flag, if so they
	 * will load after reveal.js has been started up.
	 */
	function load() {
		var scripts = [],
			scriptsAsync = [];

		for( var i = 0, len = config.dependencies.length; i < len; i++ ) {
			var s = config.dependencies[i];

			// Load if there's no condition or the condition is truthy
			if( !s.condition || s.condition() ) {
				if( s.async ) {
					scriptsAsync.push( s.src );
				}
				else {
					scripts.push( s.src );
				}

				// Extension may contain callback functions
				if( typeof s.callback === 'function' ) {
					head.ready( s.src.match( /([\w\d_\-]*)\.?js|[^\\\/]*$/i )[0], s.callback );
				}
			}
		}

		// Called once synchronous scritps finish loading
		function proceed() {
			if( scriptsAsync.length ) {
				// Load asynchronous scripts
				head.js.apply( null, scriptsAsync );
			}
			start();
		}

		if( scripts.length ) {
			head.ready( proceed );

			// Load synchronous scripts
			head.js.apply( null, scripts );
		}
		else {
			proceed();
		}
	}

	/**
	 * Starts up reveal.js by binding input events and navigating
	 * to the current URL deeplink if there is one.
	 */
	function start() {
		// Make sure we've got all the DOM elements we need
		setupDOM();
		// Subscribe to input
		addEventListeners();

		// Updates the presentation to match the current configuration values
		configure();

		// Force an initial layout, will thereafter be invoked as the window
		// is resized
		layout();

		// Read the initial hash
		readURL();

		// Start auto-sliding if it's enabled
		cueAutoSlide();
		// Notify listeners that the presentation is ready but use a 1ms
		// timeout to ensure it's not fired synchronously after #initialize()
		setTimeout( function() {
			dispatchEvent( 'ready', {
				'indexh': indexh,
				'indexv': indexv,
				'currentSlide': currentSlide
			} );
		}, 1 );
	}

	/**
	 * Applies the configuration settings from the config object.
	 */
		if( supports3DTransforms === false ) {
			config.transition = 'linear';
danielmitd's avatar
danielmitd committed
		if( config.controls && dom.controls ) {
			dom.controls.style.display = 'block';
		}

		if( config.progress && dom.progress ) {
			dom.progress.style.display = 'block';
		}

		if( config.transition !== 'default' ) {
			dom.wrapper.classList.add( config.transition );
		if( config.center ) {
			dom.wrapper.classList.add( 'center' );
		}

		if( config.mouseWheel ) {
			document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
			document.addEventListener( 'mousewheel', onDocumentMouseScroll, false );
		if( config.rollingLinks ) {
			linkify();
		}

		// Load the theme in the config, if it's not already loaded
		if( config.theme && dom.theme ) {
			var themeURL = dom.theme.getAttribute( 'href' );
			var themeFinder = /[^\/]*?(?=\.css)/;
			var themeName = themeURL.match(themeFinder)[0];

			if(  config.theme !== themeName ) {
				themeURL = themeURL.replace(themeFinder, config.theme);
				dom.theme.setAttribute( 'href', themeURL );
			}
		}
	function addEventListeners() {
		document.addEventListener( 'touchstart', onDocumentTouchStart, false );
		document.addEventListener( 'touchmove', onDocumentTouchMove, false );
		document.addEventListener( 'touchend', onDocumentTouchEnd, false );
		window.addEventListener( 'hashchange', onWindowHashChange, false );
		window.addEventListener( 'resize', onWindowResize, false );
		if( config.keyboard ) {
			document.addEventListener( 'keydown', onDocumentKeyDown, false );
		}

		if ( config.progress && dom.progress ) {
			dom.progress.addEventListener( 'click', preventAndForward( onProgressClick ), false );
		}

danielmitd's avatar
danielmitd committed
		if ( config.controls && dom.controls ) {
			dom.controlsLeft.forEach( function( el ) { el.addEventListener( 'click', preventAndForward( navigateLeft ), false ); } );
			dom.controlsRight.forEach( function( el ) { el.addEventListener( 'click', preventAndForward( navigateRight ), false ); } );
			dom.controlsUp.forEach( function( el ) { el.addEventListener( 'click', preventAndForward( navigateUp ), false ); } );
			dom.controlsDown.forEach( function( el ) { el.addEventListener( 'click', preventAndForward( navigateDown ), false ); } );
			dom.controlsPrev.forEach( function( el ) { el.addEventListener( 'click', preventAndForward( navigatePrev ), false ); } );
			dom.controlsNext.forEach( function( el ) { el.addEventListener( 'click', preventAndForward( navigateNext ), false ); } );
	function removeEventListeners() {
		document.removeEventListener( 'keydown', onDocumentKeyDown, false );
		document.removeEventListener( 'touchstart', onDocumentTouchStart, false );
		document.removeEventListener( 'touchmove', onDocumentTouchMove, false );
		document.removeEventListener( 'touchend', onDocumentTouchEnd, false );
		window.removeEventListener( 'hashchange', onWindowHashChange, false );
		window.removeEventListener( 'resize', onWindowResize, false );

		if ( config.progress && dom.progress ) {
			dom.progress.removeEventListener( 'click', preventAndForward( onProgressClick ), false );
		}
danielmitd's avatar
danielmitd committed
		if ( config.controls && dom.controls ) {
			dom.controlsLeft.forEach( function( el ) { el.removeEventListener( 'click', preventAndForward( navigateLeft ), false ); } );
			dom.controlsRight.forEach( function( el ) { el.removeEventListener( 'click', preventAndForward( navigateRight ), false ); } );
			dom.controlsUp.forEach( function( el ) { el.removeEventListener( 'click', preventAndForward( navigateUp ), false ); } );
			dom.controlsDown.forEach( function( el ) { el.removeEventListener( 'click', preventAndForward( navigateDown ), false ); } );
			dom.controlsPrev.forEach( function( el ) { el.removeEventListener( 'click', preventAndForward( navigatePrev ), false ); } );
			dom.controlsNext.forEach( function( el ) { el.removeEventListener( 'click', preventAndForward( navigateNext ), false ); } );
Hakim El Hattab's avatar
Hakim El Hattab committed
	}
	 * Extend object a with the properties of object b.
	 * If there's a conflict, object b takes precedence.
	 */
	function extend( a, b ) {
		for( var i in b ) {
			a[ i ] = b[ i ];
		}
	}

	/**
	 * Converts the target object to an array.
	 */
	function toArray( o ) {
		return Array.prototype.slice.call( o );
	}

	function each( targets, method, args ) {
		targets.forEach( function( el ) {
			el[method].apply( el, args );
		} );
	}

	/**
	 * Measures the distance in pixels between point a
	 * @param {Object} a point with x/y properties
	 * @param {Object} b point with x/y properties
	 */
	function distanceBetween( a, b ) {
		var dx = a.x - b.x,
			dy = a.y - b.y;

		return Math.sqrt( dx*dx + dy*dy );
	}

	 * Prevents an events defaults behavior calls the
	 *
	 * @param {Function} delegate The method to call
	 * after the wrapper has been executed
	 */
	function preventAndForward( delegate ) {
		return function( event ) {
			event.preventDefault();
			delegate.call( null, event );
	 * Causes the address bar to hide on mobile devices,
	 * more vertical space ftw.
	 */
	function removeAddressBar() {
		setTimeout( function() {
			window.scrollTo( 0, 1 );
		}, 0 );
	}
	 * Dispatches an event of the specified type from the
	 * reveal DOM element.
	 */
	function dispatchEvent( type, properties ) {
		var event = document.createEvent( "HTMLEvents", 1, 2 );
		event.initEvent( type, true, true );
		extend( event, properties );
		dom.wrapper.dispatchEvent( event );
	}

	/**
	 * Wrap all links in 3D goodness.
	 */
	function linkify() {
		if( supports3DTransforms && !( 'msPerspective' in document.body.style ) ) {
			var nodes = document.querySelectorAll( SLIDES_SELECTOR + ' a:not(.image)' );

			for( var i = 0, len = nodes.length; i < len; i++ ) {
				var node = nodes[i];
				if( node.textContent && !node.querySelector( 'img' ) && ( !node.className || !node.classList.contains( node, 'roll' ) ) ) {
					node.classList.add( 'roll' );
					node.innerHTML = '<span data-title="'+ node.text +'">' + node.innerHTML + '</span>';
				}
			}
		}
	/**
	 * Applies JavaScript-controlled layout rules to the
	 * presentation.
	 */
	function layout() {
		if( config.center ) {

			// Select all slides, vertical and horizontal
			var slides = toArray( document.querySelectorAll( SLIDES_SELECTOR ) );

			// Determine the minimum top offset for slides
			var minTop = -dom.wrapper.offsetHeight / 2;

			for( var i = 0, len = slides.length; i < len; i++ ) {
				var slide = slides[ i ];

				// Don't bother update invisible slides
				if( slide.style.display === 'none' ) {
					continue;
				}

				// Vertical stacks are not centered since their section 
				// children will be
				if( slide.classList.contains( 'stack' ) ) {
					slide.style.top = Math.max( - ( slide.offsetHeight / 2 ) - 20, minTop ) + 'px';
	/**
	 * Stores the vertical index of a stack so that the same 
	 * vertical slide can be selected when navigating to and 
	 * from the stack.
	 * 
	 * @param {HTMLElement} stack The vertical stack element
	 * @param {int} v Index to memorize
	 */
	function setPreviousVerticalIndex( stack, v ) {
		if( stack ) {
			stack.setAttribute( 'data-previous-indexv', v || 0 );
		}
	}

	/**
	 * Retrieves the vertical index which was stored using 
	 * #setPreviousVerticalIndex() or 0 if
Loading
Loading full blame...