rc-web@42: /*! rc-web@42: * reveal.js 2.0 r22 rc-web@42: * http://lab.hakim.se/reveal-js rc-web@42: * MIT licensed rc-web@42: * rc-web@42: * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se rc-web@42: */ rc-web@42: var Reveal = (function(){ rc-web@42: rc-web@42: var HORIZONTAL_SLIDES_SELECTOR = '.reveal .slides>section', rc-web@42: VERTICAL_SLIDES_SELECTOR = '.reveal .slides>section.present>section', rc-web@42: rc-web@42: IS_TOUCH_DEVICE = !!( 'ontouchstart' in window ), rc-web@42: rc-web@42: // Configurations defaults, can be overridden at initialization time rc-web@42: config = { rc-web@42: // Display controls in the bottom right corner rc-web@42: controls: true, rc-web@42: rc-web@42: // Display a presentation progress bar rc-web@42: progress: true, rc-web@42: rc-web@42: // Push each slide change to the browser history rc-web@42: history: false, rc-web@42: rc-web@42: // Enable keyboard shortcuts for navigation rc-web@42: keyboard: true, rc-web@42: rc-web@42: // Loop the presentation rc-web@42: loop: false, rc-web@42: rc-web@42: // Number of milliseconds between automatically proceeding to the rc-web@42: // next slide, disabled when set to 0 rc-web@42: autoSlide: 0, rc-web@42: rc-web@42: // Enable slide navigation via mouse wheel rc-web@42: mouseWheel: true, rc-web@42: rc-web@42: // Apply a 3D roll to links on hover rc-web@42: rollingLinks: true, rc-web@42: rc-web@42: // Transition style (see /css/theme) rc-web@42: theme: 'default', rc-web@42: rc-web@42: // Transition style rc-web@42: transition: 'default', // default/cube/page/concave/linear(2d), rc-web@42: rc-web@42: // Script dependencies to load rc-web@42: dependencies: [] rc-web@42: }, rc-web@42: rc-web@42: // The horizontal and verical index of the currently active slide rc-web@42: indexh = 0, rc-web@42: indexv = 0, rc-web@42: rc-web@42: // The previous and current slide HTML elements rc-web@42: previousSlide, rc-web@42: currentSlide, rc-web@42: rc-web@42: // Slides may hold a data-state attribute which we pick up and apply rc-web@42: // as a class to the body. This list contains the combined state of rc-web@42: // all current slides. rc-web@42: state = [], rc-web@42: rc-web@42: // Cached references to DOM elements rc-web@42: dom = {}, rc-web@42: rc-web@42: // Detect support for CSS 3D transforms rc-web@42: supports3DTransforms = 'WebkitPerspective' in document.body.style || rc-web@42: 'MozPerspective' in document.body.style || rc-web@42: 'msPerspective' in document.body.style || rc-web@42: 'OPerspective' in document.body.style || rc-web@42: 'perspective' in document.body.style, rc-web@42: rc-web@42: supports2DTransforms = 'WebkitTransform' in document.body.style || rc-web@42: 'MozTransform' in document.body.style || rc-web@42: 'msTransform' in document.body.style || rc-web@42: 'OTransform' in document.body.style || rc-web@42: 'transform' in document.body.style, rc-web@42: rc-web@42: // Throttles mouse wheel navigation rc-web@42: mouseWheelTimeout = 0, rc-web@42: rc-web@42: // An interval used to automatically move on to the next slide rc-web@42: autoSlideTimeout = 0, rc-web@42: rc-web@42: // Delays updates to the URL due to a Chrome thumbnailer bug rc-web@42: writeURLTimeout = 0, rc-web@42: rc-web@42: // Holds information about the currently ongoing touch input rc-web@42: touch = { rc-web@42: startX: 0, rc-web@42: startY: 0, rc-web@42: startSpan: 0, rc-web@42: startCount: 0, rc-web@42: handled: false, rc-web@42: threshold: 40 rc-web@42: }; rc-web@42: rc-web@42: rc-web@42: /** rc-web@42: * Starts up the presentation if the client is capable. rc-web@42: */ rc-web@42: function initialize( options ) { rc-web@42: if( ( !supports2DTransforms && !supports3DTransforms ) ) { rc-web@42: document.body.setAttribute( 'class', 'no-transforms' ); rc-web@42: rc-web@42: // If the browser doesn't support core features we won't be rc-web@42: // using JavaScript to control the presentation rc-web@42: return; rc-web@42: } rc-web@42: rc-web@42: // Copy options over to our config object rc-web@42: extend( config, options ); rc-web@42: rc-web@42: // Cache references to DOM elements rc-web@42: dom.theme = document.querySelector( '#theme' ); rc-web@42: dom.wrapper = document.querySelector( '.reveal' ); rc-web@42: dom.progress = document.querySelector( '.reveal .progress' ); rc-web@42: dom.progressbar = document.querySelector( '.reveal .progress span' ); rc-web@42: rc-web@42: if ( config.controls ) { rc-web@42: dom.controls = document.querySelector( '.reveal .controls' ); rc-web@42: dom.controlsLeft = document.querySelector( '.reveal .controls .left' ); rc-web@42: dom.controlsRight = document.querySelector( '.reveal .controls .right' ); rc-web@42: dom.controlsUp = document.querySelector( '.reveal .controls .up' ); rc-web@42: dom.controlsDown = document.querySelector( '.reveal .controls .down' ); rc-web@42: } rc-web@42: rc-web@42: // Loads the dependencies and continues to #start() once done rc-web@42: load(); rc-web@42: rc-web@42: // Set up hiding of the browser address bar rc-web@42: if( navigator.userAgent.match( /(iphone|ipod|android)/i ) ) { rc-web@42: // Give the page some scrollable overflow rc-web@42: document.documentElement.style.overflow = 'scroll'; rc-web@42: document.body.style.height = '120%'; rc-web@42: rc-web@42: // Events that should trigger the address bar to hide rc-web@42: window.addEventListener( 'load', removeAddressBar, false ); rc-web@42: window.addEventListener( 'orientationchange', removeAddressBar, false ); rc-web@42: } rc-web@42: rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Loads the dependencies of reveal.js. Dependencies are rc-web@42: * defined via the configuration option 'dependencies' rc-web@42: * and will be loaded prior to starting/binding reveal.js. rc-web@42: * Some dependencies may have an 'async' flag, if so they rc-web@42: * will load after reveal.js has been started up. rc-web@42: */ rc-web@42: function load() { rc-web@42: var scripts = [], rc-web@42: scriptsAsync = []; rc-web@42: rc-web@42: for( var i = 0, len = config.dependencies.length; i < len; i++ ) { rc-web@42: var s = config.dependencies[i]; rc-web@42: rc-web@42: // Load if there's no condition or the condition is truthy rc-web@42: if( !s.condition || s.condition() ) { rc-web@42: if( s.async ) { rc-web@42: scriptsAsync.push( s.src ); rc-web@42: } rc-web@42: else { rc-web@42: scripts.push( s.src ); rc-web@42: } rc-web@42: rc-web@42: // Extension may contain callback functions rc-web@42: if( typeof s.callback === 'function' ) { rc-web@42: head.ready( s.src.match( /([\w\d_-]*)\.?[^\\\/]*$/i )[0], s.callback ); rc-web@42: } rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: // Called once synchronous scritps finish loading rc-web@42: function proceed() { rc-web@42: // Load asynchronous scripts rc-web@42: head.js.apply( null, scriptsAsync ); rc-web@42: rc-web@42: start(); rc-web@42: } rc-web@42: rc-web@42: if( scripts.length ) { rc-web@42: head.ready( proceed ); rc-web@42: rc-web@42: // Load synchronous scripts rc-web@42: head.js.apply( null, scripts ); rc-web@42: } rc-web@42: else { rc-web@42: proceed(); rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Starts up reveal.js by binding input events and navigating rc-web@42: * to the current URL deeplink if there is one. rc-web@42: */ rc-web@42: function start() { rc-web@42: // Subscribe to input rc-web@42: addEventListeners(); rc-web@42: rc-web@42: // Updates the presentation to match the current configuration values rc-web@42: configure(); rc-web@42: rc-web@42: // Read the initial hash rc-web@42: readURL(); rc-web@42: rc-web@42: // Start auto-sliding if it's enabled rc-web@42: cueAutoSlide(); rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Applies the configuration settings from the config object. rc-web@42: */ rc-web@42: function configure() { rc-web@42: if( supports3DTransforms === false ) { rc-web@42: config.transition = 'linear'; rc-web@42: } rc-web@42: rc-web@42: if( config.controls && dom.controls ) { rc-web@42: dom.controls.style.display = 'block'; rc-web@42: } rc-web@42: rc-web@42: if( config.progress && dom.progress ) { rc-web@42: dom.progress.style.display = 'block'; rc-web@42: } rc-web@42: rc-web@42: // Load the theme in the config, if it's not already loaded rc-web@42: if( config.theme && dom.theme ) { rc-web@42: var themeURL = dom.theme.getAttribute( 'href' ); rc-web@42: var themeFinder = /[^/]*?(?=\.css)/; rc-web@42: var themeName = themeURL.match(themeFinder)[0]; rc-web@42: if( config.theme !== themeName ) { rc-web@42: themeURL = themeURL.replace(themeFinder, config.theme); rc-web@42: dom.theme.setAttribute( 'href', themeURL ); rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: rc-web@42: if( config.transition !== 'default' ) { rc-web@42: dom.wrapper.classList.add( config.transition ); rc-web@42: } rc-web@42: rc-web@42: if( config.mouseWheel ) { rc-web@42: document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF rc-web@42: document.addEventListener( 'mousewheel', onDocumentMouseScroll, false ); rc-web@42: } rc-web@42: rc-web@42: if( config.rollingLinks ) { rc-web@42: // Add some 3D magic to our anchors rc-web@42: linkify(); rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: function addEventListeners() { rc-web@42: document.addEventListener( 'touchstart', onDocumentTouchStart, false ); rc-web@42: document.addEventListener( 'touchmove', onDocumentTouchMove, false ); rc-web@42: document.addEventListener( 'touchend', onDocumentTouchEnd, false ); rc-web@42: window.addEventListener( 'hashchange', onWindowHashChange, false ); rc-web@42: rc-web@42: if( config.keyboard ) { rc-web@42: document.addEventListener( 'keydown', onDocumentKeyDown, false ); rc-web@42: } rc-web@42: rc-web@42: if ( config.controls && dom.controls ) { rc-web@42: dom.controlsLeft.addEventListener( 'click', preventAndForward( navigateLeft ), false ); rc-web@42: dom.controlsRight.addEventListener( 'click', preventAndForward( navigateRight ), false ); rc-web@42: dom.controlsUp.addEventListener( 'click', preventAndForward( navigateUp ), false ); rc-web@42: dom.controlsDown.addEventListener( 'click', preventAndForward( navigateDown ), false ); rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: function removeEventListeners() { rc-web@42: document.removeEventListener( 'keydown', onDocumentKeyDown, false ); rc-web@42: document.removeEventListener( 'touchstart', onDocumentTouchStart, false ); rc-web@42: document.removeEventListener( 'touchmove', onDocumentTouchMove, false ); rc-web@42: document.removeEventListener( 'touchend', onDocumentTouchEnd, false ); rc-web@42: window.removeEventListener( 'hashchange', onWindowHashChange, false ); rc-web@42: rc-web@42: if ( config.controls && dom.controls ) { rc-web@42: dom.controlsLeft.removeEventListener( 'click', preventAndForward( navigateLeft ), false ); rc-web@42: dom.controlsRight.removeEventListener( 'click', preventAndForward( navigateRight ), false ); rc-web@42: dom.controlsUp.removeEventListener( 'click', preventAndForward( navigateUp ), false ); rc-web@42: dom.controlsDown.removeEventListener( 'click', preventAndForward( navigateDown ), false ); rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Extend object a with the properties of object b. rc-web@42: * If there's a conflict, object b takes precedence. rc-web@42: */ rc-web@42: function extend( a, b ) { rc-web@42: for( var i in b ) { rc-web@42: a[ i ] = b[ i ]; rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Measures the distance in pixels between point a rc-web@42: * and point b. rc-web@42: * rc-web@42: * @param {Object} a point with x/y properties rc-web@42: * @param {Object} b point with x/y properties rc-web@42: */ rc-web@42: function distanceBetween( a, b ) { rc-web@42: var dx = a.x - b.x, rc-web@42: dy = a.y - b.y; rc-web@42: rc-web@42: return Math.sqrt( dx*dx + dy*dy ); rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Prevents an events defaults behavior calls the rc-web@42: * specified delegate. rc-web@42: * rc-web@42: * @param {Function} delegate The method to call rc-web@42: * after the wrapper has been executed rc-web@42: */ rc-web@42: function preventAndForward( delegate ) { rc-web@42: return function( event ) { rc-web@42: event.preventDefault(); rc-web@42: delegate.call(); rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Causes the address bar to hide on mobile devices, rc-web@42: * more vertical space ftw. rc-web@42: */ rc-web@42: function removeAddressBar() { rc-web@42: setTimeout( function() { rc-web@42: window.scrollTo( 0, 1 ); rc-web@42: }, 0 ); rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Handler for the document level 'keydown' event. rc-web@42: * rc-web@42: * @param {Object} event rc-web@42: */ rc-web@42: function onDocumentKeyDown( event ) { rc-web@42: // FFT: Use document.querySelector( ':focus' ) === null rc-web@42: // instead of checking contentEditable? rc-web@42: rc-web@42: // Disregard the event if the target is editable or a rc-web@42: // modifier is present rc-web@42: if ( event.target.contentEditable != 'inherit' || event.shiftKey || event.altKey || event.ctrlKey || event.metaKey ) return; rc-web@42: rc-web@42: var triggered = false; rc-web@42: rc-web@42: switch( event.keyCode ) { rc-web@42: // p, page up rc-web@42: case 80: case 33: navigatePrev(); triggered = true; break; rc-web@42: // n, page down rc-web@42: case 78: case 34: navigateNext(); triggered = true; break; rc-web@42: // h, left rc-web@42: case 72: case 37: navigateLeft(); triggered = true; break; rc-web@42: // l, right rc-web@42: case 76: case 39: navigateRight(); triggered = true; break; rc-web@42: // k, up rc-web@42: case 75: case 38: navigateUp(); triggered = true; break; rc-web@42: // j, down rc-web@42: case 74: case 40: navigateDown(); triggered = true; break; rc-web@42: // home rc-web@42: case 36: navigateTo( 0 ); triggered = true; break; rc-web@42: // end rc-web@42: case 35: navigateTo( Number.MAX_VALUE ); triggered = true; break; rc-web@42: // space rc-web@42: case 32: overviewIsActive() ? deactivateOverview() : navigateNext(); triggered = true; break; rc-web@42: // return rc-web@42: case 13: if( overviewIsActive() ) { deactivateOverview(); triggered = true; } break; rc-web@42: } rc-web@42: rc-web@42: // If the input resulted in a triggered action we should prevent rc-web@42: // the browsers default behavior rc-web@42: if( triggered ) { rc-web@42: event.preventDefault(); rc-web@42: } rc-web@42: else if ( event.keyCode === 27 && supports3DTransforms ) { rc-web@42: toggleOverview(); rc-web@42: rc-web@42: event.preventDefault(); rc-web@42: } rc-web@42: rc-web@42: // If auto-sliding is enabled we need to cue up rc-web@42: // another timeout rc-web@42: cueAutoSlide(); rc-web@42: rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Handler for the document level 'touchstart' event, rc-web@42: * enables support for swipe and pinch gestures. rc-web@42: */ rc-web@42: function onDocumentTouchStart( event ) { rc-web@42: touch.startX = event.touches[0].clientX; rc-web@42: touch.startY = event.touches[0].clientY; rc-web@42: touch.startCount = event.touches.length; rc-web@42: rc-web@42: // If there's two touches we need to memorize the distance rc-web@42: // between those two points to detect pinching rc-web@42: if( event.touches.length === 2 ) { rc-web@42: touch.startSpan = distanceBetween( { rc-web@42: x: event.touches[1].clientX, rc-web@42: y: event.touches[1].clientY rc-web@42: }, { rc-web@42: x: touch.startX, rc-web@42: y: touch.startY rc-web@42: } ); rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Handler for the document level 'touchmove' event. rc-web@42: */ rc-web@42: function onDocumentTouchMove( event ) { rc-web@42: // Each touch should only trigger one action rc-web@42: if( !touch.handled ) { rc-web@42: var currentX = event.touches[0].clientX; rc-web@42: var currentY = event.touches[0].clientY; rc-web@42: rc-web@42: // If the touch started off with two points and still has rc-web@42: // two active touches; test for the pinch gesture rc-web@42: if( event.touches.length === 2 && touch.startCount === 2 ) { rc-web@42: rc-web@42: // The current distance in pixels between the two touch points rc-web@42: var currentSpan = distanceBetween( { rc-web@42: x: event.touches[1].clientX, rc-web@42: y: event.touches[1].clientY rc-web@42: }, { rc-web@42: x: touch.startX, rc-web@42: y: touch.startY rc-web@42: } ); rc-web@42: rc-web@42: // If the span is larger than the desire amount we've got rc-web@42: // ourselves a pinch rc-web@42: if( Math.abs( touch.startSpan - currentSpan ) > touch.threshold ) { rc-web@42: touch.handled = true; rc-web@42: rc-web@42: if( currentSpan < touch.startSpan ) { rc-web@42: activateOverview(); rc-web@42: } rc-web@42: else { rc-web@42: deactivateOverview(); rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: } rc-web@42: // There was only one touch point, look for a swipe rc-web@42: else if( event.touches.length === 1 ) { rc-web@42: var deltaX = currentX - touch.startX, rc-web@42: deltaY = currentY - touch.startY; rc-web@42: rc-web@42: if( deltaX > touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) { rc-web@42: touch.handled = true; rc-web@42: navigateLeft(); rc-web@42: } rc-web@42: else if( deltaX < -touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) { rc-web@42: touch.handled = true; rc-web@42: navigateRight(); rc-web@42: } rc-web@42: else if( deltaY > touch.threshold ) { rc-web@42: touch.handled = true; rc-web@42: navigateUp(); rc-web@42: } rc-web@42: else if( deltaY < -touch.threshold ) { rc-web@42: touch.handled = true; rc-web@42: navigateDown(); rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: event.preventDefault(); rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Handler for the document level 'touchend' event. rc-web@42: */ rc-web@42: function onDocumentTouchEnd( event ) { rc-web@42: touch.handled = false; rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Handles mouse wheel scrolling, throttled to avoid rc-web@42: * skipping multiple slides. rc-web@42: */ rc-web@42: function onDocumentMouseScroll( event ){ rc-web@42: clearTimeout( mouseWheelTimeout ); rc-web@42: rc-web@42: mouseWheelTimeout = setTimeout( function() { rc-web@42: var delta = event.detail || -event.wheelDelta; rc-web@42: if( delta > 0 ) { rc-web@42: navigateNext(); rc-web@42: } rc-web@42: else { rc-web@42: navigatePrev(); rc-web@42: } rc-web@42: }, 100 ); rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Handler for the window level 'hashchange' event. rc-web@42: * rc-web@42: * @param {Object} event rc-web@42: */ rc-web@42: function onWindowHashChange( event ) { rc-web@42: readURL(); rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Wrap all links in 3D goodness. rc-web@42: */ rc-web@42: function linkify() { rc-web@42: if( supports3DTransforms && !( 'msPerspective' in document.body.style ) ) { rc-web@42: var nodes = document.querySelectorAll( '.reveal .slides section a:not(.image)' ); rc-web@42: rc-web@42: for( var i = 0, len = nodes.length; i < len; i++ ) { rc-web@42: var node = nodes[i]; rc-web@42: rc-web@42: if( node.textContent && !node.querySelector( 'img' ) && ( !node.className || !node.classList.contains( node, 'roll' ) ) ) { rc-web@42: node.classList.add( 'roll' ); rc-web@42: node.innerHTML = '' + node.innerHTML + ''; rc-web@42: } rc-web@42: }; rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Displays the overview of slides (quick nav) by rc-web@42: * scaling down and arranging all slide elements. rc-web@42: * rc-web@42: * Experimental feature, might be dropped if perf rc-web@42: * can't be improved. rc-web@42: */ rc-web@42: function activateOverview() { rc-web@42: rc-web@42: dom.wrapper.classList.add( 'overview' ); rc-web@42: rc-web@42: var horizontalSlides = Array.prototype.slice.call( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ); rc-web@42: rc-web@42: for( var i = 0, len1 = horizontalSlides.length; i < len1; i++ ) { rc-web@42: var hslide = horizontalSlides[i], rc-web@42: htransform = 'translateZ(-2500px) translate(' + ( ( i - indexh ) * 105 ) + '%, 0%)'; rc-web@42: rc-web@42: hslide.setAttribute( 'data-index-h', i ); rc-web@42: hslide.style.display = 'block'; rc-web@42: hslide.style.WebkitTransform = htransform; rc-web@42: hslide.style.MozTransform = htransform; rc-web@42: hslide.style.msTransform = htransform; rc-web@42: hslide.style.OTransform = htransform; rc-web@42: hslide.style.transform = htransform; rc-web@42: rc-web@42: if( !hslide.classList.contains( 'stack' ) ) { rc-web@42: // Navigate to this slide on click rc-web@42: hslide.addEventListener( 'click', onOverviewSlideClicked, true ); rc-web@42: } rc-web@42: rc-web@42: var verticalSlides = Array.prototype.slice.call( hslide.querySelectorAll( 'section' ) ); rc-web@42: rc-web@42: for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) { rc-web@42: var vslide = verticalSlides[j], rc-web@42: vtransform = 'translate(0%, ' + ( ( j - ( i === indexh ? indexv : 0 ) ) * 105 ) + '%)'; rc-web@42: rc-web@42: vslide.setAttribute( 'data-index-h', i ); rc-web@42: vslide.setAttribute( 'data-index-v', j ); rc-web@42: vslide.style.display = 'block'; rc-web@42: vslide.style.WebkitTransform = vtransform; rc-web@42: vslide.style.MozTransform = vtransform; rc-web@42: vslide.style.msTransform = vtransform; rc-web@42: vslide.style.OTransform = vtransform; rc-web@42: vslide.style.transform = vtransform; rc-web@42: rc-web@42: // Navigate to this slide on click rc-web@42: vslide.addEventListener( 'click', onOverviewSlideClicked, true ); rc-web@42: } rc-web@42: rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Exits the slide overview and enters the currently rc-web@42: * active slide. rc-web@42: */ rc-web@42: function deactivateOverview() { rc-web@42: dom.wrapper.classList.remove( 'overview' ); rc-web@42: rc-web@42: var slides = Array.prototype.slice.call( document.querySelectorAll( '.reveal .slides section' ) ); rc-web@42: rc-web@42: for( var i = 0, len = slides.length; i < len; i++ ) { rc-web@42: var element = slides[i]; rc-web@42: rc-web@42: // Resets all transforms to use the external styles rc-web@42: element.style.WebkitTransform = ''; rc-web@42: element.style.MozTransform = ''; rc-web@42: element.style.msTransform = ''; rc-web@42: element.style.OTransform = ''; rc-web@42: element.style.transform = ''; rc-web@42: rc-web@42: element.removeEventListener( 'click', onOverviewSlideClicked ); rc-web@42: } rc-web@42: rc-web@42: slide(); rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Checks if the overview is currently active. rc-web@42: * rc-web@42: * @return {Boolean} true if the overview is active, rc-web@42: * false otherwise rc-web@42: */ rc-web@42: function overviewIsActive() { rc-web@42: return dom.wrapper.classList.contains( 'overview' ); rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Invoked when a slide is and we're in the overview. rc-web@42: */ rc-web@42: function onOverviewSlideClicked( event ) { rc-web@42: // TODO There's a bug here where the event listeners are not rc-web@42: // removed after deactivating the overview. rc-web@42: if( overviewIsActive() ) { rc-web@42: event.preventDefault(); rc-web@42: rc-web@42: deactivateOverview(); rc-web@42: rc-web@42: indexh = this.getAttribute( 'data-index-h' ); rc-web@42: indexv = this.getAttribute( 'data-index-v' ); rc-web@42: rc-web@42: slide(); rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Updates one dimension of slides by showing the slide rc-web@42: * with the specified index. rc-web@42: * rc-web@42: * @param {String} selector A CSS selector that will fetch rc-web@42: * the group of slides we are working with rc-web@42: * @param {Number} index The index of the slide that should be rc-web@42: * shown rc-web@42: * rc-web@42: * @return {Number} The index of the slide that is now shown, rc-web@42: * might differ from the passed in index if it was out of rc-web@42: * bounds. rc-web@42: */ rc-web@42: function updateSlides( selector, index ) { rc-web@42: rc-web@42: // Select all slides and convert the NodeList result to rc-web@42: // an array rc-web@42: var slides = Array.prototype.slice.call( document.querySelectorAll( selector ) ), rc-web@42: slidesLength = slides.length; rc-web@42: rc-web@42: if( slidesLength ) { rc-web@42: rc-web@42: // Should the index loop? rc-web@42: if( config.loop ) { rc-web@42: index %= slidesLength; rc-web@42: rc-web@42: if( index < 0 ) { rc-web@42: index = slidesLength + index; rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: // Enforce max and minimum index bounds rc-web@42: index = Math.max( Math.min( index, slidesLength - 1 ), 0 ); rc-web@42: rc-web@42: for( var i = 0; i < slidesLength; i++ ) { rc-web@42: var slide = slides[i]; rc-web@42: rc-web@42: // Optimization; hide all slides that are three or more steps rc-web@42: // away from the present slide rc-web@42: if( overviewIsActive() === false ) { rc-web@42: // The distance loops so that it measures 1 between the first rc-web@42: // and last slides rc-web@42: var distance = Math.abs( ( index - i ) % ( slidesLength - 3 ) ) || 0; rc-web@42: rc-web@42: slide.style.display = distance > 3 ? 'none' : 'block'; rc-web@42: } rc-web@42: rc-web@42: slides[i].classList.remove( 'past' ); rc-web@42: slides[i].classList.remove( 'present' ); rc-web@42: slides[i].classList.remove( 'future' ); rc-web@42: rc-web@42: if( i < index ) { rc-web@42: // Any element previous to index is given the 'past' class rc-web@42: slides[i].classList.add( 'past' ); rc-web@42: } rc-web@42: else if( i > index ) { rc-web@42: // Any element subsequent to index is given the 'future' class rc-web@42: slides[i].classList.add( 'future' ); rc-web@42: } rc-web@42: rc-web@42: // If this element contains vertical slides rc-web@42: if( slide.querySelector( 'section' ) ) { rc-web@42: slides[i].classList.add( 'stack' ); rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: // Mark the current slide as present rc-web@42: slides[index].classList.add( 'present' ); rc-web@42: rc-web@42: // If this slide has a state associated with it, add it rc-web@42: // onto the current state of the deck rc-web@42: var slideState = slides[index].getAttribute( 'data-state' ); rc-web@42: if( slideState ) { rc-web@42: state = state.concat( slideState.split( ' ' ) ); rc-web@42: } rc-web@42: } rc-web@42: else { rc-web@42: // Since there are no slides we can't be anywhere beyond the rc-web@42: // zeroth index rc-web@42: index = 0; rc-web@42: } rc-web@42: rc-web@42: return index; rc-web@42: rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Updates the visual slides to represent the currently rc-web@42: * set indices. rc-web@42: */ rc-web@42: function slide( h, v ) { rc-web@42: // Remember where we were at before rc-web@42: previousSlide = currentSlide; rc-web@42: rc-web@42: // Remember the state before this slide rc-web@42: var stateBefore = state.concat(); rc-web@42: rc-web@42: // Reset the state array rc-web@42: state.length = 0; rc-web@42: rc-web@42: var indexhBefore = indexh, rc-web@42: indexvBefore = indexv; rc-web@42: rc-web@42: // Activate and transition to the new slide rc-web@42: indexh = updateSlides( HORIZONTAL_SLIDES_SELECTOR, h === undefined ? indexh : h ); rc-web@42: indexv = updateSlides( VERTICAL_SLIDES_SELECTOR, v === undefined ? indexv : v ); rc-web@42: rc-web@42: // Apply the new state rc-web@42: stateLoop: for( var i = 0, len = state.length; i < len; i++ ) { rc-web@42: // Check if this state existed on the previous slide. If it rc-web@42: // did, we will avoid adding it repeatedly. rc-web@42: for( var j = 0; j < stateBefore.length; j++ ) { rc-web@42: if( stateBefore[j] === state[i] ) { rc-web@42: stateBefore.splice( j, 1 ); rc-web@42: continue stateLoop; rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: document.documentElement.classList.add( state[i] ); rc-web@42: rc-web@42: // Dispatch custom event matching the state's name rc-web@42: dispatchEvent( state[i] ); rc-web@42: } rc-web@42: rc-web@42: // Clean up the remaints of the previous state rc-web@42: while( stateBefore.length ) { rc-web@42: document.documentElement.classList.remove( stateBefore.pop() ); rc-web@42: } rc-web@42: rc-web@42: // Update progress if enabled rc-web@42: if( config.progress && dom.progress ) { rc-web@42: dom.progressbar.style.width = ( indexh / ( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ).length - 1 ) ) * window.innerWidth + 'px'; rc-web@42: } rc-web@42: rc-web@42: // Close the overview if it's active rc-web@42: if( overviewIsActive() ) { rc-web@42: activateOverview(); rc-web@42: } rc-web@42: rc-web@42: updateControls(); rc-web@42: rc-web@42: clearTimeout( writeURLTimeout ); rc-web@42: writeURLTimeout = setTimeout( writeURL, 1500 ); rc-web@42: rc-web@42: // Query all horizontal slides in the deck rc-web@42: var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ); rc-web@42: rc-web@42: // Find the current horizontal slide and any possible vertical slides rc-web@42: // within it rc-web@42: var currentHorizontalSlide = horizontalSlides[ indexh ], rc-web@42: currentVerticalSlides = currentHorizontalSlide.querySelectorAll( 'section' ); rc-web@42: rc-web@42: // Store references to the previous and current slides rc-web@42: currentSlide = currentVerticalSlides[ indexv ] || currentHorizontalSlide; rc-web@42: rc-web@42: // Dispatch an event if the slide changed rc-web@42: if( indexh !== indexhBefore || indexv !== indexvBefore ) { rc-web@42: dispatchEvent( 'slidechanged', { rc-web@42: 'indexh': indexh, rc-web@42: 'indexv': indexv, rc-web@42: 'previousSlide': previousSlide, rc-web@42: 'currentSlide': currentSlide rc-web@42: } ); rc-web@42: } rc-web@42: else { rc-web@42: // Ensure that the previous slide is never the same as the current rc-web@42: previousSlide = null; rc-web@42: } rc-web@42: rc-web@42: // Solves an edge case where the previous slide maintains the rc-web@42: // 'present' class when navigating between adjacent vertical rc-web@42: // stacks rc-web@42: if( previousSlide ) { rc-web@42: previousSlide.classList.remove( 'present' ); rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Updates the state and link pointers of the controls. rc-web@42: */ rc-web@42: function updateControls() { rc-web@42: if ( !config.controls || !dom.controls ) { rc-web@42: return; rc-web@42: } rc-web@42: rc-web@42: var routes = availableRoutes(); rc-web@42: rc-web@42: // Remove the 'enabled' class from all directions rc-web@42: [ dom.controlsLeft, dom.controlsRight, dom.controlsUp, dom.controlsDown ].forEach( function( node ) { rc-web@42: node.classList.remove( 'enabled' ); rc-web@42: } ) rc-web@42: rc-web@42: if( routes.left ) dom.controlsLeft.classList.add( 'enabled' ); rc-web@42: if( routes.right ) dom.controlsRight.classList.add( 'enabled' ); rc-web@42: if( routes.up ) dom.controlsUp.classList.add( 'enabled' ); rc-web@42: if( routes.down ) dom.controlsDown.classList.add( 'enabled' ); rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Determine what available routes there are for navigation. rc-web@42: * rc-web@42: * @return {Object} containing four booleans: left/right/up/down rc-web@42: */ rc-web@42: function availableRoutes() { rc-web@42: var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ); rc-web@42: var verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR ); rc-web@42: rc-web@42: return { rc-web@42: left: indexh > 0, rc-web@42: right: indexh < horizontalSlides.length - 1, rc-web@42: up: indexv > 0, rc-web@42: down: indexv < verticalSlides.length - 1 rc-web@42: }; rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Reads the current URL (hash) and navigates accordingly. rc-web@42: */ rc-web@42: function readURL() { rc-web@42: var hash = window.location.hash; rc-web@42: rc-web@42: // Attempt to parse the hash as either an index or name rc-web@42: var bits = hash.slice( 2 ).split( '/' ), rc-web@42: name = hash.replace( /#|\//gi, '' ); rc-web@42: rc-web@42: // If the first bit is invalid and there is a name we can rc-web@42: // assume that this is a named link rc-web@42: if( isNaN( parseInt( bits[0] ) ) && name.length ) { rc-web@42: // Find the slide with the specified name rc-web@42: var slide = document.querySelector( '#' + name ); rc-web@42: rc-web@42: if( slide ) { rc-web@42: // Find the position of the named slide and navigate to it rc-web@42: var indices = Reveal.getIndices( slide ); rc-web@42: navigateTo( indices.h, indices.v ); rc-web@42: } rc-web@42: // If the slide doesn't exist, navigate to the current slide rc-web@42: else { rc-web@42: navigateTo( indexh, indexv ); rc-web@42: } rc-web@42: } rc-web@42: else { rc-web@42: // Read the index components of the hash rc-web@42: var h = parseInt( bits[0] ) || 0, rc-web@42: v = parseInt( bits[1] ) || 0; rc-web@42: rc-web@42: navigateTo( h, v ); rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Updates the page URL (hash) to reflect the current rc-web@42: * state. rc-web@42: */ rc-web@42: function writeURL() { rc-web@42: if( config.history ) { rc-web@42: var url = '/'; rc-web@42: rc-web@42: // Only include the minimum possible number of components in rc-web@42: // the URL rc-web@42: if( indexh > 0 || indexv > 0 ) url += indexh; rc-web@42: if( indexv > 0 ) url += '/' + indexv; rc-web@42: rc-web@42: window.location.hash = url; rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Dispatches an event of the specified type from the rc-web@42: * reveal DOM element. rc-web@42: */ rc-web@42: function dispatchEvent( type, properties ) { rc-web@42: var event = document.createEvent( "HTMLEvents", 1, 2 ); rc-web@42: event.initEvent( type, true, true ); rc-web@42: extend( event, properties ); rc-web@42: dom.wrapper.dispatchEvent( event ); rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Navigate to the next slide fragment. rc-web@42: * rc-web@42: * @return {Boolean} true if there was a next fragment, rc-web@42: * false otherwise rc-web@42: */ rc-web@42: function nextFragment() { rc-web@42: // Vertical slides: rc-web@42: if( document.querySelector( VERTICAL_SLIDES_SELECTOR + '.present' ) ) { rc-web@42: var verticalFragments = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR + '.present .fragment:not(.visible)' ); rc-web@42: if( verticalFragments.length ) { rc-web@42: verticalFragments[0].classList.add( 'visible' ); rc-web@42: rc-web@42: // Notify subscribers of the change rc-web@42: dispatchEvent( 'fragmentshown', { fragment: verticalFragments[0] } ); rc-web@42: return true; rc-web@42: } rc-web@42: } rc-web@42: // Horizontal slides: rc-web@42: else { rc-web@42: var horizontalFragments = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.present .fragment:not(.visible)' ); rc-web@42: if( horizontalFragments.length ) { rc-web@42: horizontalFragments[0].classList.add( 'visible' ); rc-web@42: rc-web@42: // Notify subscribers of the change rc-web@42: dispatchEvent( 'fragmentshown', { fragment: horizontalFragments[0] } ); rc-web@42: return true; rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: return false; rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Navigate to the previous slide fragment. rc-web@42: * rc-web@42: * @return {Boolean} true if there was a previous fragment, rc-web@42: * false otherwise rc-web@42: */ rc-web@42: function previousFragment() { rc-web@42: // Vertical slides: rc-web@42: if( document.querySelector( VERTICAL_SLIDES_SELECTOR + '.present' ) ) { rc-web@42: var verticalFragments = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR + '.present .fragment.visible' ); rc-web@42: if( verticalFragments.length ) { rc-web@42: verticalFragments[ verticalFragments.length - 1 ].classList.remove( 'visible' ); rc-web@42: rc-web@42: // Notify subscribers of the change rc-web@42: dispatchEvent( 'fragmenthidden', { fragment: verticalFragments[ verticalFragments.length - 1 ] } ); rc-web@42: return true; rc-web@42: } rc-web@42: } rc-web@42: // Horizontal slides: rc-web@42: else { rc-web@42: var horizontalFragments = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.present .fragment.visible' ); rc-web@42: if( horizontalFragments.length ) { rc-web@42: horizontalFragments[ horizontalFragments.length - 1 ].classList.remove( 'visible' ); rc-web@42: rc-web@42: // Notify subscribers of the change rc-web@42: dispatchEvent( 'fragmenthidden', { fragment: horizontalFragments[ horizontalFragments.length - 1 ] } ); rc-web@42: return true; rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: return false; rc-web@42: } rc-web@42: rc-web@42: function cueAutoSlide() { rc-web@42: clearTimeout( autoSlideTimeout ); rc-web@42: rc-web@42: // Cue the next auto-slide if enabled rc-web@42: if( config.autoSlide ) { rc-web@42: autoSlideTimeout = setTimeout( navigateNext, config.autoSlide ); rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Triggers a navigation to the specified indices. rc-web@42: * rc-web@42: * @param {Number} h The horizontal index of the slide to show rc-web@42: * @param {Number} v The vertical index of the slide to show rc-web@42: */ rc-web@42: function navigateTo( h, v ) { rc-web@42: slide( h, v ); rc-web@42: } rc-web@42: rc-web@42: function navigateLeft() { rc-web@42: // Prioritize hiding fragments rc-web@42: if( overviewIsActive() || previousFragment() === false ) { rc-web@42: slide( indexh - 1, 0 ); rc-web@42: } rc-web@42: } rc-web@42: function navigateRight() { rc-web@42: // Prioritize revealing fragments rc-web@42: if( overviewIsActive() || nextFragment() === false ) { rc-web@42: slide( indexh + 1, 0 ); rc-web@42: } rc-web@42: } rc-web@42: function navigateUp() { rc-web@42: // Prioritize hiding fragments rc-web@42: if( overviewIsActive() || previousFragment() === false ) { rc-web@42: slide( indexh, indexv - 1 ); rc-web@42: } rc-web@42: } rc-web@42: function navigateDown() { rc-web@42: // Prioritize revealing fragments rc-web@42: if( overviewIsActive() || nextFragment() === false ) { rc-web@42: slide( indexh, indexv + 1 ); rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Navigates backwards, prioritized in the following order: rc-web@42: * 1) Previous fragment rc-web@42: * 2) Previous vertical slide rc-web@42: * 3) Previous horizontal slide rc-web@42: */ rc-web@42: function navigatePrev() { rc-web@42: // Prioritize revealing fragments rc-web@42: if( previousFragment() === false ) { rc-web@42: if( availableRoutes().up ) { rc-web@42: navigateUp(); rc-web@42: } rc-web@42: else { rc-web@42: // Fetch the previous horizontal slide, if there is one rc-web@42: var previousSlide = document.querySelector( '.reveal .slides>section.past:nth-child(' + indexh + ')' ); rc-web@42: rc-web@42: if( previousSlide ) { rc-web@42: indexv = ( previousSlide.querySelectorAll('section').length + 1 ) || 0; rc-web@42: indexh --; rc-web@42: slide(); rc-web@42: } rc-web@42: } rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Same as #navigatePrev() but navigates forwards. rc-web@42: */ rc-web@42: function navigateNext() { rc-web@42: // Prioritize revealing fragments rc-web@42: if( nextFragment() === false ) { rc-web@42: availableRoutes().down ? navigateDown() : navigateRight(); rc-web@42: } rc-web@42: rc-web@42: // If auto-sliding is enabled we need to cue up rc-web@42: // another timeout rc-web@42: cueAutoSlide(); rc-web@42: } rc-web@42: rc-web@42: /** rc-web@42: * Toggles the slide overview mode on and off. rc-web@42: */ rc-web@42: function toggleOverview() { rc-web@42: if( overviewIsActive() ) { rc-web@42: deactivateOverview(); rc-web@42: } rc-web@42: else { rc-web@42: activateOverview(); rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: // Expose some methods publicly rc-web@42: return { rc-web@42: initialize: initialize, rc-web@42: navigateTo: navigateTo, rc-web@42: navigateLeft: navigateLeft, rc-web@42: navigateRight: navigateRight, rc-web@42: navigateUp: navigateUp, rc-web@42: navigateDown: navigateDown, rc-web@42: navigatePrev: navigatePrev, rc-web@42: navigateNext: navigateNext, rc-web@42: toggleOverview: toggleOverview, rc-web@42: rc-web@42: // Adds or removes all internal event listeners (such as keyboard) rc-web@42: addEventListeners: addEventListeners, rc-web@42: removeEventListeners: removeEventListeners, rc-web@42: rc-web@42: // Returns the indices of the current, or specified, slide rc-web@42: getIndices: function( slide ) { rc-web@42: // By default, return the current indices rc-web@42: var h = indexh, rc-web@42: v = indexv; rc-web@42: rc-web@42: // If a slide is specified, return the indices of that slide rc-web@42: if( slide ) { rc-web@42: var isVertical = !!slide.parentNode.nodeName.match( /section/gi ); rc-web@42: var slideh = isVertical ? slide.parentNode : slide; rc-web@42: rc-web@42: // Select all horizontal slides rc-web@42: var horizontalSlides = Array.prototype.slice.call( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ); rc-web@42: rc-web@42: // Now that we know which the horizontal slide is, get its index rc-web@42: h = Math.max( horizontalSlides.indexOf( slideh ), 0 ); rc-web@42: rc-web@42: // If this is a vertical slide, grab the vertical index rc-web@42: if( isVertical ) { rc-web@42: v = Math.max( Array.prototype.slice.call( slide.parentNode.children ).indexOf( slide ), 0 ); rc-web@42: } rc-web@42: } rc-web@42: rc-web@42: return { h: h, v: v }; rc-web@42: }, rc-web@42: rc-web@42: // Returns the previous slide element, may be null rc-web@42: getPreviousSlide: function() { rc-web@42: return previousSlide rc-web@42: }, rc-web@42: rc-web@42: // Returns the current slide element rc-web@42: getCurrentSlide: function() { rc-web@42: return currentSlide rc-web@42: }, rc-web@42: rc-web@42: // Helper method, retrieves query string as a key/value hash rc-web@42: getQueryHash: function() { rc-web@42: var query = {}; rc-web@42: rc-web@42: location.search.replace( /[A-Z0-9]+?=(\w*)/gi, function(a) { rc-web@42: query[ a.split( '=' ).shift() ] = a.split( '=' ).pop(); rc-web@42: } ); rc-web@42: rc-web@42: return query; rc-web@42: }, rc-web@42: rc-web@42: // Forward event binding to the reveal DOM element rc-web@42: addEventListener: function( type, listener, useCapture ) { rc-web@42: if( 'addEventListener' in window ) { rc-web@42: ( dom.wrapper || document.querySelector( '.reveal' ) ).addEventListener( type, listener, useCapture ); rc-web@42: } rc-web@42: }, rc-web@42: removeEventListener: function( type, listener, useCapture ) { rc-web@42: if( 'addEventListener' in window ) { rc-web@42: ( dom.wrapper || document.querySelector( '.reveal' ) ).removeEventListener( type, listener, useCapture ); rc-web@42: } rc-web@42: } rc-web@42: }; rc-web@42: rc-web@42: })();