aboutsummaryrefslogtreecommitdiffhomepage
path: root/js/reveal.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/reveal.js')
-rw-r--r--js/reveal.js626
1 files changed, 410 insertions, 216 deletions
diff --git a/js/reveal.js b/js/reveal.js
index 5cbb3ff..06b217b 100644
--- a/js/reveal.js
+++ b/js/reveal.js
@@ -151,12 +151,6 @@ var Reveal = (function(){
// Delays updates to the URL due to a Chrome thumbnailer bug
writeURLTimeout = 0,
- // A delay used to activate the overview mode
- activateOverviewTimeout = 0,
-
- // A delay used to deactivate the overview mode
- deactivateOverviewTimeout = 0,
-
// Flags if the interaction event listeners are bound
eventsAreBound = false,
@@ -431,71 +425,26 @@ var Reveal = (function(){
dom.background.innerHTML = '';
dom.background.classList.add( 'no-transition' );
- // Helper method for creating a background element for the
- // given slide
- function _createBackground( slide, container ) {
-
- var data = {
- background: slide.getAttribute( 'data-background' ),
- backgroundSize: slide.getAttribute( 'data-background-size' ),
- backgroundImage: slide.getAttribute( 'data-background-image' ),
- backgroundColor: slide.getAttribute( 'data-background-color' ),
- backgroundRepeat: slide.getAttribute( 'data-background-repeat' ),
- backgroundPosition: slide.getAttribute( 'data-background-position' ),
- backgroundTransition: slide.getAttribute( 'data-background-transition' )
- };
-
- var element = document.createElement( 'div' );
- element.className = 'slide-background';
-
- if( data.background ) {
- // Auto-wrap image urls in url(...)
- if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)$/gi.test( data.background ) ) {
- element.style.backgroundImage = 'url('+ data.background +')';
- }
- else {
- element.style.background = data.background;
- }
- }
-
- if( data.background || data.backgroundColor || data.backgroundImage ) {
- element.setAttribute( 'data-background-hash', data.background + data.backgroundSize + data.backgroundImage + data.backgroundColor + data.backgroundRepeat + data.backgroundPosition + data.backgroundTransition );
- }
-
- // Additional and optional background properties
- if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize;
- if( data.backgroundImage ) element.style.backgroundImage = 'url("' + data.backgroundImage + '")';
- if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;
- if( data.backgroundRepeat ) element.style.backgroundRepeat = data.backgroundRepeat;
- if( data.backgroundPosition ) element.style.backgroundPosition = data.backgroundPosition;
- if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition );
-
- container.appendChild( element );
-
- return element;
-
- }
-
// Iterate over all horizontal slides
toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) {
var backgroundStack;
if( isPrintingPDF() ) {
- backgroundStack = _createBackground( slideh, slideh );
+ backgroundStack = createBackground( slideh, slideh );
}
else {
- backgroundStack = _createBackground( slideh, dom.background );
+ backgroundStack = createBackground( slideh, dom.background );
}
// Iterate over all vertical slides
toArray( slideh.querySelectorAll( 'section' ) ).forEach( function( slidev ) {
if( isPrintingPDF() ) {
- _createBackground( slidev, slidev );
+ createBackground( slidev, slidev );
}
else {
- _createBackground( slidev, backgroundStack );
+ createBackground( slidev, backgroundStack );
}
} );
@@ -527,6 +476,79 @@ var Reveal = (function(){
}
/**
+ * Creates a background for the given slide.
+ *
+ * @param {HTMLElement} slide
+ * @param {HTMLElement} container The element that the background
+ * should be appended to
+ */
+ function createBackground( slide, container ) {
+
+ var data = {
+ background: slide.getAttribute( 'data-background' ),
+ backgroundSize: slide.getAttribute( 'data-background-size' ),
+ backgroundImage: slide.getAttribute( 'data-background-image' ),
+ backgroundVideo: slide.getAttribute( 'data-background-video' ),
+ backgroundColor: slide.getAttribute( 'data-background-color' ),
+ backgroundRepeat: slide.getAttribute( 'data-background-repeat' ),
+ backgroundPosition: slide.getAttribute( 'data-background-position' ),
+ backgroundTransition: slide.getAttribute( 'data-background-transition' )
+ };
+
+ var element = document.createElement( 'div' );
+ element.className = 'slide-background';
+
+ if( data.background ) {
+ // Auto-wrap image urls in url(...)
+ if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)$/gi.test( data.background ) ) {
+ element.style.backgroundImage = 'url('+ data.background +')';
+ }
+ else {
+ element.style.background = data.background;
+ }
+ }
+
+ // Create a hash for this combination of background settings.
+ // This is used to determine when two slide backgrounds are
+ // the same.
+ if( data.background || data.backgroundColor || data.backgroundImage || data.backgroundVideo ) {
+ element.setAttribute( 'data-background-hash', data.background +
+ data.backgroundSize +
+ data.backgroundImage +
+ data.backgroundVideo +
+ data.backgroundColor +
+ data.backgroundRepeat +
+ data.backgroundPosition +
+ data.backgroundTransition );
+ }
+
+ // Additional and optional background properties
+ if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize;
+ if( data.backgroundImage ) element.style.backgroundImage = 'url("' + data.backgroundImage + '")';
+ if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;
+ if( data.backgroundRepeat ) element.style.backgroundRepeat = data.backgroundRepeat;
+ if( data.backgroundPosition ) element.style.backgroundPosition = data.backgroundPosition;
+ if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition );
+
+ // Create video background element
+ if( data.backgroundVideo ) {
+ var video = document.createElement( 'video' );
+
+ // Support comma separated lists of video sources
+ data.backgroundVideo.split( ',' ).forEach( function( source ) {
+ video.innerHTML += '<source src="'+ source +'">';
+ } );
+
+ element.appendChild( video );
+ }
+
+ container.appendChild( element );
+
+ return element;
+
+ }
+
+ /**
* Applies the configuration settings from the config
* object. May be called multiple times.
*/
@@ -591,7 +613,13 @@ var Reveal = (function(){
enablePreviewLinks( '[data-preview-link]' );
}
- // Auto-slide playback controls
+ // Remove existing auto-slide controls
+ if( autoSlidePlayer ) {
+ autoSlidePlayer.destroy();
+ autoSlidePlayer = null;
+ }
+
+ // Generate auto-slide controls if needed
if( numberOfSlides > 1 && config.autoSlide && config.autoSlideStoppable && features.canvas && features.requestAnimationFrame ) {
autoSlidePlayer = new Playback( dom.wrapper, function() {
return Math.min( Math.max( ( Date.now() - autoSlideStartTime ) / autoSlide, 0 ), 1 );
@@ -600,9 +628,13 @@ var Reveal = (function(){
autoSlidePlayer.on( 'click', onAutoSlidePlayerClick );
autoSlidePaused = false;
}
- else if( autoSlidePlayer ) {
- autoSlidePlayer.destroy();
- autoSlidePlayer = null;
+
+ // When fragments are turned off they should be visible
+ if( config.fragments === false ) {
+ toArray( dom.slides.querySelectorAll( '.fragment' ) ).forEach( function( element ) {
+ element.classList.add( 'visible' );
+ element.classList.remove( 'current-fragment' );
+ } );
}
// Load the theme in the config, if it's not already loaded
@@ -637,7 +669,14 @@ var Reveal = (function(){
dom.wrapper.addEventListener( 'touchend', onTouchEnd, false );
// Support pointer-style touch interaction as well
- if( window.navigator.msPointerEnabled ) {
+ if( window.navigator.pointerEnabled ) {
+ // IE 11 uses un-prefixed version of pointer events
+ dom.wrapper.addEventListener( 'pointerdown', onPointerDown, false );
+ dom.wrapper.addEventListener( 'pointermove', onPointerMove, false );
+ dom.wrapper.addEventListener( 'pointerup', onPointerUp, false );
+ }
+ else if( window.navigator.msPointerEnabled ) {
+ // IE 10 uses prefixed version of pointer events
dom.wrapper.addEventListener( 'MSPointerDown', onPointerDown, false );
dom.wrapper.addEventListener( 'MSPointerMove', onPointerMove, false );
dom.wrapper.addEventListener( 'MSPointerUp', onPointerUp, false );
@@ -696,7 +735,14 @@ var Reveal = (function(){
dom.wrapper.removeEventListener( 'touchmove', onTouchMove, false );
dom.wrapper.removeEventListener( 'touchend', onTouchEnd, false );
- if( window.navigator.msPointerEnabled ) {
+ // IE11
+ if( window.navigator.pointerEnabled ) {
+ dom.wrapper.removeEventListener( 'pointerdown', onPointerDown, false );
+ dom.wrapper.removeEventListener( 'pointermove', onPointerMove, false );
+ dom.wrapper.removeEventListener( 'pointerup', onPointerUp, false );
+ }
+ // IE10
+ else if( window.navigator.msPointerEnabled ) {
dom.wrapper.removeEventListener( 'MSPointerDown', onPointerDown, false );
dom.wrapper.removeEventListener( 'MSPointerMove', onPointerMove, false );
dom.wrapper.removeEventListener( 'MSPointerUp', onPointerUp, false );
@@ -739,6 +785,22 @@ var Reveal = (function(){
}
/**
+ * Utility for deserializing a value.
+ */
+ function deserialize( value ) {
+
+ if( typeof value === 'string' ) {
+ 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 );
+ }
+
+ return value;
+
+ }
+
+ /**
* Measures the distance in pixels between point a
* and point b.
*
@@ -804,40 +866,26 @@ var Reveal = (function(){
/**
* Returns the remaining height within the parent of the
- * target element after subtracting the height of all
- * siblings.
+ * target element.
*
- * remaining height = [parent height] - [ siblings height]
+ * remaining height = [ configured parent height ] - [ current parent 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 newHeight, oldHeight = element.style.height;
- }
-
- } );
+ // Change the .stretch element height to 0 in order find the height of all
+ // the other elements
+ element.style.height = '0px';
+ newHeight = height - element.parentNode.offsetHeight;
- var elementStyles = window.getComputedStyle( element );
-
- // Subtract the margins of the target element
- height -= parseInt( elementStyles.marginTop, 10 ) +
- parseInt( elementStyles.marginBottom, 10 );
+ // Restore the old height, just in case
+ element.style.height = oldHeight + 'px';
+ return newHeight;
}
return height;
@@ -897,7 +945,7 @@ var Reveal = (function(){
function enableRollingLinks() {
if( features.transforms3d && !( 'msPerspective' in document.body.style ) ) {
- var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a:not(.image)' );
+ var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a' );
for( var i = 0, len = anchors.length; i < len; i++ ) {
var anchor = anchors[i];
@@ -1063,14 +1111,17 @@ var Reveal = (function(){
scale = Math.max( scale, config.minScale );
scale = Math.min( scale, config.maxScale );
- // Prefer applying scale via zoom since Chrome blurs scaled content
- // with nested transforms
- if( typeof dom.slides.style.zoom !== 'undefined' && !navigator.userAgent.match( /(iphone|ipod|ipad|android)/gi ) ) {
+ // Prefer zooming in WebKit so that content remains crisp
+ if( /webkit/i.test( navigator.userAgent ) && typeof dom.slides.style.zoom !== 'undefined' ) {
dom.slides.style.zoom = scale;
}
// Apply scale transform as a fallback
else {
- transformElement( dom.slides, 'translate(-50%, -50%) scale('+ scale +') translate(50%, 50%)' );
+ dom.slides.style.left = '50%';
+ dom.slides.style.top = '50%';
+ dom.slides.style.bottom = 'auto';
+ dom.slides.style.right = 'auto';
+ transformElement( dom.slides, 'translate(-50%, -50%) scale('+ scale +')' );
}
// Select all slides, vertical and horizontal
@@ -1091,7 +1142,7 @@ var Reveal = (function(){
slide.style.top = 0;
}
else {
- slide.style.top = Math.max( - ( getAbsoluteHeight( slide ) / 2 ) - slidePadding, -slideHeight / 2 ) + 'px';
+ slide.style.top = Math.max( ( ( slideHeight - getAbsoluteHeight( slide ) ) / 2 ) - slidePadding, 0 ) + 'px';
}
}
else {
@@ -1117,7 +1168,7 @@ var Reveal = (function(){
toArray( dom.slides.querySelectorAll( 'section > .stretch' ) ).forEach( function( element ) {
// Determine how much vertical space we can use
- var remainingHeight = getRemainingHeight( element, ( height - ( padding * 2 ) ) );
+ var remainingHeight = getRemainingHeight( element, height );
// Consider the aspect ratio of media elements
if( /(img|video)/gi.test( element.nodeName ) ) {
@@ -1198,67 +1249,57 @@ var Reveal = (function(){
dom.wrapper.classList.add( '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() {
-
- var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
+ 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;
+ for( var i = 0, len1 = horizontalSlides.length; i < len1; i++ ) {
+ var hslide = horizontalSlides[i],
+ hoffset = config.rtl ? -105 : 105;
- hslide.setAttribute( 'data-index-h', i );
+ hslide.setAttribute( 'data-index-h', i );
- // Apply CSS transform
- transformElement( hslide, 'translateZ(-'+ depth +'px) translate(' + ( ( i - indexh ) * hoffset ) + '%, 0%)' );
+ // Apply CSS transform
+ transformElement( hslide, 'translateZ(-'+ depth +'px) translate(' + ( ( i - indexh ) * hoffset ) + '%, 0%)' );
- if( hslide.classList.contains( 'stack' ) ) {
+ if( hslide.classList.contains( 'stack' ) ) {
- var verticalSlides = hslide.querySelectorAll( 'section' );
+ var verticalSlides = hslide.querySelectorAll( 'section' );
- for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) {
- var verticalIndex = i === indexh ? indexv : getPreviousVerticalIndex( hslide );
+ for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) {
+ var verticalIndex = i === indexh ? indexv : getPreviousVerticalIndex( hslide );
- var vslide = verticalSlides[j];
+ var vslide = verticalSlides[j];
- vslide.setAttribute( 'data-index-h', i );
- vslide.setAttribute( 'data-index-v', j );
+ vslide.setAttribute( 'data-index-h', i );
+ vslide.setAttribute( 'data-index-v', j );
- // Apply CSS transform
- transformElement( vslide, 'translate(0%, ' + ( ( j - verticalIndex ) * 105 ) + '%)' );
-
- // Navigate to this slide on click
- vslide.addEventListener( 'click', onOverviewSlideClicked, true );
- }
-
- }
- else {
+ // Apply CSS transform
+ transformElement( vslide, 'translate(0%, ' + ( ( j - verticalIndex ) * 105 ) + '%)' );
// Navigate to this slide on click
- hslide.addEventListener( 'click', onOverviewSlideClicked, true );
-
+ vslide.addEventListener( 'click', onOverviewSlideClicked, true );
}
- }
- updateSlidesVisibility();
+ }
+ else {
- layout();
+ // Navigate to this slide on click
+ hslide.addEventListener( 'click', onOverviewSlideClicked, true );
- if( !wasActive ) {
- // Notify observers of the overview showing
- dispatchEvent( 'overviewshown', {
- 'indexh': indexh,
- 'indexv': indexv,
- 'currentSlide': currentSlide
- } );
}
+ }
+
+ updateSlidesVisibility();
+
+ layout();
- }, 10 );
+ if( !wasActive ) {
+ // Notify observers of the overview showing
+ dispatchEvent( 'overviewshown', {
+ 'indexh': indexh,
+ 'indexv': indexv,
+ 'currentSlide': currentSlide
+ } );
+ }
}
@@ -1273,9 +1314,6 @@ var Reveal = (function(){
// Only proceed if enabled in config
if( config.overview ) {
- clearTimeout( activateOverviewTimeout );
- clearTimeout( deactivateOverviewTimeout );
-
dom.wrapper.classList.remove( 'overview' );
// Temporarily add a class so that transitions can do different things
@@ -1283,7 +1321,7 @@ var Reveal = (function(){
// moving from slide to slide
dom.wrapper.classList.add( 'overview-deactivating' );
- deactivateOverviewTimeout = setTimeout( function () {
+ setTimeout( function () {
dom.wrapper.classList.remove( 'overview-deactivating' );
}, 1 );
@@ -1370,7 +1408,7 @@ var Reveal = (function(){
element.webkitRequestFullscreen ||
element.webkitRequestFullScreen ||
element.mozRequestFullScreen ||
- element.msRequestFullScreen;
+ element.msRequestFullscreen;
if( requestMethod ) {
requestMethod.apply( element );
@@ -1414,13 +1452,13 @@ var Reveal = (function(){
/**
* Toggles the paused mode on and off.
*/
- function togglePause() {
+ function togglePause( override ) {
- if( isPaused() ) {
- resume();
+ if( typeof override === 'boolean' ) {
+ override ? pause() : resume();
}
else {
- pause();
+ isPaused() ? resume() : pause();
}
}
@@ -1435,6 +1473,34 @@ var Reveal = (function(){
}
/**
+ * Toggles the auto slide mode on and off.
+ *
+ * @param {Boolean} override Optional flag which sets the desired state.
+ * True means autoplay starts, false means it stops.
+ */
+
+ function toggleAutoSlide( override ) {
+
+ if( typeof override === 'boolean' ) {
+ override ? resumeAutoSlide() : pauseAutoSlide();
+ }
+
+ else {
+ autoSlidePaused ? resumeAutoSlide() : pauseAutoSlide();
+ }
+
+ }
+
+ /**
+ * Checks if the auto slide mode is currently on.
+ */
+ function isAutoSliding() {
+
+ return !!( autoSlide && !autoSlidePaused );
+
+ }
+
+ /**
* Steps from the current point in the presentation to the
* slide which matches the specified horizontal and vertical
* indices.
@@ -1708,26 +1774,30 @@ var Reveal = (function(){
// Any element previous to index is given the 'past' class
element.classList.add( reverse ? 'future' : 'past' );
- var pastFragments = toArray( element.querySelectorAll( '.fragment' ) );
+ if( config.fragments ) {
+ var pastFragments = toArray( element.querySelectorAll( '.fragment' ) );
- // Show all fragments on prior slides
- while( pastFragments.length ) {
- var pastFragment = pastFragments.pop();
- pastFragment.classList.add( 'visible' );
- pastFragment.classList.remove( 'current-fragment' );
+ // Show all fragments on prior slides
+ while( pastFragments.length ) {
+ var pastFragment = pastFragments.pop();
+ pastFragment.classList.add( 'visible' );
+ pastFragment.classList.remove( 'current-fragment' );
+ }
}
}
else if( i > index ) {
// Any element subsequent to index is given the 'future' class
element.classList.add( reverse ? 'past' : 'future' );
- var futureFragments = toArray( element.querySelectorAll( '.fragment.visible' ) );
+ if( config.fragments ) {
+ var futureFragments = toArray( element.querySelectorAll( '.fragment.visible' ) );
- // No fragments in future slides should be visible ahead of time
- while( futureFragments.length ) {
- var futureFragment = futureFragments.pop();
- futureFragment.classList.remove( 'visible' );
- futureFragment.classList.remove( 'current-fragment' );
+ // No fragments in future slides should be visible ahead of time
+ while( futureFragments.length ) {
+ var futureFragment = futureFragments.pop();
+ futureFragment.classList.remove( 'visible' );
+ futureFragment.classList.remove( 'current-fragment' );
+ }
}
}
@@ -1822,42 +1892,7 @@ var Reveal = (function(){
// Update progress if enabled
if( config.progress && dom.progress ) {
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
-
- // The number of past and total slides
- var totalCount = document.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ).length;
- var pastCount = 0;
-
- // Step through all slides and count the past ones
- mainLoop: for( var i = 0; i < horizontalSlides.length; i++ ) {
-
- var horizontalSlide = horizontalSlides[i];
- var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
-
- for( var j = 0; j < verticalSlides.length; j++ ) {
-
- // Stop as soon as we arrive at the present
- if( verticalSlides[j].classList.contains( 'present' ) ) {
- break mainLoop;
- }
-
- pastCount++;
-
- }
-
- // Stop as soon as we arrive at the present
- if( horizontalSlide.classList.contains( 'present' ) ) {
- break;
- }
-
- // Don't count the wrapping section for vertical slides
- if( horizontalSlide.classList.contains( 'stack' ) === false ) {
- pastCount++;
- }
-
- }
-
- dom.progressbar.style.width = ( pastCount / ( totalCount - 1 ) ) * window.innerWidth + 'px';
+ dom.progressbar.style.width = getProgress() * window.innerWidth + 'px';
}
@@ -1965,7 +2000,7 @@ var Reveal = (function(){
}
if( includeAll || h === indexh ) {
- toArray( backgroundh.childNodes ).forEach( function( backgroundv, v ) {
+ toArray( backgroundh.querySelectorAll( 'section' ) ).forEach( function( backgroundv, v ) {
if( v < indexv ) {
backgroundv.className = 'slide-background past';
@@ -1985,9 +2020,22 @@ var Reveal = (function(){
} );
- // Don't transition between identical backgrounds. This
- // prevents unwanted flicker.
+ // Stop any currently playing video background
+ if( previousBackground ) {
+
+ var previousVideo = previousBackground.querySelector( 'video' );
+ if( previousVideo ) previousVideo.pause();
+
+ }
+
if( currentBackground ) {
+
+ // Start video playback
+ var currentVideo = currentBackground.querySelector( 'video' );
+ if( currentVideo ) currentVideo.play();
+
+ // Don't transition between identical backgrounds. This
+ // prevents unwanted flicker.
var previousBackgroundHash = previousBackground ? previousBackground.getAttribute( 'data-background-hash' ) : null;
var currentBackgroundHash = currentBackground.getAttribute( 'data-background-hash' );
if( currentBackgroundHash && currentBackgroundHash === previousBackgroundHash && currentBackground !== previousBackground ) {
@@ -1995,6 +2043,7 @@ var Reveal = (function(){
}
previousBackground = currentBackground;
+
}
// Allow the first background to apply without transition
@@ -2032,7 +2081,7 @@ var Reveal = (function(){
var slideHeight = dom.background.offsetHeight;
var verticalSlideCount = verticalSlides.length;
- var verticalOffset = verticalSlideCount > 0 ? -( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 ) * indexv : 0;
+ var verticalOffset = verticalSlideCount > 1 ? -( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 ) * indexv : 0;
dom.background.style.backgroundPosition = horizontalOffset + 'px ' + verticalOffset + 'px';
@@ -2150,6 +2199,70 @@ var Reveal = (function(){
}
/**
+ * Returns a value ranging from 0-1 that represents
+ * how far into the presentation we have navigated.
+ */
+ function getProgress() {
+
+ var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
+
+ // The number of past and total slides
+ var totalCount = document.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ).length;
+ var pastCount = 0;
+
+ // Step through all slides and count the past ones
+ mainLoop: for( var i = 0; i < horizontalSlides.length; i++ ) {
+
+ var horizontalSlide = horizontalSlides[i];
+ var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
+
+ for( var j = 0; j < verticalSlides.length; j++ ) {
+
+ // Stop as soon as we arrive at the present
+ if( verticalSlides[j].classList.contains( 'present' ) ) {
+ break mainLoop;
+ }
+
+ pastCount++;
+
+ }
+
+ // Stop as soon as we arrive at the present
+ if( horizontalSlide.classList.contains( 'present' ) ) {
+ break;
+ }
+
+ // Don't count the wrapping section for vertical slides
+ if( horizontalSlide.classList.contains( 'stack' ) === false ) {
+ pastCount++;
+ }
+
+ }
+
+ if( currentSlide ) {
+
+ var allFragments = currentSlide.querySelectorAll( '.fragment' );
+
+ // If there are fragments in the current slide those should be
+ // accounted for in the progress.
+ if( allFragments.length > 0 ) {
+ var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
+
+ // This value represents how big a portion of the slide progress
+ // that is made up by its fragments (0-1)
+ var fragmentWeight = 0.9;
+
+ // Add fragment progress to the past slide count
+ pastCount += ( visibleFragments.length / allFragments.length ) * fragmentWeight;
+ }
+
+ }
+
+ return pastCount / ( totalCount - 1 );
+
+ }
+
+ /**
* Checks if this presentation is running inside of the
* speaker notes window.
*/
@@ -2173,8 +2286,16 @@ var Reveal = (function(){
// If the first bit is invalid and there is a name we can
// assume that this is a named link
if( isNaN( parseInt( bits[0], 10 ) ) && name.length ) {
- // Find the slide with the specified name
- var element = document.querySelector( '#' + name );
+ var element;
+
+ try {
+ // Find the slide with the specified name
+ element = document.querySelector( '#' + name );
+ }
+ catch( e ) {
+ // If the ID is an invalid selector a harmless SyntaxError
+ // may be thrown here.
+ }
if( element ) {
// Find the position of the named slide and navigate to it
@@ -2219,9 +2340,16 @@ var Reveal = (function(){
else {
var url = '/';
+ // Attempt to create a named link based on the slide's ID
+ var id = currentSlide.getAttribute( 'id' );
+ if( id ) {
+ id = id.toLowerCase();
+ id = id.replace( /[^a-zA-Z0-9\-\_\:\.]/g, '' );
+ }
+
// If the current slide has an ID, use that as a named link
- if( currentSlide && typeof currentSlide.getAttribute( 'id' ) === 'string' ) {
- url = '/' + currentSlide.getAttribute( 'id' );
+ if( currentSlide && typeof id === 'string' && id.length ) {
+ url = '/' + id;
}
// Otherwise use the /h/v index
else {
@@ -2282,6 +2410,49 @@ var Reveal = (function(){
}
/**
+ * Retrieves the total number of slides in this presentation.
+ */
+ function getTotalSlides() {
+
+ return document.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ).length;
+
+ }
+
+ /**
+ * Retrieves the current state of the presentation as
+ * an object. This state can then be restored at any
+ * time.
+ */
+ function getState() {
+
+ var indices = getIndices();
+
+ return {
+ indexh: indices.h,
+ indexv: indices.v,
+ indexf: indices.f,
+ paused: isPaused(),
+ overview: isOverview()
+ };
+
+ }
+
+ /**
+ * Restores the presentation to the given state.
+ *
+ * @param {Object} state As generated by getState()
+ */
+ function setState( state ) {
+
+ if( typeof state === 'object' ) {
+ slide( deserialize( state.indexh ), deserialize( state.indexv ), deserialize( state.indexf ) );
+ togglePause( deserialize( state.paused ) );
+ toggleOverview( deserialize( state.overview ) );
+ }
+
+ }
+
+ /**
* Return a sorted fragments list, ordered by an increasing
* "data-fragment-index" attribute.
*
@@ -2415,6 +2586,7 @@ var Reveal = (function(){
}
updateControls();
+ updateProgress();
return !!( fragmentsShown.length || fragmentsHidden.length );
@@ -2459,14 +2631,21 @@ var Reveal = (function(){
if( currentSlide ) {
+ var currentFragment = currentSlide.querySelector( '.current-fragment' );
+
+ var fragmentAutoSlide = currentFragment ? currentFragment.getAttribute( 'data-autoslide' ) : null;
var parentAutoSlide = currentSlide.parentNode ? currentSlide.parentNode.getAttribute( 'data-autoslide' ) : null;
var slideAutoSlide = currentSlide.getAttribute( 'data-autoslide' );
// Pick value in the following priority order:
- // 1. Current slide's data-autoslide
- // 2. Parent slide's data-autoslide
- // 3. Global autoSlide setting
- if( slideAutoSlide ) {
+ // 1. Current fragment's data-autoslide
+ // 2. Current slide's data-autoslide
+ // 3. Parent slide's data-autoslide
+ // 4. Global autoSlide setting
+ if( fragmentAutoSlide ) {
+ autoSlide = parseInt( fragmentAutoSlide, 10 );
+ }
+ else if( slideAutoSlide ) {
autoSlide = parseInt( slideAutoSlide, 10 );
}
else if( parentAutoSlide ) {
@@ -2519,6 +2698,7 @@ var Reveal = (function(){
function pauseAutoSlide() {
autoSlidePaused = true;
+ dispatchEvent( 'autoslidepaused' );
clearTimeout( autoSlideTimeout );
if( autoSlidePlayer ) {
@@ -2530,6 +2710,7 @@ var Reveal = (function(){
function resumeAutoSlide() {
autoSlidePaused = false;
+ dispatchEvent( 'autoslideresumed' );
cueAutoSlide();
}
@@ -2647,6 +2828,9 @@ var Reveal = (function(){
*/
function onDocumentKeyDown( event ) {
+ // Remember if auto-sliding was paused so we can toggle it
+ var autoSlideWasPaused = autoSlidePaused;
+
onUserInput( event );
// Check if there's a focused element that could be using
@@ -2719,10 +2903,12 @@ var Reveal = (function(){
case 32: isOverview() ? deactivateOverview() : event.shiftKey ? navigatePrev() : navigateNext(); break;
// return
case 13: isOverview() ? deactivateOverview() : triggered = false; break;
- // b, period, Logitech presenter tools "black screen" button
- case 66: case 190: case 191: togglePause(); break;
+ // two-spot, semicolon, b, period, Logitech presenter tools "black screen" button
+ case 58: case 59: case 66: case 190: case 191: togglePause(); break;
// f
case 70: enterFullscreen(); break;
+ // a
+ case 65: if ( config.autoSlideStoppable ) toggleAutoSlide( autoSlideWasPaused ); break;
default:
triggered = false;
}
@@ -2877,7 +3063,7 @@ var Reveal = (function(){
*/
function onPointerDown( event ) {
- if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
onTouchStart( event );
}
@@ -2889,7 +3075,7 @@ var Reveal = (function(){
*/
function onPointerMove( event ) {
- if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
onTouchMove( event );
}
@@ -2901,7 +3087,7 @@ var Reveal = (function(){
*/
function onPointerUp( event ) {
- if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
onTouchEnd( event );
}
@@ -3275,17 +3461,30 @@ var Reveal = (function(){
// Toggles the "black screen" mode on/off
togglePause: togglePause,
+ // Toggles the auto slide mode on/off
+ toggleAutoSlide: toggleAutoSlide,
+
// State checks
isOverview: isOverview,
isPaused: isPaused,
+ isAutoSliding: isAutoSliding,
// Adds or removes all internal event listeners (such as keyboard)
addEventListeners: addEventListeners,
removeEventListeners: removeEventListeners,
+ // Facility for persisting and restoring the presentation state
+ getState: getState,
+ setState: setState,
+
+ // Presentation progress on range of 0-1
+ getProgress: getProgress,
+
// Returns the indices of the current, or specified, slide
getIndices: getIndices,
+ getTotalSlides: getTotalSlides,
+
// Returns the slide at the specified index, y is optional
getSlide: function( x, y ) {
var horizontalSlide = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR )[ x ];
@@ -3330,12 +3529,7 @@ var Reveal = (function(){
for( var i in query ) {
var value = query[ i ];
- query[ i ] = unescape( value );
-
- if( value === 'null' ) query[ i ] = null;
- else if( value === 'true' ) query[ i ] = true;
- else if( value === 'false' ) query[ i ] = false;
- else if( value.match( /^\d+$/ ) ) query[ i ] = parseFloat( value );
+ query[ i ] = deserialize( unescape( value ) );
}
return query;