reveal.js/js/reveal.js in reveal-ck-0.1.3 vs reveal.js/js/reveal.js in reveal-ck-0.1.4

- old
+ new

@@ -57,20 +57,24 @@ rtl: false, // Turns fragments on and off globally fragments: true, + // Flags if the presentation is running in an embedded mode, + // i.e. contained within a limited portion of the screen + embedded: false, + // 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: false, // Apply a 3D roll to links on hover - rollingLinks: true, + rollingLinks: false, // Opens links in an iframe preview overlay previewLinks: false, // Theme (see /css/theme) @@ -81,22 +85,28 @@ // Transition speed transitionSpeed: 'default', // default/fast/slow // Transition style for full page slide backgrounds - backgroundTransition: 'default', // default/linear + backgroundTransition: 'default', // default/linear/none + // Number of slides away from the current that are visible + viewDistance: 3, + // Script dependencies to load dependencies: [] }, + // Flags if reveal.js is loaded (has dispatched the 'ready' event) + loaded = false, + // The current auto-slide duration autoSlide = 0, // The horizontal and vertical index of the currently active slide - indexh = 0, - indexv = 0, + indexh, + indexv, // The previous and current slide HTML elements previousSlide, currentSlide, @@ -109,24 +119,19 @@ scale = 1, // 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, + // Client support for CSS 3D transforms, see #checkCapabilities() + supports3DTransforms, - // Detect support for CSS 2D transforms - 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, + // Client support for CSS 2D transforms, see #checkCapabilities() + supports2DTransforms, + // Client is a mobile device, see #checkCapabilities() + isMobileDevice, + // Throttles mouse wheel navigation lastMouseWheelStep = 0, // An interval used to automatically move on to the next slide autoSlideTimeout = 0, @@ -147,19 +152,21 @@ touch = { startX: 0, startY: 0, startSpan: 0, startCount: 0, - handled: false, - threshold: 80 + captured: false, + threshold: 40 }; /** * Starts up the presentation if the client is capable. */ function initialize( options ) { + checkCapabilities(); + 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 @@ -179,10 +186,140 @@ load(); } /** + * Inspect the client to see what it's capable of, this + * should only happens once per runtime. + */ + function checkCapabilities() { + + 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; + + isMobileDevice = navigator.userAgent.match( /(iphone|ipod|android)/gi ); + + } + + /** + * 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 scripts 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(); + + // Decorate the slide DOM elements with state classes (past/future) + setupSlides(); + + // Updates the presentation to match the current configuration values + configure(); + + // Read the initial hash + readURL(); + + // Notify listeners that the presentation is ready but use a 1ms + // timeout to ensure it's not fired synchronously after #initialize() + setTimeout( function() { + // Enable transitions now that we're loaded + dom.slides.classList.remove( 'no-transition' ); + + loaded = true; + + dispatchEvent( 'ready', { + 'indexh': indexh, + 'indexv': indexv, + 'currentSlide': currentSlide + } ); + }, 1 ); + + } + + /** + * Iterates through and decorates slides DOM elements with + * appropriate classes. + */ + function setupSlides() { + + var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ); + horizontalSlides.forEach( function( horizontalSlide ) { + + var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) ); + verticalSlides.forEach( function( verticalSlide, y ) { + + if( y > 0 ) verticalSlide.classList.add( 'future' ); + + } ); + + } ); + + } + + /** * 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() { @@ -190,65 +327,63 @@ // Cache references to key DOM elements dom.theme = document.querySelector( '#theme' ); dom.wrapper = document.querySelector( '.reveal' ); dom.slides = document.querySelector( '.reveal .slides' ); + // Prevent transitions while we're loading + dom.slides.classList.add( 'no-transition' ); + // Background element - if( !document.querySelector( '.reveal .backgrounds' ) ) { - dom.background = document.createElement( 'div' ); - dom.background.classList.add( 'backgrounds' ); - dom.wrapper.appendChild( dom.background ); - } + dom.background = createSingletonNode( dom.wrapper, 'div', 'backgrounds', null ); // Progress bar - if( !dom.wrapper.querySelector( '.progress' ) ) { - var progressElement = document.createElement( 'div' ); - progressElement.classList.add( 'progress' ); - progressElement.innerHTML = '<span></span>'; - dom.wrapper.appendChild( progressElement ); - } + dom.progress = createSingletonNode( dom.wrapper, 'div', 'progress', '<span></span>' ); + dom.progressbar = dom.progress.querySelector( 'span' ); // Arrow controls - if( !dom.wrapper.querySelector( '.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 ); - } + createSingletonNode( dom.wrapper, 'aside', 'controls', + '<div class="navigate-left"></div>' + + '<div class="navigate-right"></div>' + + '<div class="navigate-up"></div>' + + '<div class="navigate-down"></div>' ); // State background element [DEPRECATED] - if( !dom.wrapper.querySelector( '.state-background' ) ) { - var stateBackgroundElement = document.createElement( 'div' ); - stateBackgroundElement.classList.add( 'state-background' ); - dom.wrapper.appendChild( stateBackgroundElement ); - } + createSingletonNode( dom.wrapper, 'div', 'state-background', null ); // 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 ); - } + createSingletonNode( dom.wrapper, 'div', 'pause-overlay', null ); // Cache references to elements - dom.progress = document.querySelector( '.reveal .progress' ); - dom.progressbar = document.querySelector( '.reveal .progress span' ); + dom.controls = document.querySelector( '.reveal .controls' ); - if ( config.controls ) { - 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' ) ); - // 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' ) ); + } + + /** + * Creates an HTML element and returns a reference to it. + * If the element already exists the existing instance will + * be returned. + */ + function createSingletonNode( container, tagname, classname, innerHTML ) { + + var node = container.querySelector( '.' + classname ); + if( !node ) { + node = document.createElement( tagname ); + node.classList.add( classname ); + if( innerHTML !== null ) { + node.innerHTML = innerHTML; + } + container.appendChild( node ); } + return node; } /** * Creates the slide background elements and appends them @@ -333,103 +468,10 @@ } ); } /** - * Hides the address bar if we're on a mobile device. - */ - function hideAddressBar() { - - if( /iphone|ipod|android/gi.test( navigator.userAgent ) && !/crios/gi.test( navigator.userAgent ) ) { - // 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 scripts 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(); - - // Updates the presentation to match the current configuration values - configure(); - - // Read the initial hash - readURL(); - - // 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. May be called multiple times. */ function configure( options ) { @@ -445,18 +487,13 @@ dom.wrapper.classList.add( config.transition ); dom.wrapper.setAttribute( 'data-transition-speed', config.transitionSpeed ); dom.wrapper.setAttribute( 'data-background-transition', config.backgroundTransition ); - if( dom.controls ) { - dom.controls.style.display = ( config.controls && dom.controls ) ? 'block' : 'none'; - } + dom.controls.style.display = config.controls ? 'block' : 'none'; + dom.progress.style.display = config.progress ? 'block' : 'none'; - if( dom.progress ) { - dom.progress.style.display = ( config.progress && dom.progress ) ? 'block' : 'none'; - } - if( config.rtl ) { dom.wrapper.classList.add( 'rtl' ); } else { dom.wrapper.classList.remove( 'rtl' ); @@ -540,20 +577,18 @@ if ( config.progress && dom.progress ) { dom.progress.addEventListener( 'click', onProgressClicked, false ); } - if ( config.controls && dom.controls ) { - [ 'touchstart', 'click' ].forEach( function( eventName ) { - dom.controlsLeft.forEach( function( el ) { el.addEventListener( eventName, onNavigateLeftClicked, false ); } ); - dom.controlsRight.forEach( function( el ) { el.addEventListener( eventName, onNavigateRightClicked, false ); } ); - dom.controlsUp.forEach( function( el ) { el.addEventListener( eventName, onNavigateUpClicked, false ); } ); - dom.controlsDown.forEach( function( el ) { el.addEventListener( eventName, onNavigateDownClicked, false ); } ); - dom.controlsPrev.forEach( function( el ) { el.addEventListener( eventName, onNavigatePrevClicked, false ); } ); - dom.controlsNext.forEach( function( el ) { el.addEventListener( eventName, onNavigateNextClicked, false ); } ); - } ); - } + [ 'touchstart', 'click' ].forEach( function( eventName ) { + dom.controlsLeft.forEach( function( el ) { el.addEventListener( eventName, onNavigateLeftClicked, false ); } ); + dom.controlsRight.forEach( function( el ) { el.addEventListener( eventName, onNavigateRightClicked, false ); } ); + dom.controlsUp.forEach( function( el ) { el.addEventListener( eventName, onNavigateUpClicked, false ); } ); + dom.controlsDown.forEach( function( el ) { el.addEventListener( eventName, onNavigateDownClicked, false ); } ); + dom.controlsPrev.forEach( function( el ) { el.addEventListener( eventName, onNavigatePrevClicked, false ); } ); + dom.controlsNext.forEach( function( el ) { el.addEventListener( eventName, onNavigateNextClicked, false ); } ); + } ); } /** * Unbinds all event listeners. @@ -578,20 +613,18 @@ if ( config.progress && dom.progress ) { dom.progress.removeEventListener( 'click', onProgressClicked, false ); } - if ( config.controls && dom.controls ) { - [ 'touchstart', 'click' ].forEach( function( eventName ) { - dom.controlsLeft.forEach( function( el ) { el.removeEventListener( eventName, onNavigateLeftClicked, false ); } ); - dom.controlsRight.forEach( function( el ) { el.removeEventListener( eventName, onNavigateRightClicked, false ); } ); - dom.controlsUp.forEach( function( el ) { el.removeEventListener( eventName, onNavigateUpClicked, false ); } ); - dom.controlsDown.forEach( function( el ) { el.removeEventListener( eventName, onNavigateDownClicked, false ); } ); - dom.controlsPrev.forEach( function( el ) { el.removeEventListener( eventName, onNavigatePrevClicked, false ); } ); - dom.controlsNext.forEach( function( el ) { el.removeEventListener( eventName, onNavigateNextClicked, false ); } ); - } ); - } + [ 'touchstart', 'click' ].forEach( function( eventName ) { + dom.controlsLeft.forEach( function( el ) { el.removeEventListener( eventName, onNavigateLeftClicked, false ); } ); + dom.controlsRight.forEach( function( el ) { el.removeEventListener( eventName, onNavigateRightClicked, false ); } ); + dom.controlsUp.forEach( function( el ) { el.removeEventListener( eventName, onNavigateUpClicked, false ); } ); + dom.controlsDown.forEach( function( el ) { el.removeEventListener( eventName, onNavigateDownClicked, false ); } ); + dom.controlsPrev.forEach( function( el ) { el.removeEventListener( eventName, onNavigatePrevClicked, false ); } ); + dom.controlsNext.forEach( function( el ) { el.removeEventListener( eventName, onNavigateNextClicked, false ); } ); + } ); } /** * Extend object a with the properties of object b. @@ -629,10 +662,23 @@ return Math.sqrt( dx*dx + dy*dy ); } /** + * Applies a CSS transform to the target element. + */ + function transformElement( element, transform ) { + + element.style.WebkitTransform = transform; + element.style.MozTransform = transform; + element.style.msTransform = transform; + element.style.OTransform = transform; + element.style.transform = transform; + + } + + /** * Retrieves the height of the given element by looking * at the position and height of its immediate children. */ function getAbsoluteHeight( element ) { @@ -664,25 +710,81 @@ return height; } /** + * Returns the remaining height within the parent of the + * target element after subtracting the height of all + * siblings. + * + * remaining height = [parent height] - [ siblings height] + */ + function getRemainingHeight( element, height ) { + + height = height || 0; + + if( element ) { + var parent = element.parentNode; + var siblings = parent.childNodes; + + // Subtract the height of each sibling + toArray( siblings ).forEach( function( sibling ) { + + if( typeof sibling.offsetHeight === 'number' && sibling !== element ) { + + var styles = window.getComputedStyle( sibling ), + marginTop = parseInt( styles.marginTop, 10 ), + marginBottom = parseInt( styles.marginBottom, 10 ); + + height -= sibling.offsetHeight + marginTop + marginBottom; + + } + + } ); + + var elementStyles = window.getComputedStyle( element ); + + // Subtract the margins of the target element + height -= parseInt( elementStyles.marginTop, 10 ) + + parseInt( elementStyles.marginBottom, 10 ); + + } + + return height; + + } + + /** * Checks if this instance is being used to print a PDF. */ function isPrintingPDF() { return ( /print-pdf/gi ).test( window.location.search ); } /** + * Hides the address bar if we're on a mobile device. + */ + function hideAddressBar() { + + if( isMobileDevice ) { + // Events that should trigger the address bar to hide + window.addEventListener( 'load', removeAddressBar, false ); + window.addEventListener( 'orientationchange', removeAddressBar, false ); + } + + } + + /** * Causes the address bar to hide on mobile devices, * more vertical space ftw. */ function removeAddressBar() { - if( window.orientation === 0 ) { + // Portrait and not Chrome for iOS + if( window.orientation === 0 && !/crios/gi.test( navigator.userAgent ) ) { document.documentElement.style.overflow = 'scroll'; document.body.style.height = '120%'; } else { document.documentElement.style.overflow = ''; @@ -884,12 +986,16 @@ availableWidth -= ( availableHeight * config.margin ); availableHeight -= ( availableHeight * config.margin ); // Dimensions of the content var slideWidth = config.width, - slideHeight = config.height; + slideHeight = config.height, + slidePadding = 20; // TODO Dig this out of DOM + // Layout the contents of the slides + layoutSlideContents( config.width, config.height, slidePadding ); + // Slide width may be a percentage of available width if( typeof slideWidth === 'string' && /%$/.test( slideWidth ) ) { slideWidth = parseInt( slideWidth, 10 ) / 100 * availableWidth; } @@ -913,17 +1019,11 @@ if( typeof dom.slides.style.zoom !== 'undefined' && !navigator.userAgent.match( /(iphone|ipod|ipad|android)/gi ) ) { dom.slides.style.zoom = scale; } // Apply scale transform as a fallback else { - var transform = 'translate(-50%, -50%) scale('+ scale +') translate(50%, 50%)'; - - dom.slides.style.WebkitTransform = transform; - dom.slides.style.MozTransform = transform; - dom.slides.style.msTransform = transform; - dom.slides.style.OTransform = transform; - dom.slides.style.transform = transform; + transformElement( dom.slides, 'translate(-50%, -50%) scale('+ scale +') translate(50%, 50%)' ); } // Select all slides, vertical and horizontal var slides = toArray( document.querySelectorAll( SLIDES_SELECTOR ) ); @@ -940,11 +1040,11 @@ // children will be if( slide.classList.contains( 'stack' ) ) { slide.style.top = 0; } else { - slide.style.top = Math.max( - ( getAbsoluteHeight( slide ) / 2 ) - 20, -slideHeight / 2 ) + 'px'; + slide.style.top = Math.max( - ( getAbsoluteHeight( slide ) / 2 ) - slidePadding, -slideHeight / 2 ) + 'px'; } } else { slide.style.top = ''; } @@ -956,10 +1056,42 @@ } } /** + * Applies layout logic to the contents of all slides in + * the presentation. + */ + function layoutSlideContents( width, height, padding ) { + + // Handle sizing of elements with the 'stretch' class + toArray( dom.slides.querySelectorAll( 'section > .stretch' ) ).forEach( function( element ) { + + // Determine how much vertical space we can use + var remainingHeight = getRemainingHeight( element, ( height - ( padding * 2 ) ) ); + + // Consider the aspect ratio of media elements + if( /(img|video)/gi.test( element.nodeName ) ) { + var nw = element.naturalWidth || element.videoWidth, + nh = element.naturalHeight || element.videoHeight; + + var es = Math.min( width / nw, remainingHeight / nh ); + + element.style.width = ( nw * es ) + 'px'; + element.style.height = ( nh * es ) + 'px'; + + } + else { + element.style.width = width + 'px'; + element.style.height = remainingHeight + '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 @@ -1008,55 +1140,50 @@ // Don't auto-slide while in overview mode cancelAutoSlide(); var wasActive = dom.wrapper.classList.contains( 'overview' ); + // Vary the depth of the overview based on screen size + var depth = window.innerWidth < 400 ? 1000 : 2500; + dom.wrapper.classList.add( 'overview' ); - dom.wrapper.classList.remove( 'exit-overview' ); + dom.wrapper.classList.remove( 'overview-deactivating' ); clearTimeout( activateOverviewTimeout ); clearTimeout( deactivateOverviewTimeout ); // Not the pretties solution, but need to let the overview // class apply first so that slides are measured accurately // before we can position them - activateOverviewTimeout = setTimeout( function(){ + activateOverviewTimeout = setTimeout( function() { var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ); for( var i = 0, len1 = horizontalSlides.length; i < len1; i++ ) { var hslide = horizontalSlides[i], - hoffset = config.rtl ? -105 : 105, - htransform = 'translateZ(-2500px) translate(' + ( ( i - indexh ) * hoffset ) + '%, 0%)'; + hoffset = config.rtl ? -105 : 105; hslide.setAttribute( 'data-index-h', i ); - hslide.style.display = 'block'; - hslide.style.WebkitTransform = htransform; - hslide.style.MozTransform = htransform; - hslide.style.msTransform = htransform; - hslide.style.OTransform = htransform; - hslide.style.transform = htransform; + // Apply CSS transform + transformElement( hslide, 'translateZ(-'+ depth +'px) translate(' + ( ( i - indexh ) * hoffset ) + '%, 0%)' ); + if( hslide.classList.contains( 'stack' ) ) { var verticalSlides = hslide.querySelectorAll( 'section' ); for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) { var verticalIndex = i === indexh ? indexv : getPreviousVerticalIndex( hslide ); - var vslide = verticalSlides[j], - vtransform = 'translate(0%, ' + ( ( j - verticalIndex ) * 105 ) + '%)'; + var vslide = verticalSlides[j]; vslide.setAttribute( 'data-index-h', i ); vslide.setAttribute( 'data-index-v', j ); - vslide.style.display = 'block'; - vslide.style.WebkitTransform = vtransform; - vslide.style.MozTransform = vtransform; - vslide.style.msTransform = vtransform; - vslide.style.OTransform = vtransform; - vslide.style.transform = vtransform; + // Apply CSS transform + transformElement( vslide, 'translate(0%, ' + ( ( j - verticalIndex ) * 105 ) + '%)' ); + // Navigate to this slide on click vslide.addEventListener( 'click', onOverviewSlideClicked, true ); } } @@ -1066,10 +1193,12 @@ hslide.addEventListener( 'click', onOverviewSlideClicked, true ); } } + updateSlidesVisibility(); + layout(); if( !wasActive ) { // Notify observers of the overview showing dispatchEvent( 'overviewshown', { @@ -1100,33 +1229,23 @@ dom.wrapper.classList.remove( 'overview' ); // Temporarily add a class so that transitions can do different things // depending on whether they are exiting/entering overview, or just // moving from slide to slide - dom.wrapper.classList.add( 'exit-overview' ); + dom.wrapper.classList.add( 'overview-deactivating' ); deactivateOverviewTimeout = setTimeout( function () { - dom.wrapper.classList.remove( 'exit-overview' ); - }, 10); + dom.wrapper.classList.remove( 'overview-deactivating' ); + }, 1 ); // Select all slides - var slides = toArray( document.querySelectorAll( SLIDES_SELECTOR ) ); - - for( var i = 0, len = slides.length; i < len; i++ ) { - var element = slides[i]; - - element.style.display = ''; - + toArray( document.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) { // Resets all transforms to use the external styles - element.style.WebkitTransform = ''; - element.style.MozTransform = ''; - element.style.msTransform = ''; - element.style.OTransform = ''; - element.style.transform = ''; + transformElement( slide, '' ); - element.removeEventListener( 'click', onOverviewSlideClicked, true ); - } + slide.removeEventListener( 'click', onOverviewSlideClicked, true ); + } ); slide( indexh, indexv ); cueAutoSlide(); @@ -1180,11 +1299,11 @@ function isVerticalSlide( slide ) { // Prefer slide argument, otherwise use current slide slide = slide ? slide : currentSlide; - return slide && !!slide.parentNode.nodeName.match( /section/i ); + return slide && slide.parentNode && !!slide.parentNode.nodeName.match( /section/i ); } /** * Handling the fullscreen functionality via the fullscreen API @@ -1300,17 +1419,20 @@ var stateBefore = state.concat(); // Reset the state array state.length = 0; - var indexhBefore = indexh, - indexvBefore = indexv; + var indexhBefore = indexh || 0, + indexvBefore = indexv || 0; // Activate and transition to the new slide indexh = updateSlides( HORIZONTAL_SLIDES_SELECTOR, h === undefined ? indexh : h ); indexv = updateSlides( VERTICAL_SLIDES_SELECTOR, v === undefined ? indexv : v ); + // Update the visibility of slides now that the indices have changed + updateSlidesVisibility(); + layout(); // Apply the new state stateLoop: for( var i = 0, len = state.length; i < len; i++ ) { // Check if this state existed on the previous slide. If it @@ -1336,14 +1458,10 @@ // If the overview is active, re-activate it to update positions if( isOverview() ) { activateOverview(); } - // Update the URL hash after a delay since updating it mid-transition - // is likely to cause visual lag - writeURL( 1500 ); - // Find the current horizontal slide and any possible vertical slides // within it var currentHorizontalSlide = horizontalSlides[ indexh ], currentVerticalSlides = currentHorizontalSlide.querySelectorAll( 'section' ); @@ -1411,10 +1529,13 @@ updateControls(); updateProgress(); updateBackground(); + // Update the URL hash + writeURL(); + } /** * Syncs the presentation with the current DOM. Useful * when new slides or control elements are added or when @@ -1479,20 +1600,10 @@ index = Math.max( Math.min( index, slidesLength - 1 ), 0 ); for( var i = 0; i < slidesLength; i++ ) { var element = slides[i]; - // Optimization; hide all slides that are three or more steps - // away from the present slide - if( isOverview() === false ) { - // The distance loops so that it measures 1 between the first - // and last slides - var distance = Math.abs( ( index - i ) % ( slidesLength - 3 ) ) || 0; - - element.style.display = distance > 3 ? 'none' : 'block'; - } - var reverse = config.rtl && !isVerticalSlide( element ); element.classList.remove( 'past' ); element.classList.remove( 'present' ); element.classList.remove( 'future' ); @@ -1505,10 +1616,17 @@ element.classList.add( reverse ? 'future' : 'past' ); } else if( i > index ) { // Any element subsequent to index is given the 'future' class element.classList.add( reverse ? 'past' : 'future' ); + + var fragments = toArray( element.querySelectorAll( '.fragment.visible' ) ); + + // No fragments in future slides should be visible ahead of time + while( fragments.length ) { + fragments.pop().classList.remove( 'visible' ); + } } // If this element contains vertical slides if( element.querySelector( 'section' ) ) { element.classList.add( 'stack' ); @@ -1524,20 +1642,22 @@ var slideState = slides[index].getAttribute( 'data-state' ); if( slideState ) { state = state.concat( slideState.split( ' ' ) ); } - // If this slide has a data-autoslide attribtue associated use this as + // If this slide has a data-autoslide attribute associated use this as // autoSlide value otherwise use the global configured time var slideAutoSlide = slides[index].getAttribute( 'data-autoslide' ); if( slideAutoSlide ) { autoSlide = parseInt( slideAutoSlide, 10 ); } else { autoSlide = config.autoSlide; } + cueAutoSlide(); + } else { // Since there are no slides we can't be anywhere beyond the // zeroth index index = 0; @@ -1546,10 +1666,65 @@ return index; } /** + * Optimization method; hide all slides that are far away + * from the present slide. + */ + function updateSlidesVisibility() { + + // Select all slides and convert the NodeList result to + // an array + var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ), + horizontalSlidesLength = horizontalSlides.length, + distanceX, + distanceY; + + if( horizontalSlidesLength ) { + + // The number of steps away from the present slide that will + // be visible + var viewDistance = isOverview() ? 10 : config.viewDistance; + + // Limit view distance on weaker devices + if( isMobileDevice ) { + viewDistance = isOverview() ? 6 : 1; + } + + for( var x = 0; x < horizontalSlidesLength; x++ ) { + var horizontalSlide = horizontalSlides[x]; + + var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) ), + verticalSlidesLength = verticalSlides.length; + + // Loops so that it measures 1 between the first and last slides + distanceX = Math.abs( ( indexh - x ) % ( horizontalSlidesLength - viewDistance ) ) || 0; + + // Show the horizontal slide if it's within the view distance + horizontalSlide.style.display = distanceX > viewDistance ? 'none' : 'block'; + + if( verticalSlidesLength ) { + + var oy = getPreviousVerticalIndex( horizontalSlide ); + + for( var y = 0; y < verticalSlidesLength; y++ ) { + var verticalSlide = verticalSlides[y]; + + distanceY = x === indexh ? Math.abs( indexv - y ) : Math.abs( y - oy ); + + verticalSlide.style.display = ( distanceX + distanceY ) > viewDistance ? 'none' : 'block'; + } + + } + } + + } + + } + + /** * Updates the progress bar to reflect the current slide. */ function updateProgress() { // Update progress if enabled @@ -1599,53 +1774,50 @@ /** * Updates the state of all control/navigation arrows. */ function updateControls() { - if ( config.controls && dom.controls ) { + var routes = availableRoutes(); + var fragments = availableFragments(); - var routes = availableRoutes(); - var fragments = availableFragments(); + // 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' ); + node.classList.remove( 'fragmented' ); + } ); - // 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' ); - node.classList.remove( 'fragmented' ); - } ); + // 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' ); } ); - // 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' ); } ); + // Prev/next buttons + 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' ); } ); - // Prev/next buttons - 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' ); } ); + // Highlight fragment directions + if( currentSlide ) { - // Highlight fragment directions - if( currentSlide ) { + // Always apply fragment decorator to prev/next buttons + if( fragments.prev ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } ); + if( fragments.next ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } ); - // Always apply fragment decorator to prev/next buttons - if( fragments.prev ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } ); - if( fragments.next ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } ); - - // Apply fragment decorators to directional buttons based on - // what slide axis they are in - if( isVerticalSlide( currentSlide ) ) { - if( fragments.prev ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } ); - if( fragments.next ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } ); - } - else { - if( fragments.prev ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } ); - if( fragments.next ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } ); - } + // Apply fragment decorators to directional buttons based on + // what slide axis they are in + if( isVerticalSlide( currentSlide ) ) { + if( fragments.prev ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } ); + if( fragments.next ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } ); } + else { + if( fragments.prev ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } ); + if( fragments.next ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } ); + } } } @@ -1801,19 +1973,21 @@ var indices = Reveal.getIndices( element ); slide( indices.h, indices.v ); } // If the slide doesn't exist, navigate to the current slide else { - slide( indexh, indexv ); + slide( indexh || 0, indexv || 0 ); } } else { // Read the index components of the hash var h = parseInt( bits[0], 10 ) || 0, v = parseInt( bits[1], 10 ) || 0; - slide( h, v ); + if( h !== indexh || v !== indexv ) { + slide( h, v ); + } } } /** @@ -1886,12 +2060,13 @@ v = Math.max( toArray( slide.parentNode.querySelectorAll( 'section' ) ).indexOf( slide ), 0 ); } } if( !slide && currentSlide ) { - var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' ); - if( visibleFragments.length ) { + var hasFragments = currentSlide.querySelectorAll( '.fragment' ).length > 0; + if( hasFragments ) { + var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' ); f = visibleFragments.length; } } return { h: h, v: v, f: f }; @@ -2117,11 +2292,11 @@ // Check if this binding matches the pressed key if( parseInt( key, 10 ) === event.keyCode ) { var value = config.keyboard[ key ]; - // Calback function + // Callback function if( typeof value === 'function' ) { value.apply( null, [ event ] ); } // String shortcuts to reveal.js API else if( typeof value === 'string' && typeof Reveal[ value ] === 'function' ) { @@ -2176,11 +2351,12 @@ // 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 ) { + // ESC or O key + else if ( ( event.keyCode === 27 || event.keyCode === 79 ) && supports3DTransforms ) { toggleOverview(); event.preventDefault(); } @@ -2218,15 +2394,15 @@ * Handler for the 'touchmove' event. */ function onTouchMove( event ) { // Each touch should only trigger one action - if( !touch.handled ) { + if( !touch.captured ) { var currentX = event.touches[0].clientX; var currentY = event.touches[0].clientY; - // If the touch started off with two points and still has + // 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( { @@ -2238,11 +2414,11 @@ } ); // 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; + touch.captured = true; if( currentSpan < touch.startSpan ) { activateOverview(); } else { @@ -2258,27 +2434,38 @@ var deltaX = currentX - touch.startX, deltaY = currentY - touch.startY; if( deltaX > touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) { - touch.handled = true; + touch.captured = true; navigateLeft(); } else if( deltaX < -touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) { - touch.handled = true; + touch.captured = true; navigateRight(); } else if( deltaY > touch.threshold ) { - touch.handled = true; + touch.captured = true; navigateUp(); } else if( deltaY < -touch.threshold ) { - touch.handled = true; + touch.captured = true; navigateDown(); } - event.preventDefault(); + // If we're embedded, only block touch events if they have + // triggered an action + if( config.embedded ) { + 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 @@ -2291,11 +2478,11 @@ /** * Handler for the 'touchend' event. */ function onTouchEnd( event ) { - touch.handled = false; + touch.captured = false; } /** * Convert pointer down to touch start. @@ -2551,15 +2738,25 @@ return document.querySelector( SLIDES_SELECTOR + '.past' ) == null ? true : false; }, // Returns true if we're currently on the last slide isLastSlide: function() { - if( currentSlide && currentSlide.classList.contains( '.stack' ) ) { - return currentSlide.querySelector( SLIDES_SELECTOR + '.future' ) == null ? true : false; + 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; } - else { - return document.querySelector( SLIDES_SELECTOR + '.future' ) == null ? true : false; - } + + return false; + }, + + // 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 ) { if( 'addEventListener' in window ) {