aboutsummaryrefslogtreecommitdiffhomepage
path: root/js/reveal.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/reveal.js')
-rw-r--r--js/reveal.js1235
1 files changed, 904 insertions, 331 deletions
diff --git a/js/reveal.js b/js/reveal.js
index e668d48..859449a 100644
--- a/js/reveal.js
+++ b/js/reveal.js
@@ -5,14 +5,30 @@
*
* Copyright (C) 2014 Hakim El Hattab, http://hakim.se
*/
-var Reveal = (function(){
+(function( root, factory ) {
+ if( typeof define === 'function' && define.amd ) {
+ // AMD. Register as an anonymous module.
+ define( function() {
+ root.Reveal = factory();
+ return root.Reveal;
+ } );
+ } else if( typeof exports === 'object' ) {
+ // Node. Does not work with strict CommonJS.
+ module.exports = factory();
+ } else {
+ // Browser globals.
+ root.Reveal = factory();
+ }
+}( this, function() {
'use strict';
- var SLIDES_SELECTOR = '.reveal .slides section',
- HORIZONTAL_SLIDES_SELECTOR = '.reveal .slides>section',
- VERTICAL_SLIDES_SELECTOR = '.reveal .slides>section.present>section',
- HOME_SLIDE_SELECTOR = '.reveal .slides>section:first-of-type',
+ var Reveal;
+
+ var SLIDES_SELECTOR = '.slides section',
+ HORIZONTAL_SLIDES_SELECTOR = '.slides>section',
+ VERTICAL_SLIDES_SELECTOR = '.slides>section.present>section',
+ HOME_SLIDE_SELECTOR = '.slides>section:first-of-type',
// Configurations defaults, can be overridden at initialization time
config = {
@@ -44,6 +60,9 @@ var Reveal = (function(){
// Enable keyboard shortcuts for navigation
keyboard: true,
+ // Optional function that blocks keyboard events when retuning false
+ keyboardCondition: null,
+
// Enable the slide overview mode
overview: true,
@@ -86,20 +105,26 @@ var Reveal = (function(){
// Opens links in an iframe preview overlay
previewLinks: false,
+ // Exposes the reveal.js API through window.postMessage
+ postMessage: true,
+
+ // Dispatches all reveal.js events to the parent window through postMessage
+ postMessageEvents: false,
+
// Focuses body when page changes visiblity to ensure keyboard shortcuts work
- focusBodyOnPageVisiblityChange: true,
+ focusBodyOnPageVisibilityChange: true,
// Theme (see /css/theme)
theme: null,
// Transition style
- transition: 'default', // default/cube/page/concave/zoom/linear/fade/none
+ transition: 'default', // none/fade/slide/convex/concave/zoom
// Transition speed
transitionSpeed: 'default', // default/fast/slow
// Transition style for full page slide backgrounds
- backgroundTransition: 'default', // default/linear/none
+ backgroundTransition: 'default', // none/fade/slide/convex/concave/zoom
// Parallax background image
parallaxBackgroundImage: '', // CSS syntax, e.g. "a.jpg"
@@ -151,12 +176,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,
@@ -203,11 +222,26 @@ var Reveal = (function(){
if( !features.transforms2d && !features.transforms3d ) {
document.body.setAttribute( 'class', 'no-transforms' );
+ // Since JS won't be running any further, we need to load all
+ // images that were intended to lazy load now
+ var images = document.getElementsByTagName( 'img' );
+ for( var i = 0, len = images.length; i < len; i++ ) {
+ var image = images[i];
+ if( image.getAttribute( 'data-src' ) ) {
+ image.setAttribute( 'src', image.getAttribute( 'data-src' ) );
+ image.removeAttribute( 'data-src' );
+ }
+ }
+
// If the browser doesn't support core features we won't be
// using JavaScript to control the presentation
return;
}
+ // Cache references to key DOM elements
+ dom.wrapper = document.querySelector( '.reveal' );
+ dom.slides = document.querySelector( '.reveal .slides' );
+
// Force a layout when the whole page, incl fonts, has loaded
window.addEventListener( 'load', layout, false );
@@ -252,11 +286,10 @@ var Reveal = (function(){
features.canvas = !!document.createElement( 'canvas' ).getContext;
- isMobileDevice = navigator.userAgent.match( /(iphone|ipod|android)/gi );
+ isMobileDevice = navigator.userAgent.match( /(iphone|ipod|ipad|android)/gi );
}
-
/**
* Loads the dependencies of reveal.js. Dependencies are
* defined via the configuration option 'dependencies'
@@ -330,6 +363,9 @@ var Reveal = (function(){
// Make sure we've got all the DOM elements we need
setupDOM();
+ // Listen to messages posted to this window
+ setupPostMessage();
+
// Resets all vertical slides so that only the first is visible
resetVerticalSlides();
@@ -357,6 +393,20 @@ var Reveal = (function(){
} );
}, 1 );
+ // Special setup and config is required when printing to PDF
+ if( isPrintingPDF() ) {
+ removeEventListeners();
+
+ // The document needs to have loaded for the PDF layout
+ // measurements to be accurate
+ if( document.readyState === 'complete' ) {
+ setupPDF();
+ }
+ else {
+ window.addEventListener( 'load', setupPDF );
+ }
+ }
+
}
/**
@@ -366,11 +416,6 @@ var Reveal = (function(){
*/
function setupDOM() {
- // 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' );
@@ -399,6 +444,7 @@ var Reveal = (function(){
// Cache references to elements
dom.controls = document.querySelector( '.reveal .controls' );
+ dom.theme = document.querySelector( '#theme' );
// There can be multiple instances of controls throughout the page
dom.controlsLeft = toArray( document.querySelectorAll( '.navigate-left' ) );
@@ -411,21 +457,101 @@ var Reveal = (function(){
}
/**
+ * Configures the presentation for printing to a static
+ * PDF.
+ */
+ function setupPDF() {
+
+ var slideSize = getComputedSlideSize( window.innerWidth, window.innerHeight );
+
+ // Dimensions of the PDF pages
+ var pageWidth = Math.floor( slideSize.width * ( 1 + config.margin ) ),
+ pageHeight = Math.floor( slideSize.height * ( 1 + config.margin ) );
+
+ // Dimensions of slides within the pages
+ var slideWidth = slideSize.width,
+ slideHeight = slideSize.height;
+
+ // Let the browser know what page size we want to print
+ injectStyleSheet( '@page{size:'+ pageWidth +'px '+ pageHeight +'px; margin: 0;}' );
+
+ // Limit the size of certain elements to the dimensions of the slide
+ injectStyleSheet( '.reveal img, .reveal video, .reveal iframe{max-width: '+ slideWidth +'px; max-height:'+ slideHeight +'px}' );
+
+ document.body.classList.add( 'print-pdf' );
+ document.body.style.width = pageWidth + 'px';
+ document.body.style.height = pageHeight + 'px';
+
+ // Slide and slide background layout
+ toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
+
+ // Vertical stacks are not centred since their section
+ // children will be
+ if( slide.classList.contains( 'stack' ) === false ) {
+ // Center the slide inside of the page, giving the slide some margin
+ var left = ( pageWidth - slideWidth ) / 2,
+ top = ( pageHeight - slideHeight ) / 2;
+
+ var contentHeight = getAbsoluteHeight( slide );
+ var numberOfPages = Math.max( Math.ceil( contentHeight / pageHeight ), 1 );
+
+ // Center slides vertically
+ if( numberOfPages === 1 && config.center || slide.classList.contains( 'center' ) ) {
+ top = Math.max( ( pageHeight - contentHeight ) / 2, 0 );
+ }
+
+ // Position the slide inside of the page
+ slide.style.left = left + 'px';
+ slide.style.top = top + 'px';
+ slide.style.width = slideWidth + 'px';
+
+ // TODO Backgrounds need to be multiplied when the slide
+ // stretches over multiple pages
+ var background = slide.querySelector( '.slide-background' );
+ if( background ) {
+ background.style.width = pageWidth + 'px';
+ background.style.height = ( pageHeight * numberOfPages ) + 'px';
+ background.style.top = -top + 'px';
+ background.style.left = -left + 'px';
+ }
+ }
+
+ } );
+
+ // Show all fragments
+ toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' .fragment' ) ).forEach( function( fragment ) {
+ fragment.classList.add( 'visible' );
+ } );
+
+ }
+
+ /**
* 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;
+ // Find all nodes matching the description
+ var nodes = container.querySelectorAll( '.' + classname );
+
+ // Check all matches to find one which is a direct child of
+ // the specified container
+ for( var i = 0; i < nodes.length; i++ ) {
+ var testNode = nodes[i];
+ if( testNode.parentNode === container ) {
+ return testNode;
}
- container.appendChild( node );
}
+
+ // If no node was found, create it now
+ var node = document.createElement( tagname );
+ node.classList.add( classname );
+ if( typeof innerHTML === 'string' ) {
+ node.innerHTML = innerHTML;
+ }
+ container.appendChild( node );
+
return node;
}
@@ -437,81 +563,36 @@ var Reveal = (function(){
*/
function createBackgrounds() {
- if( isPrintingPDF() ) {
- document.body.classList.add( 'print-pdf' );
- }
+ var printMode = isPrintingPDF();
// Clear prior backgrounds
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 ) {
+ toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) {
var backgroundStack;
- if( isPrintingPDF() ) {
- backgroundStack = _createBackground( slideh, slideh );
+ if( printMode ) {
+ 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 );
+ if( printMode ) {
+ createBackground( slidev, slidev );
}
else {
- _createBackground( slidev, backgroundStack );
+ createBackground( slidev, backgroundStack );
}
+ backgroundStack.classList.add( 'stack' );
+
} );
} );
@@ -541,12 +622,104 @@ 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' );
+
+ // Carry over custom classes from the slide to the background
+ element.className = 'slide-background ' + slide.className.replace( /present|past|future/, '' );
+
+ 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 ) ) {
+ slide.setAttribute( 'data-background-image', 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.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;
+
+ }
+
+ /**
+ * Registers a listener to postMessage events, this makes it
+ * possible to call all reveal.js API methods from another
+ * window. For example:
+ *
+ * revealWindow.postMessage( JSON.stringify({
+ * method: 'slide',
+ * args: [ 2 ]
+ * }), '*' );
+ */
+ function setupPostMessage() {
+
+ if( config.postMessage ) {
+ window.addEventListener( 'message', function ( event ) {
+ var data = event.data;
+
+ // Make sure we're dealing with JSON
+ if( data.charAt( 0 ) === '{' && data.charAt( data.length - 1 ) === '}' ) {
+ data = JSON.parse( data );
+
+ // Check if the requested method can be found
+ if( data.method && typeof Reveal[data.method] === 'function' ) {
+ Reveal[data.method].apply( Reveal, data.args );
+ }
+ }
+ }, false );
+ }
+
+ }
+
+ /**
* Applies the configuration settings from the config
* object. May be called multiple times.
*/
function configure( options ) {
- var numberOfSlides = document.querySelectorAll( SLIDES_SELECTOR ).length;
+ var numberOfSlides = dom.wrapper.querySelectorAll( SLIDES_SELECTOR ).length;
dom.wrapper.classList.remove( config.transition );
@@ -605,7 +778,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 );
@@ -614,9 +793,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
@@ -651,7 +834,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 );
@@ -667,7 +857,7 @@ var Reveal = (function(){
dom.progress.addEventListener( 'click', onProgressClicked, false );
}
- if( config.focusBodyOnPageVisiblityChange ) {
+ if( config.focusBodyOnPageVisibilityChange ) {
var visibilityChange;
if( 'hidden' in document ) {
@@ -712,7 +902,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 );
@@ -755,6 +952,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.
*
@@ -784,6 +997,23 @@ var Reveal = (function(){
}
/**
+ * Injects the given CSS styles into the DOM.
+ */
+ function injectStyleSheet( value ) {
+
+ var tag = document.createElement( 'style' );
+ tag.type = 'text/css';
+ if( tag.styleSheet ) {
+ tag.styleSheet.cssText = value;
+ }
+ else {
+ tag.appendChild( document.createTextNode( value ) );
+ }
+ document.getElementsByTagName( 'head' )[0].appendChild( tag );
+
+ }
+
+ /**
* Retrieves the height of the given element by looking
* at the position and height of its immediate children.
*/
@@ -798,7 +1028,7 @@ var Reveal = (function(){
if( typeof child.offsetTop === 'number' && child.style ) {
// Count # of abs children
- if( child.style.position === 'absolute' ) {
+ if( window.getComputedStyle( child ).position === 'absolute' ) {
absoluteChildren += 1;
}
@@ -820,40 +1050,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 ) {
+ var newHeight, oldHeight = element.style.height;
- 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;
-
- }
-
- } );
+ // 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;
@@ -898,13 +1114,19 @@ var Reveal = (function(){
* Dispatches an event of the specified type from the
* reveal DOM element.
*/
- function dispatchEvent( type, properties ) {
+ function dispatchEvent( type, args ) {
- var event = document.createEvent( "HTMLEvents", 1, 2 );
+ var event = document.createEvent( 'HTMLEvents', 1, 2 );
event.initEvent( type, true, true );
- extend( event, properties );
+ extend( event, args );
dom.wrapper.dispatchEvent( event );
+ // If we're in an iframe, post each reveal.js event to the
+ // parent window. Used by the notes plugin
+ if( config.postMessageEvents && window.parent !== window.self ) {
+ window.parent.postMessage( JSON.stringify({ namespace: 'reveal', eventName: type, state: getState() }), '*' );
+ }
+
}
/**
@@ -913,7 +1135,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 = dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' a' );
for( var i = 0, len = anchors.length; i < len; i++ ) {
var anchor = anchors[i];
@@ -937,7 +1159,7 @@ var Reveal = (function(){
*/
function disableRollingLinks() {
- var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a.roll' );
+ var anchors = dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' a.roll' );
for( var i = 0, len = anchors.length; i < len; i++ ) {
var anchor = anchors[i];
@@ -1081,54 +1303,38 @@ var Reveal = (function(){
if( dom.wrapper && !isPrintingPDF() ) {
- // Available space to scale within
- var availableWidth = dom.wrapper.offsetWidth,
- availableHeight = dom.wrapper.offsetHeight;
+ var size = getComputedSlideSize();
- // Reduce available space by margin
- availableWidth -= ( availableHeight * config.margin );
- availableHeight -= ( availableHeight * config.margin );
-
- // Dimensions of the content
- var slideWidth = config.width,
- slideHeight = config.height,
- slidePadding = 20; // TODO Dig this out of DOM
+ var 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;
- }
-
- // Slide height may be a percentage of available height
- if( typeof slideHeight === 'string' && /%$/.test( slideHeight ) ) {
- slideHeight = parseInt( slideHeight, 10 ) / 100 * availableHeight;
- }
-
- dom.slides.style.width = slideWidth + 'px';
- dom.slides.style.height = slideHeight + 'px';
+ dom.slides.style.width = size.width + 'px';
+ dom.slides.style.height = size.height + 'px';
// Determine scale of content to fit within available space
- scale = Math.min( availableWidth / slideWidth, availableHeight / slideHeight );
+ scale = Math.min( size.presentationWidth / size.width, size.presentationHeight / size.height );
// Respect max/min scale settings
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 desktop Chrome so that content remains crisp
+ if( !isMobileDevice && /chrome/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
- var slides = toArray( document.querySelectorAll( SLIDES_SELECTOR ) );
+ var slides = toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) );
for( var i = 0, len = slides.length; i < len; i++ ) {
var slide = slides[ i ];
@@ -1145,7 +1351,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( ( ( size.height - getAbsoluteHeight( slide ) ) / 2 ) - slidePadding, 0 ) + 'px';
}
}
else {
@@ -1171,7 +1377,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 ) ) {
@@ -1194,6 +1400,41 @@ var Reveal = (function(){
}
/**
+ * Calculates the computed pixel size of our slides. These
+ * values are based on the width and height configuration
+ * options.
+ */
+ function getComputedSlideSize( presentationWidth, presentationHeight ) {
+
+ var size = {
+ // Slide size
+ width: config.width,
+ height: config.height,
+
+ // Presentation size
+ presentationWidth: presentationWidth || dom.wrapper.offsetWidth,
+ presentationHeight: presentationHeight || dom.wrapper.offsetHeight
+ };
+
+ // Reduce available space by margin
+ size.presentationWidth -= ( size.presentationHeight * config.margin );
+ size.presentationHeight -= ( size.presentationHeight * config.margin );
+
+ // Slide width may be a percentage of available width
+ if( typeof size.width === 'string' && /%$/.test( size.width ) ) {
+ size.width = parseInt( size.width, 10 ) / 100 * size.presentationWidth;
+ }
+
+ // Slide height may be a percentage of available height
+ if( typeof size.height === 'string' && /%$/.test( size.height ) ) {
+ size.height = parseInt( size.height, 10 ) / 100 * size.presentationHeight;
+ }
+
+ return size;
+
+ }
+
+ /**
* Stores the vertical index of a stack so that the same
* vertical slide can be selected when navigating to and
* from the stack.
@@ -1252,67 +1493,57 @@ var Reveal = (function(){
dom.wrapper.classList.add( 'overview' );
dom.wrapper.classList.remove( 'overview-deactivating' );
- clearTimeout( activateOverviewTimeout );
- clearTimeout( deactivateOverviewTimeout );
+ var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
- // 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() {
+ for( var i = 0, len1 = horizontalSlides.length; i < len1; i++ ) {
+ var hslide = horizontalSlides[i],
+ hoffset = config.rtl ? -105 : 105;
- var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
+ hslide.setAttribute( 'data-index-h', i );
- for( var i = 0, len1 = horizontalSlides.length; i < len1; i++ ) {
- var hslide = horizontalSlides[i],
- hoffset = config.rtl ? -105 : 105;
+ // Apply CSS transform
+ transformElement( hslide, 'translateZ(-'+ depth +'px) translate(' + ( ( i - indexh ) * hoffset ) + '%, 0%)' );
- hslide.setAttribute( 'data-index-h', i );
+ if( hslide.classList.contains( 'stack' ) ) {
- // Apply CSS transform
- transformElement( hslide, 'translateZ(-'+ depth +'px) translate(' + ( ( i - indexh ) * hoffset ) + '%, 0%)' );
+ var verticalSlides = hslide.querySelectorAll( 'section' );
- if( hslide.classList.contains( 'stack' ) ) {
+ for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) {
+ var verticalIndex = i === indexh ? indexv : getPreviousVerticalIndex( hslide );
- var verticalSlides = hslide.querySelectorAll( 'section' );
+ var vslide = verticalSlides[j];
- for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) {
- var verticalIndex = i === indexh ? indexv : getPreviousVerticalIndex( hslide );
+ vslide.setAttribute( 'data-index-h', i );
+ vslide.setAttribute( 'data-index-v', j );
- var vslide = verticalSlides[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
+ } );
+ }
}
@@ -1327,9 +1558,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
@@ -1337,12 +1565,12 @@ 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 );
// Select all slides
- toArray( document.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
+ toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
// Resets all transforms to use the external styles
transformElement( slide, '' );
@@ -1424,7 +1652,7 @@ var Reveal = (function(){
element.webkitRequestFullscreen ||
element.webkitRequestFullScreen ||
element.mozRequestFullScreen ||
- element.msRequestFullScreen;
+ element.msRequestFullscreen;
if( requestMethod ) {
requestMethod.apply( element );
@@ -1468,13 +1696,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();
}
}
@@ -1489,6 +1717,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.
@@ -1505,7 +1761,7 @@ var Reveal = (function(){
previousSlide = currentSlide;
// Query all horizontal slides in the deck
- var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
+ var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
// If no vertical index is specified and the upcoming slide is a
// stack, resume at its previous vertical index
@@ -1601,10 +1857,10 @@ var Reveal = (function(){
// Reset all slides upon navigate to home
// Issue: #285
- if ( document.querySelector( HOME_SLIDE_SELECTOR ).classList.contains( 'present' ) ) {
+ if ( dom.wrapper.querySelector( HOME_SLIDE_SELECTOR ).classList.contains( 'present' ) ) {
// Launch async task
setTimeout( function () {
- var slides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.stack') ), i;
+ var slides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.stack') ), i;
for( i in slides ) {
if( slides[i] ) {
// Reset stack
@@ -1616,7 +1872,7 @@ var Reveal = (function(){
}
// Handle embedded content
- if( slideChanged ) {
+ if( slideChanged || !previousSlide ) {
stopEmbeddedContent( previousSlide );
startEmbeddedContent( currentSlide );
}
@@ -1657,12 +1913,16 @@ var Reveal = (function(){
// Re-create the slide backgrounds
createBackgrounds();
+ // Write the current hash to the URL
+ writeURL();
+
sortAllFragments();
updateControls();
updateProgress();
updateBackground( true );
updateSlideNumber();
+ updateSlidesVisibility();
}
@@ -1672,7 +1932,7 @@ var Reveal = (function(){
*/
function resetVerticalSlides() {
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
horizontalSlides.forEach( function( horizontalSlide ) {
var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
@@ -1696,7 +1956,7 @@ var Reveal = (function(){
*/
function sortAllFragments() {
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
horizontalSlides.forEach( function( horizontalSlide ) {
var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
@@ -1729,9 +1989,11 @@ var Reveal = (function(){
// Select all slides and convert the NodeList result to
// an array
- var slides = toArray( document.querySelectorAll( selector ) ),
+ var slides = toArray( dom.wrapper.querySelectorAll( selector ) ),
slidesLength = slides.length;
+ var printMode = isPrintingPDF();
+
if( slidesLength ) {
// Should the index loop?
@@ -1758,37 +2020,47 @@ var Reveal = (function(){
// http://www.w3.org/html/wg/drafts/html/master/editing.html#the-hidden-attribute
element.setAttribute( 'hidden', '' );
+ // If this element contains vertical slides
+ if( element.querySelector( 'section' ) ) {
+ element.classList.add( 'stack' );
+ }
+
+ // If we're printing static slides, all slides are "present"
+ if( printMode ) {
+ element.classList.add( 'present' );
+ continue;
+ }
+
if( i < index ) {
// 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' );
+ }
}
}
-
- // If this element contains vertical slides
- if( element.querySelector( 'section' ) ) {
- element.classList.add( 'stack' );
- }
}
// Mark the current slide as present
@@ -1821,7 +2093,7 @@ var Reveal = (function(){
// Select all slides and convert the NodeList result to
// an array
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ),
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ),
horizontalSlidesLength = horizontalSlides.length,
distanceX,
distanceY;
@@ -1834,7 +2106,12 @@ var Reveal = (function(){
// Limit view distance on weaker devices
if( isMobileDevice ) {
- viewDistance = isOverview() ? 6 : 1;
+ viewDistance = isOverview() ? 6 : 2;
+ }
+
+ // Limit view distance on weaker devices
+ if( isPrintingPDF() ) {
+ viewDistance = Number.MAX_VALUE;
}
for( var x = 0; x < horizontalSlidesLength; x++ ) {
@@ -1847,7 +2124,12 @@ var Reveal = (function(){
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( distanceX < viewDistance ) {
+ showSlide( horizontalSlide );
+ }
+ else {
+ hideSlide( horizontalSlide );
+ }
if( verticalSlidesLength ) {
@@ -1858,7 +2140,12 @@ var Reveal = (function(){
distanceY = x === indexh ? Math.abs( indexv - y ) : Math.abs( y - oy );
- verticalSlide.style.display = ( distanceX + distanceY ) > viewDistance ? 'none' : 'block';
+ if( distanceX + distanceY < viewDistance ) {
+ showSlide( verticalSlide );
+ }
+ else {
+ hideSlide( verticalSlide );
+ }
}
}
@@ -1874,44 +2161,9 @@ var Reveal = (function(){
function updateProgress() {
// 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;
+ if( config.progress && dom.progressbar ) {
- // 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';
}
@@ -2005,30 +2257,38 @@ var Reveal = (function(){
// states of their slides (past/present/future)
toArray( dom.background.childNodes ).forEach( function( backgroundh, h ) {
+ backgroundh.classList.remove( 'past' );
+ backgroundh.classList.remove( 'present' );
+ backgroundh.classList.remove( 'future' );
+
if( h < indexh ) {
- backgroundh.className = 'slide-background ' + horizontalPast;
+ backgroundh.classList.add( horizontalPast );
}
else if ( h > indexh ) {
- backgroundh.className = 'slide-background ' + horizontalFuture;
+ backgroundh.classList.add( horizontalFuture );
}
else {
- backgroundh.className = 'slide-background present';
+ backgroundh.classList.add( 'present' );
// Store a reference to the current background element
currentBackground = backgroundh;
}
if( includeAll || h === indexh ) {
- toArray( backgroundh.childNodes ).forEach( function( backgroundv, v ) {
+ toArray( backgroundh.querySelectorAll( '.slide-background' ) ).forEach( function( backgroundv, v ) {
+
+ backgroundv.classList.remove( 'past' );
+ backgroundv.classList.remove( 'present' );
+ backgroundv.classList.remove( 'future' );
if( v < indexv ) {
- backgroundv.className = 'slide-background past';
+ backgroundv.classList.add( 'past' );
}
else if ( v > indexv ) {
- backgroundv.className = 'slide-background future';
+ backgroundv.classList.add( 'future' );
}
else {
- backgroundv.className = 'slide-background present';
+ backgroundv.classList.add( 'present' );
// Only if this is the present horizontal and vertical slide
if( h === indexh ) currentBackground = backgroundv;
@@ -2039,9 +2299,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 ) {
@@ -2049,6 +2322,7 @@ var Reveal = (function(){
}
previousBackground = currentBackground;
+
}
// Allow the first background to apply without transition
@@ -2066,8 +2340,8 @@ var Reveal = (function(){
if( config.parallaxBackgroundImage ) {
- var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
- verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
+ var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
+ verticalSlides = dom.wrapper.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
var backgroundSize = dom.background.style.backgroundSize.split( ' ' ),
backgroundWidth, backgroundHeight;
@@ -2086,7 +2360,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';
@@ -2095,14 +2369,99 @@ var Reveal = (function(){
}
/**
+ * 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).
+ */
+ function showSlide( slide ) {
+
+ // Show the slide element
+ slide.style.display = 'block';
+
+ // Media elements with data-src attributes
+ toArray( slide.querySelectorAll( 'img[data-src], video[data-src], audio[data-src], iframe[data-src]' ) ).forEach( function( element ) {
+ element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
+ element.removeAttribute( 'data-src' );
+ } );
+
+ // Media elements with <source> children
+ toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( media ) {
+ var sources = 0;
+
+ toArray( media.querySelectorAll( 'source[data-src]' ) ).forEach( function( source ) {
+ source.setAttribute( 'src', source.getAttribute( 'data-src' ) );
+ source.removeAttribute( 'data-src' );
+ sources += 1;
+ } );
+
+ // If we rewrote sources for this video/audio element, we need
+ // to manually tell it to load from its new origin
+ if( sources > 0 ) {
+ media.load();
+ }
+ } );
+
+
+ // Show the corresponding background element
+ var indices = getIndices( slide );
+ var background = getSlideBackground( indices.h, indices.v );
+ if( background ) {
+ background.style.display = 'block';
+
+ // If the background contains media, load it
+ if( background.hasAttribute( 'data-loaded' ) === false ) {
+ background.setAttribute( 'data-loaded', 'true' );
+
+ var backgroundImage = slide.getAttribute( 'data-background-image' ),
+ backgroundVideo = slide.getAttribute( 'data-background-video' );
+
+ // Images
+ if( backgroundImage ) {
+ background.style.backgroundImage = 'url('+ backgroundImage +')';
+ }
+ // Videos
+ else if ( backgroundVideo ) {
+ var video = document.createElement( 'video' );
+
+ // Support comma separated lists of video sources
+ backgroundVideo.split( ',' ).forEach( function( source ) {
+ video.innerHTML += '<source src="'+ source +'">';
+ } );
+
+ background.appendChild( video );
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Called when the given slide is moved outside of the
+ * configured view distance.
+ */
+ function hideSlide( 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 );
+ if( background ) {
+ background.style.display = 'none';
+ }
+
+ }
+
+ /**
* Determine what available routes there are for navigation.
*
* @return {Object} containing four booleans: left/right/up/down
*/
function availableRoutes() {
- var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
- verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
+ var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
+ verticalSlides = dom.wrapper.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
var routes = {
left: indexh > 0 || config.loop,
@@ -2204,6 +2563,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( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
+
+ // The number of past and total slides
+ var totalCount = dom.wrapper.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.
*/
@@ -2227,8 +2650,13 @@ 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;
+
+ // Ensure the named link is a valid HTML ID attribute
+ if( /^[a-zA-Z][\w:.-]*$/.test( name ) ) {
+ // Find the slide with the specified ID
+ element = document.querySelector( '#' + name );
+ }
if( element ) {
// Find the position of the named slide and navigate to it
@@ -2270,12 +2698,19 @@ var Reveal = (function(){
if( typeof delay === 'number' ) {
writeURLTimeout = setTimeout( writeURL, delay );
}
- else {
+ else if( currentSlide ) {
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( typeof id === 'string' && id.length ) {
+ url = '/' + id;
}
// Otherwise use the /h/v index
else {
@@ -2312,11 +2747,14 @@ var Reveal = (function(){
var slideh = isVertical ? slide.parentNode : slide;
// Select all horizontal slides
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
// Now that we know which the horizontal slide is, get its index
h = Math.max( horizontalSlides.indexOf( slideh ), 0 );
+ // Assume we're not vertical
+ v = undefined;
+
// If this is a vertical slide, grab the vertical index
if( isVertical ) {
v = Math.max( toArray( slide.parentNode.querySelectorAll( 'section' ) ).indexOf( slide ), 0 );
@@ -2336,6 +2774,107 @@ var Reveal = (function(){
}
/**
+ * Retrieves the total number of slides in this presentation.
+ */
+ function getTotalSlides() {
+
+ return dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ).length;
+
+ }
+
+ /**
+ * Returns the slide element matching the specified index.
+ */
+ function getSlide( x, y ) {
+
+ var horizontalSlide = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR )[ x ];
+ var verticalSlides = horizontalSlide && horizontalSlide.querySelectorAll( 'section' );
+
+ if( verticalSlides && verticalSlides.length && typeof y === 'number' ) {
+ return verticalSlides ? verticalSlides[ y ] : undefined;
+ }
+
+ return horizontalSlide;
+
+ }
+
+ /**
+ * Returns the background element for the given slide.
+ * All slides, even the ones with no background properties
+ * defined, have a background element so as long as the
+ * index is valid an element will be returned.
+ */
+ 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 ) {
+ var background = slide.querySelector( '.slide-background' );
+ if( background && background.parentNode === slide ) {
+ return background;
+ }
+ }
+
+ 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;
+ }
+
+ return horizontalBackground;
+
+ }
+
+ /**
+ * 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 ) );
+
+ var pausedFlag = deserialize( state.paused ),
+ overviewFlag = deserialize( state.overview );
+
+ if( typeof pausedFlag === 'boolean' && pausedFlag !== isPaused() ) {
+ togglePause( pausedFlag );
+ }
+
+ if( typeof overviewFlag === 'boolean' && overviewFlag !== isOverview() ) {
+ toggleOverview( overviewFlag );
+ }
+ }
+
+ }
+
+ /**
* Return a sorted fragments list, ordered by an increasing
* "data-fragment-index" attribute.
*
@@ -2469,6 +3008,7 @@ var Reveal = (function(){
}
updateControls();
+ updateProgress();
return !!( fragmentsShown.length || fragmentsHidden.length );
@@ -2513,14 +3053,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 ) {
@@ -2572,19 +3119,25 @@ var Reveal = (function(){
function pauseAutoSlide() {
- autoSlidePaused = true;
- clearTimeout( autoSlideTimeout );
+ if( autoSlide && !autoSlidePaused ) {
+ autoSlidePaused = true;
+ dispatchEvent( 'autoslidepaused' );
+ clearTimeout( autoSlideTimeout );
- if( autoSlidePlayer ) {
- autoSlidePlayer.setPlaying( false );
+ if( autoSlidePlayer ) {
+ autoSlidePlayer.setPlaying( false );
+ }
}
}
function resumeAutoSlide() {
- autoSlidePaused = false;
- cueAutoSlide();
+ if( autoSlide && autoSlidePaused ) {
+ autoSlidePaused = false;
+ dispatchEvent( 'autoslideresumed' );
+ cueAutoSlide();
+ }
}
@@ -2651,7 +3204,7 @@ var Reveal = (function(){
}
else {
// Fetch the previous horizontal slide, if there is one
- var previousSlide = document.querySelector( HORIZONTAL_SLIDES_SELECTOR + '.past:nth-child(' + indexh + ')' );
+ var previousSlide = dom.wrapper.querySelector( HORIZONTAL_SLIDES_SELECTOR + '.past:nth-child(' + indexh + ')' );
if( previousSlide ) {
var v = ( previousSlide.querySelectorAll( 'section' ).length - 1 ) || undefined;
@@ -2712,16 +3265,25 @@ var Reveal = (function(){
*/
function onDocumentKeyDown( event ) {
+ // If there's a condition specified and it returns false,
+ // ignore this event
+ if( typeof config.keyboardCondition === 'function' && config.keyboardCondition() === false ) {
+ return true;
+ }
+
+ // 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
// the keyboard
- var activeElement = document.activeElement;
- var hasFocus = !!( document.activeElement && ( document.activeElement.type || document.activeElement.href || document.activeElement.contentEditable !== 'inherit' ) );
+ var activeElementIsCE = document.activeElement && document.activeElement.contentEditable !== 'inherit';
+ var activeElementIsInput = document.activeElement && document.activeElement.tagName && /input|textarea/i.test( document.activeElement.tagName );
// Disregard the event if there's a focused element or a
// keyboard modifier key is present
- if( hasFocus || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
+ if( activeElementIsCE || activeElementIsInput || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
// While paused only allow "unpausing" keyboard events (b and .)
if( isPaused() && [66,190,191].indexOf( event.keyCode ) === -1 ) {
@@ -2784,10 +3346,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;
}
@@ -2797,7 +3361,7 @@ var Reveal = (function(){
// If the input resulted in a triggered action we should prevent
// the browsers default behavior
if( triggered ) {
- event.preventDefault();
+ event.preventDefault && event.preventDefault();
}
// ESC or O key
else if ( ( event.keyCode === 27 || event.keyCode === 79 ) && features.transforms3d ) {
@@ -2808,7 +3372,7 @@ var Reveal = (function(){
toggleOverview();
}
- event.preventDefault();
+ event.preventDefault && event.preventDefault();
}
// If auto-sliding is enabled we need to cue up
@@ -2942,7 +3506,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 );
}
@@ -2954,7 +3518,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 );
}
@@ -2966,7 +3530,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 );
}
@@ -3007,7 +3571,7 @@ var Reveal = (function(){
event.preventDefault();
- var slidesTotal = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
+ var slidesTotal = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
var slideIndex = Math.floor( ( event.clientX / dom.wrapper.offsetWidth ) * slidesTotal );
slide( slideIndex );
@@ -3297,7 +3861,7 @@ var Reveal = (function(){
// --------------------------------------------------------------------//
- return {
+ Reveal = {
initialize: initialize,
configure: configure,
sync: sync,
@@ -3340,28 +3904,35 @@ 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,
- // Returns the slide at the specified index, y is optional
- getSlide: function( x, y ) {
- var horizontalSlide = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR )[ x ];
- var verticalSlides = horizontalSlide && horizontalSlide.querySelectorAll( 'section' );
+ getTotalSlides: getTotalSlides,
- if( typeof y !== 'undefined' ) {
- return verticalSlides ? verticalSlides[ y ] : undefined;
- }
+ // Returns the slide element at the specified index
+ getSlide: getSlide,
- return horizontalSlide;
- },
+ // Returns the slide background element at the specified index
+ getSlideBackground: getSlideBackground,
// Returns the previous slide element, may be null
getPreviousSlide: function() {
@@ -3395,12 +3966,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;
@@ -3408,7 +3974,7 @@ var Reveal = (function(){
// Returns true if we're currently on the first slide
isFirstSlide: function() {
- return document.querySelector( SLIDES_SELECTOR + '.past' ) == null ? true : false;
+ return ( indexh === 0 && indexv === 0 );
},
// Returns true if we're currently on the last slide
@@ -3441,7 +4007,14 @@ var Reveal = (function(){
if( 'addEventListener' in window ) {
( dom.wrapper || document.querySelector( '.reveal' ) ).removeEventListener( type, listener, useCapture );
}
+ },
+
+ // Programatically triggers a keyboard event
+ triggerKey: function( keyCode ) {
+ onDocumentKeyDown( { keyCode: keyCode } );
}
};
-})();
+ return Reveal;
+
+}));