diff options
Diffstat (limited to 'js')
-rw-r--r-- | js/reveal.js | 282 |
1 files changed, 204 insertions, 78 deletions
diff --git a/js/reveal.js b/js/reveal.js index 0bdcd12..021c8d9 100644 --- a/js/reveal.js +++ b/js/reveal.js @@ -1,6 +1,6 @@ /*! * reveal.js - * http://lab.hakim.se/reveal-js + * http://revealjs.com * MIT licensed * * Copyright (C) 2017 Hakim El Hattab, http://hakim.se @@ -26,7 +26,7 @@ var Reveal; // The reveal.js version - var VERSION = '3.5.0'; + var VERSION = '3.6.0'; var SLIDES_SELECTOR = '.slides section', HORIZONTAL_SLIDES_SELECTOR = '.slides>section', @@ -49,9 +49,20 @@ minScale: 0.2, maxScale: 2.0, - // Display controls in the bottom right corner + // Display presentation control arrows controls: true, + // Help the user learn the controls by providing hints, for example by + // bouncing the down arrow when they first encounter a vertical slide + controlsTutorial: true, + + // Determines where controls appear, "edges" or "bottom-right" + controlsLayout: 'bottom-right', + + // Visibility rule for backwards navigation arrows; "faded", "hidden" + // or "visible" + controlsBackArrows: 'faded', + // Display a presentation progress bar progress: true, @@ -106,14 +117,16 @@ showNotes: false, // Global override for autolaying embedded media (video/audio/iframe) - // - null: Media will only autoplay if data-autoplay is present - // - true: All media will autoplay, regardless of individual setting - // - false: No media will autoplay, regardless of individual setting + // - null: Media will only autoplay if data-autoplay is present + // - true: All media will autoplay, regardless of individual setting + // - false: No media will autoplay, regardless of individual setting autoPlayMedia: null, - // 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 + // Controls automatic progression to the next slide + // - 0: Auto-sliding only happens if the data-autoslide HTML attribute + // is present on the current slide or fragment + // - 1+: All slides will progress automatically at the given interval + // - false: No auto-sliding, even if data-autoslide is present autoSlide: 0, // Stop auto-sliding after user input @@ -207,6 +220,10 @@ previousBackground, + // Remember which directions that the user has navigated towards + hasNavigatedRight = false, + hasNavigatedDown = false, + // 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 // all current slides. @@ -447,6 +464,8 @@ */ function start() { + loaded = true; + // Make sure we've got all the DOM elements we need setupDOM(); @@ -474,8 +493,6 @@ // Enable transitions now that we're loaded dom.slides.classList.remove( 'no-transition' ); - loaded = true; - dom.wrapper.classList.add( 'ready' ); dispatchEvent( 'ready', { @@ -511,6 +528,20 @@ // Prevent transitions while we're loading dom.slides.classList.add( 'no-transition' ); + if( isMobileDevice ) { + dom.wrapper.classList.add( 'no-hover' ); + } + else { + dom.wrapper.classList.remove( 'no-hover' ); + } + + if( /iphone/gi.test( UA ) ) { + dom.wrapper.classList.add( 'ua-iphone' ); + } + else { + dom.wrapper.classList.remove( 'ua-iphone' ); + } + // Background element dom.background = createSingletonNode( dom.wrapper, 'div', 'backgrounds', null ); @@ -519,11 +550,11 @@ dom.progressbar = dom.progress.querySelector( 'span' ); // Arrow controls - createSingletonNode( dom.wrapper, 'aside', 'controls', - '<button class="navigate-left" aria-label="previous slide"></button>' + - '<button class="navigate-right" aria-label="next slide"></button>' + - '<button class="navigate-up" aria-label="above slide"></button>' + - '<button class="navigate-down" aria-label="below slide"></button>' ); + dom.controls = createSingletonNode( dom.wrapper, 'aside', 'controls', + '<button class="navigate-left" aria-label="previous slide"><div class="controls-arrow"></div></button>' + + '<button class="navigate-right" aria-label="next slide"><div class="controls-arrow"></div></button>' + + '<button class="navigate-up" aria-label="above slide"><div class="controls-arrow"></div></button>' + + '<button class="navigate-down" aria-label="below slide"><div class="controls-arrow"></div></button>' ); // Slide number dom.slideNumber = createSingletonNode( dom.wrapper, 'div', 'slide-number', '' ); @@ -536,9 +567,6 @@ // Overlay graphic which is displayed during the paused mode createSingletonNode( dom.wrapper, 'div', 'pause-overlay', null ); - // Cache references to elements - dom.controls = document.querySelector( '.reveal .controls' ); - dom.wrapper.setAttribute( 'role', 'application' ); // There can be multiple instances of controls throughout the page @@ -549,6 +577,10 @@ dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) ); dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) ); + // The right and down arrows in the standard reveal.js controls + dom.controlsRightArrow = dom.controls.querySelector( '.navigate-right' ); + dom.controlsDownArrow = dom.controls.querySelector( '.navigate-down' ); + dom.statusDiv = createStatusDiv(); } @@ -792,7 +824,7 @@ // If no node was found, create it now var node = document.createElement( tagname ); - node.classList.add( classname ); + node.className = classname; if( typeof innerHTML === 'string' ) { node.innerHTML = innerHTML; } @@ -985,14 +1017,22 @@ */ function configure( options ) { - var numberOfSlides = dom.wrapper.querySelectorAll( SLIDES_SELECTOR ).length; - - dom.wrapper.classList.remove( config.transition ); + var oldTransition = config.transition; // New config options may be passed when this method // is invoked through the API after initialization if( typeof options === 'object' ) extend( config, options ); + // Abort if reveal.js hasn't finished loading, config + // changes will be applied automatically once loading + // finishes + if( loaded === false ) return; + + var numberOfSlides = dom.wrapper.querySelectorAll( SLIDES_SELECTOR ).length; + + // Remove the previously configured transition class + dom.wrapper.classList.remove( oldTransition ); + // Force linear transition based on browser capabilities if( features.transforms3d === false ) config.transition = 'linear'; @@ -1004,6 +1044,9 @@ dom.controls.style.display = config.controls ? 'block' : 'none'; dom.progress.style.display = config.progress ? 'block' : 'none'; + dom.controls.setAttribute( 'data-controls-layout', config.controlsLayout ); + dom.controls.setAttribute( 'data-controls-back-arrows', config.controlsBackArrows ); + if( config.shuffle ) { shuffle(); } @@ -1028,12 +1071,8 @@ } if( config.showNotes ) { - dom.speakerNotes.classList.add( 'visible' ); dom.speakerNotes.setAttribute( 'data-layout', typeof config.showNotes === 'string' ? config.showNotes : 'inline' ); } - else { - dom.speakerNotes.classList.remove( 'visible' ); - } if( config.mouseWheel ) { document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF @@ -1265,6 +1304,8 @@ a[ i ] = b[ i ]; } + return a; + } /** @@ -1291,7 +1332,7 @@ if( value === 'null' ) return null; else if( value === 'true' ) return true; else if( value === 'false' ) return false; - else if( value.match( /^[\d\.]+$/ ) ) return parseFloat( value ); + else if( value.match( /^-?[\d\.]+$/ ) ) return parseFloat( value ); } return value; @@ -2500,13 +2541,14 @@ updateSlideNumber(); updateSlidesVisibility(); updateBackground( true ); + updateNotesVisibility(); updateNotes(); formatEmbeddedContent(); // Start or stop embedded content depending on global config if( config.autoPlayMedia === false ) { - stopEmbeddedContent( currentSlide ); + stopEmbeddedContent( currentSlide, { unloadIframes: false } ); } else { startEmbeddedContent( currentSlide ); @@ -2743,10 +2785,10 @@ // Show the horizontal slide if it's within the view distance if( distanceX < viewDistance ) { - showSlide( horizontalSlide ); + loadSlide( horizontalSlide ); } else { - hideSlide( horizontalSlide ); + unloadSlide( horizontalSlide ); } if( verticalSlidesLength ) { @@ -2759,16 +2801,32 @@ distanceY = x === ( indexh || 0 ) ? Math.abs( ( indexv || 0 ) - y ) : Math.abs( y - oy ); if( distanceX + distanceY < viewDistance ) { - showSlide( verticalSlide ); + loadSlide( verticalSlide ); } else { - hideSlide( verticalSlide ); + unloadSlide( verticalSlide ); } } } } + // Flag if there are ANY vertical slides, anywhere in the deck + if( dom.wrapper.querySelectorAll( '.slides>section>section' ).length ) { + dom.wrapper.classList.add( 'has-vertical-slides' ); + } + else { + dom.wrapper.classList.remove( 'has-vertical-slides' ); + } + + // Flag if there are ANY horizontal slides, anywhere in the deck + if( dom.wrapper.querySelectorAll( '.slides>section' ).length > 1 ) { + dom.wrapper.classList.add( 'has-horizontal-slides' ); + } + else { + dom.wrapper.classList.remove( 'has-horizontal-slides' ); + } + } } @@ -2783,13 +2841,40 @@ if( config.showNotes && dom.speakerNotes && currentSlide && !isPrintingPDF() ) { - dom.speakerNotes.innerHTML = getSlideNotes() || ''; + dom.speakerNotes.innerHTML = getSlideNotes() || '<span class="notes-placeholder">No notes on this slide.</span>'; } } /** + * Updates the visibility of the speaker notes sidebar that + * is used to share annotated slides. The notes sidebar is + * only visible if showNotes is true and there are notes on + * one or more slides in the deck. + */ + function updateNotesVisibility() { + + if( config.showNotes && hasNotes() ) { + dom.wrapper.classList.add( 'show-notes' ); + } + else { + dom.wrapper.classList.remove( 'show-notes' ); + } + + } + + /** + * Checks if there are speaker notes for ANY slide in the + * presentation. + */ + function hasNotes() { + + return dom.slides.querySelectorAll( '[data-notes], aside.notes' ).length > 0; + + } + + /** * Updates the progress bar to reflect the current slide. */ function updateProgress() { @@ -2919,6 +3004,26 @@ } + if( config.controlsTutorial ) { + + // Highlight control arrows with an animation to ensure + // that the viewer knows how to navigate + if( !hasNavigatedDown && routes.down ) { + dom.controlsDownArrow.classList.add( 'highlight' ); + } + else { + dom.controlsDownArrow.classList.remove( 'highlight' ); + + if( !hasNavigatedRight && routes.right && indexv === 0 ) { + dom.controlsRightArrow.classList.add( 'highlight' ); + } + else { + dom.controlsRightArrow.classList.remove( 'highlight' ); + } + } + + } + } /** @@ -3098,14 +3203,9 @@ * * @param {HTMLElement} slide Slide to show */ - /** - * Called when the given slide is within the configured view - * distance. Shows the slide element and loads any content - * that is set to load lazily (data-src). - * - * @param {HTMLElement} slide Slide to show - */ - function showSlide( slide ) { + function loadSlide( slide, options ) { + + options = options || {}; // Show the slide element slide.style.display = config.display; @@ -3113,6 +3213,7 @@ // Media elements with data-src attributes toArray( slide.querySelectorAll( 'img[data-src], video[data-src], audio[data-src]' ) ).forEach( function( element ) { element.setAttribute( 'src', element.getAttribute( 'data-src' ) ); + element.setAttribute( 'data-lazy-loaded', '' ); element.removeAttribute( 'data-src' ); } ); @@ -3123,6 +3224,7 @@ toArray( media.querySelectorAll( 'source[data-src]' ) ).forEach( function( source ) { source.setAttribute( 'src', source.getAttribute( 'data-src' ) ); source.removeAttribute( 'data-src' ); + source.setAttribute( 'data-lazy-loaded', '' ); sources += 1; } ); @@ -3135,8 +3237,7 @@ // Show the corresponding background element - var indices = getIndices( slide ); - var background = getSlideBackground( indices.h, indices.v ); + var background = getSlideBackground( slide ); if( background ) { background.style.display = 'block'; @@ -3183,7 +3284,7 @@ background.appendChild( video ); } // Iframes - else if( backgroundIframe ) { + else if( backgroundIframe && options.excludeIframes !== true ) { var iframe = document.createElement( 'iframe' ); iframe.setAttribute( 'allowfullscreen', '' ); iframe.setAttribute( 'mozallowfullscreen', '' ); @@ -3212,23 +3313,34 @@ } /** - * Called when the given slide is moved outside of the - * configured view distance. + * Unloads and hides the given slide. This is called when the + * slide is moved outside of the configured view distance. * * @param {HTMLElement} slide */ - function hideSlide( slide ) { + function unloadSlide( slide ) { // Hide the slide element slide.style.display = 'none'; // Hide the corresponding background element - var indices = getIndices( slide ); - var background = getSlideBackground( indices.h, indices.v ); + var background = getSlideBackground( slide ); if( background ) { background.style.display = 'none'; } + // Reset lazy-loaded media elements with src attributes + toArray( slide.querySelectorAll( 'video[data-lazy-loaded][src], audio[data-lazy-loaded][src]' ) ).forEach( function( element ) { + element.setAttribute( 'data-src', element.getAttribute( 'src' ) ); + element.removeAttribute( 'src' ); + } ); + + // Reset lazy-loaded media elements with <source> children + toArray( slide.querySelectorAll( 'video[data-lazy-loaded] source[src], audio source[src]' ) ).forEach( function( source ) { + source.setAttribute( 'data-src', source.getAttribute( 'src' ) ); + source.removeAttribute( 'src' ); + } ); + } /** @@ -3304,6 +3416,13 @@ _appendParamToIframeSource( 'src', 'player.vimeo.com/', 'api=1' ); _appendParamToIframeSource( 'data-src', 'player.vimeo.com/', 'api=1' ); + // Always show media controls on mobile devices + if( isMobileDevice ) { + toArray( dom.slides.querySelectorAll( 'video, audio' ) ).forEach( function( el ) { + el.controls = true; + } ); + } + } /** @@ -3448,7 +3567,12 @@ * * @param {HTMLElement} element */ - function stopEmbeddedContent( element ) { + function stopEmbeddedContent( element, options ) { + + options = extend( { + // Defaults + unloadIframes: true + }, options || {} ); if( element && element.parentNode ) { // HTML5 media elements @@ -3479,13 +3603,15 @@ } }); - // Lazy loading iframes - toArray( element.querySelectorAll( 'iframe[data-src]' ) ).forEach( function( el ) { - // Only removing the src doesn't actually unload the frame - // in all browsers (Firefox) so we set it to blank first - el.setAttribute( 'src', 'about:blank' ); - el.removeAttribute( 'src' ); - } ); + if( options.unloadIframes === true ) { + // Unload lazy-loaded iframes + toArray( element.querySelectorAll( 'iframe[data-src]' ) ).forEach( function( el ) { + // Only removing the src doesn't actually unload the frame + // in all browsers (Firefox) so we set it to blank first + el.setAttribute( 'src', 'about:blank' ); + el.removeAttribute( 'src' ); + } ); + } } } @@ -3767,31 +3893,19 @@ * defined, have a background element so as long as the * index is valid an element will be returned. * - * @param {number} x Horizontal background index + * @param {mixed} x Horizontal background index OR a slide + * HTML element * @param {number} y Vertical background index * @return {(HTMLElement[]|*)} */ function getSlideBackground( x, y ) { - // When printing to PDF the slide backgrounds are nested - // inside of the slides - if( isPrintingPDF() ) { - var slide = getSlide( x, y ); - if( slide ) { - return slide.slideBackgroundElement; - } - - return undefined; - } - - var horizontalBackground = dom.wrapper.querySelectorAll( '.backgrounds>.slide-background' )[ x ]; - var verticalBackgrounds = horizontalBackground && horizontalBackground.querySelectorAll( '.slide-background' ); - - if( verticalBackgrounds && verticalBackgrounds.length && typeof y === 'number' ) { - return verticalBackgrounds ? verticalBackgrounds[ y ] : undefined; + var slide = typeof x === 'number' ? getSlide( x, y ) : x; + if( slide ) { + return slide.slideBackgroundElement; } - return horizontalBackground; + return undefined; } @@ -4053,7 +4167,7 @@ cancelAutoSlide(); - if( currentSlide ) { + if( currentSlide && config.autoSlide !== false ) { var fragment = currentSlide.querySelector( '.current-fragment' ); @@ -4171,6 +4285,8 @@ function navigateRight() { + hasNavigatedRight = true; + // Reverse for RTL if( config.rtl ) { if( ( isOverview() || previousFragment() === false ) && availableRoutes().right ) { @@ -4195,6 +4311,8 @@ function navigateDown() { + hasNavigatedDown = true; + // Prioritize revealing fragments if( ( isOverview() || nextFragment() === false ) && availableRoutes().down ) { slide( indexh, indexv + 1 ); @@ -4241,6 +4359,9 @@ */ function navigateNext() { + hasNavigatedRight = true; + hasNavigatedDown = true; + // Prioritize revealing fragments if( nextFragment() === false ) { if( availableRoutes().down ) { @@ -4933,7 +5054,7 @@ this.context.beginPath(); this.context.arc( x, y, radius, 0, Math.PI * 2, false ); this.context.lineWidth = this.thickness; - this.context.strokeStyle = '#666'; + this.context.strokeStyle = 'rgba( 255, 255, 255, 0.2 )'; this.context.stroke(); if( this.playing ) { @@ -5049,6 +5170,11 @@ isOverview: isOverview, isPaused: isPaused, isAutoSliding: isAutoSliding, + isSpeakerNotes: isSpeakerNotes, + + // Slide preloading + loadSlide: loadSlide, + unloadSlide: unloadSlide, // Adds or removes all internal event listeners (such as keyboard) addEventListeners: addEventListeners, |