Newer
Older
* http://lab.hakim.se/reveal-js
* MIT licensed
(function( root, factory ) {
if( typeof define === 'function' && define.amd ) {
root.Reveal = factory();
return root.Reveal;
} );
} else if( typeof exports === 'object' ) {
// Node. Does not work with strict CommonJS.
'use strict';

Hakim El Hattab
committed
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

Hakim El Hattab
committed

hakimel
committed
// The "normal" size of the presentation, aspect ratio will be preserved
// when the presentation is scaled to fit different resolutions

hakimel
committed
// Factor of the display size that should remain empty around the content

hakimel
committed
margin: 0.1,

Hakim El Hattab
committed
// Bounds for smallest/largest possible scale to apply to content
minScale: 0.2,

Hakim El Hattab
committed
// Display controls in the bottom right corner

Hakim El Hattab
committed
// Display a presentation progress bar
progress: true,

Hakim El Hattab
committed
// Display the page number of the current slide
slideNumber: false,

Hakim El Hattab
committed
// Push each slide change to the browser history

Hakim El Hattab
committed
// 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,
// Vertical centering of slides

Hakim El Hattab
committed
center: true,
// Enables touch navigation on devices with touch input
touch: true,

Hakim El Hattab
committed
// Loop the presentation

Hakim El Hattab
committed
// Change the presentation direction to be RTL
// Turns fragments on and off globally
fragments: true,
// Flags if the presentation is running in an embedded mode,
// i.e. contained within a limited portion of the screen
embedded: false,
// Flags if we should show a help overlay when the questionmark
// key is pressed
help: true,
// Flags if it should be possible to pause the presentation (blackout)
pause: true,
// Number of milliseconds between automatically proceeding to the
// next slide, disabled when set to 0, this value can be overwritten
// by using a data-autoslide attribute on your slides

Hakim El Hattab
committed
autoSlide: 0,

Hakim El Hattab
committed
// Stop auto-sliding after user input
autoSlideStoppable: true,

Hakim El Hattab
committed

Hakim El Hattab
committed
// Enable slide navigation via mouse wheel

Hakim El Hattab
committed
mouseWheel: false,

Hakim El Hattab
committed
// Apply a 3D roll to links on hover
rollingLinks: false,
// Hides the address bar on mobile devices
hideAddressBar: true,

Hakim El Hattab
committed
// Opens links in an iframe preview overlay
previewLinks: false,
// Exposes the reveal.js API through window.postMessage
// Dispatches all reveal.js events to the parent window through postMessage
postMessageEvents: false,

Espen Hovlandsdal
committed
// Focuses body when page changes visiblity to ensure keyboard shortcuts work

Espen Hovlandsdal
committed

Hakim El Hattab
committed
// Transition style
transition: 'default', // none/fade/slide/convex/concave/zoom
// Transition speed
transitionSpeed: 'default', // default/fast/slow
// Transition style for full page slide backgrounds
backgroundTransition: 'default', // none/fade/slide/convex/concave/zoom

Hakim El Hattab
committed
parallaxBackgroundImage: '', // CSS syntax, e.g. "a.jpg"

Hakim El Hattab
committed
// Parallax background size
parallaxBackgroundSize: '', // CSS syntax, e.g. "3000px 2000px"
// Number of slides away from the current that are visible
viewDistance: 3,
// Script dependencies to load

Hakim El Hattab
committed
dependencies: []
// Flags if reveal.js is loaded (has dispatched the 'ready' event)
loaded = false,
// The horizontal and vertical index of the currently active slide

Hakim El Hattab
committed
// The previous and current slide HTML elements
previousSlide,
currentSlide,
previousBackground,
// Slides may hold a data-state attribute which we pick up and apply
// as a class to the body. This list contains the combined state of

Hakim El Hattab
committed
// all current slides.
state = [],

Hakim El Hattab
committed
// The current scale of the presentation (see width/height config)
scale = 1,

Hakim El Hattab
committed
// Features supported by the browser, see #checkCapabilities()
features = {},
// Client is a mobile device, see #checkCapabilities()
isMobileDevice,

Hakim El Hattab
committed
// Delays updates to the URL due to a Chrome thumbnailer bug
// Flags if the interaction event listeners are bound
eventsAreBound = false,
// The current auto-slide duration
autoSlide = 0,
// Auto slide properties
autoSlideTimeout = 0,
autoSlideStartTime = -1,
autoSlidePaused = false,
// Holds information about the currently ongoing touch input
touch = {
startX: 0,
startY: 0,
startSpan: 0,
startCount: 0,
},
// Holds information about the keyboard shortcuts
'N , SPACE': 'Next slide',
'P': 'Previous slide',
'← , H': 'Navigate left',
'→ , L': 'Navigate right',
'↑ , K': 'Navigate up',
'↓ , J': 'Navigate down',
'Home': 'First slide',
'End': 'Last slide',
'B , .': 'Pause',
'F': 'Fullscreen',
'ESC, O': 'Slide overview'
* Starts up the presentation if the client is capable.
function initialize( options ) {
checkCapabilities();

Hakim El Hattab
committed
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

Hakim El Hattab
committed
// 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 );
var query = Reveal.getQueryHash();
// Do not accept new dependencies via query config to avoid
// the potential of malicious script injection
if( typeof query['dependencies'] !== 'undefined' ) delete query['dependencies'];
// Copy options over to our config object
extend( config, options );

Hakim El Hattab
committed
// Hide the address bar in mobile browsers
hideAddressBar();
// Loads the dependencies and continues to #start() once done
load();

Hakim El Hattab
committed
}
/**
* Inspect the client to see what it's capable of, this
* should only happens once per runtime.
*/
function checkCapabilities() {

Hakim El Hattab
committed
features.transforms3d = 'WebkitPerspective' in document.body.style ||
'MozPerspective' in document.body.style ||
'msPerspective' in document.body.style ||
'OPerspective' in document.body.style ||
'perspective' in document.body.style;

Hakim El Hattab
committed
features.transforms2d = 'WebkitTransform' in document.body.style ||
'MozTransform' in document.body.style ||
'msTransform' in document.body.style ||
'OTransform' in document.body.style ||
'transform' in document.body.style;
features.requestAnimationFrameMethod = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
features.requestAnimationFrame = typeof features.requestAnimationFrameMethod === 'function';

Hakim El Hattab
committed
features.canvas = !!document.createElement( 'canvas' ).getContext;
features.touch = !!( 'ontouchstart' in window );
isMobileDevice = navigator.userAgent.match( /(iphone|ipod|ipad|android)/gi );

Thomas Endres
committed
/**
* Loads the dependencies of reveal.js. Dependencies are
* defined via the configuration option 'dependencies'
* and will be loaded prior to starting/binding reveal.js.
* Some dependencies may have an 'async' flag, if so they
* will load after reveal.js has been started up.
*/
function load() {
var scripts = [],
scriptsAsync = [],
scriptsToPreload = 0;
// Called once synchronous scripts finish loading
function proceed() {
if( scriptsAsync.length ) {
// Load asynchronous scripts
head.js.apply( null, scriptsAsync );
}
start();
}
function loadScript( s ) {
head.ready( s.src.match( /([\w\d_\-]*)\.?js$|[^\\\/]*$/i )[0], function() {
// Extension may contain callback functions
if( typeof s.callback === 'function' ) {
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
}
if( --scriptsToPreload === 0 ) {
proceed();
}
});
}
for( var i = 0, len = config.dependencies.length; i < len; i++ ) {
var s = config.dependencies[i];
// Load if there's no condition or the condition is truthy
if( !s.condition || s.condition() ) {
if( s.async ) {
scriptsAsync.push( s.src );
}
else {
scripts.push( s.src );
}
loadScript( s );
}
}
if( scripts.length ) {
scriptsToPreload = scripts.length;
// Load synchronous scripts
head.js.apply( null, scripts );
}
else {
proceed();
}
}
/**
* Starts up reveal.js by binding input events and navigating
* to the current URL deeplink if there is one.
*/
function start() {
// Make sure we've got all the DOM elements we need
setupDOM();
// Listen to messages posted to this window
setupPostMessage();
// Resets all vertical slides so that only the first is visible
resetVerticalSlides();
// Updates the presentation to match the current configuration values
configure();
// Read the initial hash
readURL();
// Update all backgrounds
updateBackground( true );
// Notify listeners that the presentation is ready but use a 1ms
// timeout to ensure it's not fired synchronously after #initialize()
setTimeout( function() {
// Enable transitions now that we're loaded
dom.slides.classList.remove( 'no-transition' );
loaded = true;
dispatchEvent( 'ready', {
'indexh': indexh,
'indexv': indexv,
'currentSlide': currentSlide
} );
}, 1 );
// 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 );
}
}

Hakim El Hattab
committed
/**
* Finds and stores references to DOM elements which are
* required by the presentation. If a required element is

Hakim El Hattab
committed
* not found, it is created.
*/
function setupDOM() {
// Prevent transitions while we're loading
dom.slides.classList.add( 'no-transition' );
// Background element
dom.background = createSingletonNode( dom.wrapper, 'div', 'backgrounds', null );

Hakim El Hattab
committed
// Progress bar
dom.progress = createSingletonNode( dom.wrapper, 'div', 'progress', '<span></span>' );
dom.progressbar = dom.progress.querySelector( 'span' );

Hakim El Hattab
committed
// Arrow controls
'<div class="navigate-left"></div>' +
'<div class="navigate-right"></div>' +
'<div class="navigate-up"></div>' +

Hakim El Hattab
committed

Hakim El Hattab
committed
// Slide number
dom.slideNumber = createSingletonNode( dom.wrapper, 'div', 'slide-number', '' );

Hakim El Hattab
committed
// State background element [DEPRECATED]
createSingletonNode( dom.wrapper, 'div', 'state-background', null );

Hakim El Hattab
committed
// Overlay graphic which is displayed during the paused mode
createSingletonNode( dom.wrapper, 'div', 'pause-overlay', null );

Hakim El Hattab
committed
// Cache references to elements
dom.controls = document.querySelector( '.reveal .controls' );

Hakim El Hattab
committed
dom.theme = document.querySelector( '#theme' );
dom.wrapper.setAttribute( 'role', 'application' );

Nawaz
committed
// There can be multiple instances of controls throughout the page
dom.controlsLeft = toArray( document.querySelectorAll( '.navigate-left' ) );
dom.controlsRight = toArray( document.querySelectorAll( '.navigate-right' ) );
dom.controlsUp = toArray( document.querySelectorAll( '.navigate-up' ) );
dom.controlsDown = toArray( document.querySelectorAll( '.navigate-down' ) );
dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) );
dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) );

Nawaz
committed
dom.statusDiv = createStatusDiv();
}
/**
* Creates a hidden div with role aria-live to announce the
* current slide content. Hide the div off-screen to make it
* available only to Assistive Technologies.

Nawaz
committed
*/
function createStatusDiv() {
var statusDiv = document.getElementById( 'aria-status-div' );
if( !statusDiv ) {

Nawaz
committed
statusDiv = document.createElement( 'div' );
statusDiv.style.position = 'absolute';
statusDiv.style.height = '1px';
statusDiv.style.width = '1px';
statusDiv.style.overflow ='hidden';
statusDiv.style.clip = 'rect( 1px, 1px, 1px, 1px )';
statusDiv.setAttribute( 'id', 'aria-status-div' );

Nawaz
committed
statusDiv.setAttribute( 'aria-live', 'polite' );
statusDiv.setAttribute( 'aria-atomic','true' );
dom.wrapper.appendChild( statusDiv );
}
return statusDiv;

Hakim El Hattab
committed
}
/**
* Configures the presentation for printing to a static
* PDF.
*/
function setupPDF() {
var slideSize = getComputedSlideSize( window.innerWidth, window.innerHeight );
var pageWidth = Math.floor( slideSize.width * ( 1 + config.margin ) ),
pageHeight = Math.floor( slideSize.height * ( 1 + config.margin ) );
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 section>img, .reveal section>video, .reveal section>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

Hakim El Hattab
committed
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

Hakim El Hattab
committed
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 ) {
// 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;
// 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 );
/**
* Creates the slide background elements and appends them
* to the background container. One element is created per
* slide no matter if the given slide has visible background.
*/
function createBackgrounds() {
var printMode = isPrintingPDF();
// Clear prior backgrounds
dom.background.innerHTML = '';
dom.background.classList.add( 'no-transition' );
// Iterate over all horizontal slides

Hakim El Hattab
committed
toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) {
var backgroundStack;
if( printMode ) {
backgroundStack = createBackground( slideh, slideh );
}
else {
backgroundStack = createBackground( slideh, dom.background );
// Iterate over all vertical slides
toArray( slideh.querySelectorAll( 'section' ) ).forEach( function( slidev ) {
if( printMode ) {
createBackground( slidev, slidev );
}
else {
createBackground( slidev, backgroundStack );
backgroundStack.classList.add( 'stack' );
} );
} );

Hakim El Hattab
committed
// Add parallax background if specified
if( config.parallaxBackgroundImage ) {

Hakim El Hattab
committed
dom.background.style.backgroundImage = 'url("' + config.parallaxBackgroundImage + '")';
dom.background.style.backgroundSize = config.parallaxBackgroundSize;

Hakim El Hattab
committed
// Make sure the below properties are set on the element - these properties are
// needed for proper transitions to be set on the element via CSS. To remove
// annoying background slide-in effect when the presentation starts, apply
// these properties after short time delay
setTimeout( function() {
dom.wrapper.classList.add( 'has-parallax-background' );

Hakim El Hattab
committed
else {
dom.background.style.backgroundImage = '';
dom.wrapper.classList.remove( 'has-parallax-background' );
}
/**
* 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' ),
backgroundIframe: slide.getAttribute( 'data-background-iframe' ),
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 || data.backgroundIframe ) {
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 );
// If backgrounds are being recreated, clear old classes
slide.classList.remove( 'has-dark-background' );
slide.classList.remove( 'has-light-background' );

Hakim El Hattab
committed
// If this slide has a background color, add a class that
// signals if it is light or dark. If the slide has no background
// color, no class will be set
var computedBackgroundColor = window.getComputedStyle( element ).backgroundColor;
var rgb = colorToRgb( computedBackgroundColor );
// Ignore fully transparent backgrounds. Some browsers return
// rgba(0,0,0,0) when reading the computed background color of
// an element with no background
if( rgb && rgb.a !== 0 ) {
if( colorBrightness( computedBackgroundColor ) < 128 ) {
slide.classList.add( 'has-dark-background' );
}
else {
slide.classList.add( 'has-light-background' );
}
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 );
}
* Applies the configuration settings from the config
* object. May be called multiple times.
function configure( options ) {

Hakim El Hattab
committed
var numberOfSlides = dom.wrapper.querySelectorAll( SLIDES_SELECTOR ).length;
dom.wrapper.classList.remove( config.transition );
// New config options may be passed when this method
// is invoked through the API after initialization
if( typeof options === 'object' ) extend( config, options );
// Force linear transition based on browser capabilities

Hakim El Hattab
committed
if( features.transforms3d === false ) config.transition = 'linear';
dom.wrapper.classList.add( config.transition );
dom.wrapper.setAttribute( 'data-transition-speed', config.transitionSpeed );
dom.wrapper.setAttribute( 'data-background-transition', config.backgroundTransition );
dom.controls.style.display = config.controls ? 'block' : 'none';
dom.progress.style.display = config.progress ? 'block' : 'none';
if( config.rtl ) {
dom.wrapper.classList.add( 'rtl' );
}
else {
dom.wrapper.classList.remove( 'rtl' );
}
if( config.center ) {
dom.wrapper.classList.add( 'center' );
}
else {
dom.wrapper.classList.remove( 'center' );
}
// Exit the paused mode if it was configured off
if( config.pause === false ) {
resume();
}
document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
document.addEventListener( 'mousewheel', onDocumentMouseScroll, false );
else {
document.removeEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
document.removeEventListener( 'mousewheel', onDocumentMouseScroll, false );
}
// Rolling 3D links
if( config.rollingLinks ) {
enableRollingLinks();
}
else {
disableRollingLinks();
}

Hakim El Hattab
committed
// Iframe link previews
if( config.previewLinks ) {
enablePreviewLinks();
}
else {

Hakim El Hattab
committed
disablePreviewLinks();
enablePreviewLinks( '[data-preview-link]' );

Hakim El Hattab
committed
// 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 );
autoSlidePlayer.on( 'click', onAutoSlidePlayerClick );
autoSlidePaused = false;
// 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' );
} );
}

Hakim El Hattab
committed
/**
* Binds all event listeners.
*/
function addEventListeners() {
eventsAreBound = true;
window.addEventListener( 'hashchange', onWindowHashChange, false );
window.addEventListener( 'resize', onWindowResize, false );
if( config.touch ) {
dom.wrapper.addEventListener( 'touchstart', onTouchStart, false );
dom.wrapper.addEventListener( 'touchmove', onTouchMove, false );
dom.wrapper.addEventListener( 'touchend', onTouchEnd, false );
// Support pointer-style touch interaction as well
// 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 );
}
if( config.keyboard ) {
document.addEventListener( 'keydown', onDocumentKeyDown, false );
document.addEventListener( 'keypress', onDocumentKeyPress, false );
}

Espen Hovlandsdal
committed
if( config.progress && dom.progress ) {

Hakim El Hattab
committed
dom.progress.addEventListener( 'click', onProgressClicked, false );

Espen Hovlandsdal
committed
var visibilityChange;

Espen Hovlandsdal
committed
visibilityChange = 'visibilitychange';

Espen Hovlandsdal
committed
visibilityChange = 'msvisibilitychange';

Espen Hovlandsdal
committed
visibilityChange = 'webkitvisibilitychange';
}
if( visibilityChange ) {
document.addEventListener( visibilityChange, onPageVisibilityChange, false );
}

Espen Hovlandsdal
committed
}
// Listen to both touch and click events, in case the device
// supports both
var pointerEvents = [ 'touchstart', 'click' ];
// Only support touch for Android, fixes double navigations in
// stock browser
if( navigator.userAgent.match( /android/gi ) ) {
pointerEvents = [ 'touchstart' ];
}
pointerEvents.forEach( function( eventName ) {
dom.controlsLeft.forEach( function( el ) { el.addEventListener( eventName, onNavigateLeftClicked, false ); } );
dom.controlsRight.forEach( function( el ) { el.addEventListener( eventName, onNavigateRightClicked, false ); } );
dom.controlsUp.forEach( function( el ) { el.addEventListener( eventName, onNavigateUpClicked, false ); } );
dom.controlsDown.forEach( function( el ) { el.addEventListener( eventName, onNavigateDownClicked, false ); } );
dom.controlsPrev.forEach( function( el ) { el.addEventListener( eventName, onNavigatePrevClicked, false ); } );
dom.controlsNext.forEach( function( el ) { el.addEventListener( eventName, onNavigateNextClicked, false ); } );
} );

Hakim El Hattab
committed
/**
* Unbinds all event listeners.
*/
function removeEventListeners() {
eventsAreBound = false;
document.removeEventListener( 'keydown', onDocumentKeyDown, false );
document.removeEventListener( 'keypress', onDocumentKeyPress, false );
window.removeEventListener( 'hashchange', onWindowHashChange, false );
window.removeEventListener( 'resize', onWindowResize, false );
dom.wrapper.removeEventListener( 'touchstart', onTouchStart, false );
dom.wrapper.removeEventListener( 'touchmove', onTouchMove, false );
dom.wrapper.removeEventListener( 'touchend', onTouchEnd, false );
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 );
}
if ( config.progress && dom.progress ) {

Hakim El Hattab
committed
dom.progress.removeEventListener( 'click', onProgressClicked, false );
[ 'touchstart', 'click' ].forEach( function( eventName ) {
dom.controlsLeft.forEach( function( el ) { el.removeEventListener( eventName, onNavigateLeftClicked, false ); } );
dom.controlsRight.forEach( function( el ) { el.removeEventListener( eventName, onNavigateRightClicked, false ); } );
dom.controlsUp.forEach( function( el ) { el.removeEventListener( eventName, onNavigateUpClicked, false ); } );
dom.controlsDown.forEach( function( el ) { el.removeEventListener( eventName, onNavigateDownClicked, false ); } );
dom.controlsPrev.forEach( function( el ) { el.removeEventListener( eventName, onNavigatePrevClicked, false ); } );
dom.controlsNext.forEach( function( el ) { el.removeEventListener( eventName, onNavigateNextClicked, false ); } );
} );
* Extend object a with the properties of object b.
* If there's a conflict, object b takes precedence.
*/
function extend( a, b ) {
for( var i in b ) {
a[ i ] = b[ i ];
}

Hakim El Hattab
committed
/**
* Converts the target object to an array.
*/
function toArray( o ) {

Hakim El Hattab
committed
return Array.prototype.slice.call( o );
}
/**
* 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
* @param {Object} a point with x/y properties
* @param {Object} b point with x/y properties
*/
function distanceBetween( a, b ) {
var dx = a.x - b.x,
dy = a.y - b.y;
return Math.sqrt( dx*dx + dy*dy );
/**
* Applies a CSS transform to the target element.
*/
function transformElement( element, transform ) {
element.style.WebkitTransform = transform;
element.style.MozTransform = transform;
element.style.msTransform = transform;
element.style.OTransform = transform;
element.style.transform = transform;
}
/**
* 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 );
}
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
/**
* Measures the distance in pixels between point a and point b.
*
* @param {String} color The string representation of a color,
* the following formats are supported:
* - #000
* - #000000
* - rgb(0,0,0)
*/
function colorToRgb( color ) {
var hex3 = color.match( /^#([0-9a-f]{3})$/i );
if( hex3 && hex3[1] ) {
hex3 = hex3[1];
return {
r: parseInt( hex3.charAt( 0 ), 16 ) * 0x11,
g: parseInt( hex3.charAt( 1 ), 16 ) * 0x11,
b: parseInt( hex3.charAt( 2 ), 16 ) * 0x11
};
}
var hex6 = color.match( /^#([0-9a-f]{6})$/i );
if( hex6 && hex6[1] ) {
hex6 = hex6[1];
return {
r: parseInt( hex6.substr( 0, 2 ), 16 ),
g: parseInt( hex6.substr( 2, 2 ), 16 ),
b: parseInt( hex6.substr( 4, 2 ), 16 )
};
}
var rgb = color.match( /^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i );
if( rgb ) {
return {
r: parseInt( rgb[1], 10 ),
g: parseInt( rgb[2], 10 ),
b: parseInt( rgb[3], 10 )
};
}
var rgba = color.match( /^rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\,\s*([\d]+|[\d]*.[\d]+)\s*\)$/i );
if( rgba ) {
return {
r: parseInt( rgba[1], 10 ),
g: parseInt( rgba[2], 10 ),
b: parseInt( rgba[3], 10 ),
a: parseFloat( rgba[4] )
};
}
return null;
}
/**
* Calculates brightness on a scale of 0-255.
*
* @param color See colorStringToRgb for supported formats.
*/
function colorBrightness( color ) {
if( typeof color === 'string' ) color = colorToRgb( color );

Hakim El Hattab
committed
if( color ) {
return ( color.r * 299 + color.g * 587 + color.b * 114 ) / 1000;
}
return null;
/**
* Retrieves the height of the given element by looking
* at the position and height of its immediate children.
*/
function getAbsoluteHeight( element ) {
var height = 0;
if( element ) {
var absoluteChildren = 0;
toArray( element.childNodes ).forEach( function( child ) {
if( typeof child.offsetTop === 'number' && child.style ) {
// Count # of abs children
if( window.getComputedStyle( child ).position === 'absolute' ) {
absoluteChildren += 1;
}
height = Math.max( height, child.offsetTop + child.offsetHeight );
}
} );
// If there are no absolute children, use offsetHeight
if( absoluteChildren === 0 ) {
height = element.offsetHeight;
}
}
return height;
}

Hakim El Hattab
committed
/**
* Returns the remaining height within the parent of the

Hakim El Hattab
committed
*
* remaining height = [ configured parent height ] - [ current parent height ]

Hakim El Hattab
committed
*/
function getRemainingHeight( element, height ) {

Hakim El Hattab
committed
height = height || 0;

Hakim El Hattab
committed
if( element ) {
var newHeight, oldHeight = element.style.height;

Hakim El Hattab
committed
// 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;

Hakim El Hattab
committed
// Restore the old height, just in case
element.style.height = oldHeight + 'px';

Hakim El Hattab
committed
}
return height;
}
/**
* Checks if this instance is being used to print a PDF.
*/
function isPrintingPDF() {
return ( /print-pdf/gi ).test( window.location.search );
}
/**
* Hides the address bar if we're on a mobile device.
*/
function hideAddressBar() {
if( config.hideAddressBar && isMobileDevice ) {
// Events that should trigger the address bar to hide
window.addEventListener( 'load', removeAddressBar, false );
window.addEventListener( 'orientationchange', removeAddressBar, false );
}
}
* Causes the address bar to hide on mobile devices,
* more vertical space ftw.
*/
function removeAddressBar() {
setTimeout( function() {
window.scrollTo( 0, 1 );

hakimel
committed
}, 10 );
* Dispatches an event of the specified type from the
* reveal DOM element.
*/
function dispatchEvent( type, args ) {
var event = document.createEvent( 'HTMLEvents', 1, 2 );
event.initEvent( type, true, true );
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() }), '*' );
}
/**
* Wrap all links in 3D goodness.
*/
function enableRollingLinks() {

Hakim El Hattab
committed
if( features.transforms3d && !( 'msPerspective' in document.body.style ) ) {

Hakim El Hattab
committed
var anchors = dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' a' );
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
for( var i = 0, len = anchors.length; i < len; i++ ) {
var anchor = anchors[i];
if( anchor.textContent && !anchor.querySelector( '*' ) && ( !anchor.className || !anchor.classList.contains( anchor, 'roll' ) ) ) {
var span = document.createElement('span');
span.setAttribute('data-title', anchor.text);
span.innerHTML = anchor.innerHTML;
anchor.classList.add( 'roll' );
anchor.innerHTML = '';
anchor.appendChild(span);
}
}
}
}
/**
* Unwrap all 3D links.
*/
function disableRollingLinks() {

Hakim El Hattab
committed
var anchors = dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' a.roll' );
for( var i = 0, len = anchors.length; i < len; i++ ) {
var anchor = anchors[i];
var span = anchor.querySelector( 'span' );
if( span ) {
anchor.classList.remove( 'roll' );
anchor.innerHTML = span.innerHTML;
}
}
}

Hakim El Hattab
committed
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
/**
* Bind preview frame links.
*/
function enablePreviewLinks( selector ) {
var anchors = toArray( document.querySelectorAll( selector ? selector : 'a' ) );
anchors.forEach( function( element ) {
if( /^(http|www)/gi.test( element.getAttribute( 'href' ) ) ) {
element.addEventListener( 'click', onPreviewLinkClicked, false );
}
} );
}
/**
* Unbind preview frame links.
*/
function disablePreviewLinks() {
var anchors = toArray( document.querySelectorAll( 'a' ) );
anchors.forEach( function( element ) {
if( /^(http|www)/gi.test( element.getAttribute( 'href' ) ) ) {
element.removeEventListener( 'click', onPreviewLinkClicked, false );
}
} );
}
/**
* Opens a preview window for the target URL.
*/
function showPreview( url ) {

Hakim El Hattab
committed

Hakim El Hattab
committed
dom.overlay = document.createElement( 'div' );
dom.overlay.classList.add( 'overlay' );
dom.overlay.classList.add( 'overlay-preview' );
dom.wrapper.appendChild( dom.overlay );

Hakim El Hattab
committed

Hakim El Hattab
committed
'<header>',
'<a class="close" href="#"><span class="icon"></span></a>',
'<a class="external" href="'+ url +'" target="_blank"><span class="icon"></span></a>',
'</header>',
'<div class="spinner"></div>',
'<div class="viewport">',
'<iframe src="'+ url +'"></iframe>',
'</div>'
].join('');
dom.overlay.querySelector( 'iframe' ).addEventListener( 'load', function( event ) {
dom.overlay.classList.add( 'loaded' );

Hakim El Hattab
committed
}, false );
dom.overlay.querySelector( '.close' ).addEventListener( 'click', function( event ) {
closeOverlay();

Hakim El Hattab
committed
event.preventDefault();
}, false );
dom.overlay.querySelector( '.external' ).addEventListener( 'click', function( event ) {
closeOverlay();

Hakim El Hattab
committed
}, false );
setTimeout( function() {
dom.overlay.classList.add( 'visible' );

Hakim El Hattab
committed
}, 1 );
}
/**
* Opens a overlay window with help material.

Hakim El Hattab
committed
*/

Hakim El Hattab
committed

Hakim El Hattab
committed
dom.overlay = document.createElement( 'div' );
dom.overlay.classList.add( 'overlay' );
dom.overlay.classList.add( 'overlay-help' );
dom.wrapper.appendChild( dom.overlay );
var html = '<p class="title">Keyboard Shortcuts</p><br/>';
html += '<table><th>KEY</th><th>ACTION</th>';
for( var key in keyboardShortcuts ) {
html += '<tr><td>' + key + '</td><td>' + keyboardShortcuts[ key ] + '</td></tr>';
}
dom.overlay.innerHTML = [
'<header>',
'<a class="close" href="#"><span class="icon"></span></a>',
'</header>',
'<div class="viewport">',
'<div class="viewport-inner">'+ html +'</div>',
'</div>'
].join('');
dom.overlay.querySelector( '.close' ).addEventListener( 'click', function( event ) {
closeOverlay();
event.preventDefault();
}, false );
setTimeout( function() {
dom.overlay.classList.add( 'visible' );
}, 1 );
}
/**
* Closes any currently open overlay.
*/
function closeOverlay() {
if( dom.overlay ) {
dom.overlay.parentNode.removeChild( dom.overlay );
dom.overlay = null;
}
}
/**
* Applies JavaScript-controlled layout rules to the
* presentation.
*/
function layout() {
if( dom.wrapper && !isPrintingPDF() ) {

hakimel
committed
var size = getComputedSlideSize();

hakimel
committed
var slidePadding = 20; // TODO Dig this out of DOM

hakimel
committed
// Layout the contents of the slides
layoutSlideContents( config.width, config.height, slidePadding );
dom.slides.style.width = size.width + 'px';
dom.slides.style.height = size.height + 'px';

hakimel
committed

Hakim El Hattab
committed
// Determine scale of content to fit within available space
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 );
// Don't apply any scaling styles if scale is 1
if( scale === 1 ) {
dom.slides.style.zoom = '';
dom.slides.style.left = '';
dom.slides.style.top = '';
dom.slides.style.bottom = '';
dom.slides.style.right = '';
transformElement( dom.slides, '' );
}
else {
// 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 {
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 +')' );
}

Hakim El Hattab
committed
// Select all slides, vertical and horizontal

Hakim El Hattab
committed
var slides = toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) );
for( var i = 0, len = slides.length; i < len; i++ ) {
var slide = slides[ i ];
// Don't bother updating invisible slides
if( slide.style.display === 'none' ) {
continue;
}
if( config.center || slide.classList.contains( 'center' ) ) {
// Vertical stacks are not centred since their section
// children will be
if( slide.classList.contains( 'stack' ) ) {
slide.style.top = 0;
}
else {
slide.style.top = Math.max( ( ( size.height - getAbsoluteHeight( slide ) ) / 2 ) - slidePadding, 0 ) + 'px';
else {
slide.style.top = '';
}
updateParallax();
/**
* Applies layout logic to the contents of all slides in
* the presentation.
*/
function layoutSlideContents( width, height, padding ) {
// Handle sizing of elements with the 'stretch' class
toArray( dom.slides.querySelectorAll( 'section > .stretch' ) ).forEach( function( element ) {
// Determine how much vertical space we can use
var remainingHeight = getRemainingHeight( element, height );
// Consider the aspect ratio of media elements
if( /(img|video)/gi.test( element.nodeName ) ) {
var nw = element.naturalWidth || element.videoWidth,
nh = element.naturalHeight || element.videoHeight;
var es = Math.min( width / nw, remainingHeight / nh );
element.style.width = ( nw * es ) + 'px';
element.style.height = ( nh * es ) + 'px';
}
else {
element.style.width = width + 'px';
element.style.height = remainingHeight + 'px';
}
} );
}
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
/**
* 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;
}

Hakim El Hattab
committed
/**
* Stores the vertical index of a stack so that the same
* vertical slide can be selected when navigating to and

Hakim El Hattab
committed
* from the stack.

Hakim El Hattab
committed
* @param {HTMLElement} stack The vertical stack element
* @param {int} v Index to memorize
*/
function setPreviousVerticalIndex( stack, v ) {
if( typeof stack === 'object' && typeof stack.setAttribute === 'function' ) {

Hakim El Hattab
committed
stack.setAttribute( 'data-previous-indexv', v || 0 );
}

Hakim El Hattab
committed
}
/**
* Retrieves the vertical index which was stored using

Hakim El Hattab
committed
* #setPreviousVerticalIndex() or 0 if no previous index
* exists.
*
* @param {HTMLElement} stack The vertical stack element
*/
function getPreviousVerticalIndex( stack ) {
if( typeof stack === 'object' && typeof stack.setAttribute === 'function' && stack.classList.contains( 'stack' ) ) {
// Prefer manually defined start-indexv
var attributeName = stack.hasAttribute( 'data-start-indexv' ) ? 'data-start-indexv' : 'data-previous-indexv';
return parseInt( stack.getAttribute( attributeName ) || 0, 10 );

Hakim El Hattab
committed
}
return 0;

Hakim El Hattab
committed
}
* Displays the overview of slides (quick nav) by
* scaling down and arranging all slide elements.
*
* Experimental feature, might be dropped if perf
* can't be improved.
*/
function activateOverview() {
// Only proceed if enabled in config
if( config.overview ) {
// Don't auto-slide while in overview mode
cancelAutoSlide();
var wasActive = dom.wrapper.classList.contains( 'overview' );
// Vary the depth of the overview based on screen size
var depth = window.innerWidth < 400 ? 1000 : 2500;
dom.wrapper.classList.add( 'overview' );
dom.wrapper.classList.remove( 'overview-deactivating' );

Hakim El Hattab
committed
var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
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 );
// Apply CSS transform
transformElement( hslide, 'translateZ(-'+ depth +'px) translate(' + ( ( i - indexh ) * hoffset ) + '%, 0%)' );
if( hslide.classList.contains( 'stack' ) ) {
var verticalSlides = hslide.querySelectorAll( 'section' );
for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) {
var verticalIndex = i === indexh ? indexv : getPreviousVerticalIndex( hslide );
var vslide = verticalSlides[j];
vslide.setAttribute( 'data-index-h', i );
vslide.setAttribute( 'data-index-v', j );
// Apply CSS transform
transformElement( vslide, 'translate(0%, ' + ( ( j - verticalIndex ) * 105 ) + '%)' );

Hakim El Hattab
committed
// Navigate to this slide on click
vslide.addEventListener( 'click', onOverviewSlideClicked, true );

Hakim El Hattab
committed
}

Hakim El Hattab
committed
// Navigate to this slide on click
hslide.addEventListener( 'click', onOverviewSlideClicked, true );
updateSlidesVisibility();
layout();
if( !wasActive ) {
// Notify observers of the overview showing
dispatchEvent( 'overviewshown', {
'indexh': indexh,
'indexv': indexv,
'currentSlide': currentSlide
} );
}

Hakim El Hattab
committed
/**
* Exits the slide overview and enters the currently
* active slide.
*/
function deactivateOverview() {
// Only proceed if enabled in config
if( config.overview ) {
dom.wrapper.classList.remove( 'overview' );
// Temporarily add a class so that transitions can do different things
// depending on whether they are exiting/entering overview, or just
// moving from slide to slide
dom.wrapper.classList.add( 'overview-deactivating' );
setTimeout( function () {
dom.wrapper.classList.remove( 'overview-deactivating' );
}, 1 );
// Select all slides

Hakim El Hattab
committed
toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
// Resets all transforms to use the external styles
slide.removeEventListener( 'click', onOverviewSlideClicked, true );
} );

Hakim El Hattab
committed
slide( indexh, indexv );
cueAutoSlide();
// Notify observers of the overview hiding
dispatchEvent( 'overviewhidden', {
'indexh': indexh,
'indexv': indexv,
'currentSlide': currentSlide
} );

Hakim El Hattab
committed
/**
* Toggles the slide overview mode on and off.
*
* @param {Boolean} override Optional flag which overrides the
* toggle logic and forcibly sets the desired state. True means

Hakim El Hattab
committed
* overview is open, false means it's closed.
*/
function toggleOverview( override ) {

Hakim El Hattab
committed
if( typeof override === 'boolean' ) {
override ? activateOverview() : deactivateOverview();
}
else {
isOverview() ? deactivateOverview() : activateOverview();

Hakim El Hattab
committed
}

Hakim El Hattab
committed
}
/**
* Checks if the overview is currently active.
* @return {Boolean} true if the overview is active,
* false otherwise
*/
function isOverview() {
return dom.wrapper.classList.contains( 'overview' );
/**
* Checks if the current or specified slide is vertical
* (nested within another slide).
*
* @param {HTMLElement} slide [optional] The slide to check
* orientation of
*/
function isVerticalSlide( slide ) {
// Prefer slide argument, otherwise use current slide
slide = slide ? slide : currentSlide;
return slide && slide.parentNode && !!slide.parentNode.nodeName.match( /section/i );
/**
* Handling the fullscreen functionality via the fullscreen API
*
* @see http://fullscreen.spec.whatwg.org/
* @see https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode
*/
function enterFullscreen() {
// Check which implementation is available
var requestMethod = element.requestFullScreen ||

Marc van Gend
committed
element.webkitRequestFullscreen ||
element.webkitRequestFullScreen ||
element.mozRequestFullScreen ||
element.msRequestFullscreen;
if( requestMethod ) {
requestMethod.apply( element );
}

Hakim El Hattab
committed
/**
* Enters the paused mode which fades everything on screen to

Hakim El Hattab
committed
* black.
*/
function pause() {
if( config.pause ) {
var wasPaused = dom.wrapper.classList.contains( 'paused' );
cancelAutoSlide();
dom.wrapper.classList.add( 'paused' );
if( wasPaused === false ) {
dispatchEvent( 'paused' );
}

Hakim El Hattab
committed
}
/**
* Exits from the paused mode.
*/
function resume() {
var wasPaused = dom.wrapper.classList.contains( 'paused' );
dom.wrapper.classList.remove( 'paused' );
if( wasPaused ) {
dispatchEvent( 'resumed' );
}

Hakim El Hattab
committed
}
/**
* Toggles the paused mode on and off.
*/
function togglePause( override ) {
if( typeof override === 'boolean' ) {
override ? pause() : resume();

Hakim El Hattab
committed
}
else {
isPaused() ? resume() : pause();

Hakim El Hattab
committed
}

Hakim El Hattab
committed
}
/**
* Checks if we are currently in the paused mode.
*/
function isPaused() {

Hakim El Hattab
committed
return dom.wrapper.classList.contains( 'paused' );

Hakim El Hattab
committed
}
/**
* 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.
*/

Hakim El Hattab
committed
return !!( autoSlide && !autoSlidePaused );
* Steps from the current point in the presentation to the
* slide which matches the specified horizontal and vertical
* indices.

Hakim El Hattab
committed
*
* @param {int} h Horizontal index of the target slide
* @param {int} v Vertical index of the target slide
* @param {int} f Optional index of a fragment within the
* target slide to activate
* @param {int} o Optional origin for use in multimaster environments

Hakim El Hattab
committed
// Remember where we were at before
previousSlide = currentSlide;
// Query all horizontal slides in the deck

Hakim El Hattab
committed
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

Hakim El Hattab
committed
if( v === undefined ) {
v = getPreviousVerticalIndex( horizontalSlides[ h ] );
// If we were on a vertical stack, remember what vertical index
// it was on so we can resume at the same position when returning
if( previousSlide && previousSlide.parentNode && previousSlide.parentNode.classList.contains( 'stack' ) ) {

Hakim El Hattab
committed
setPreviousVerticalIndex( previousSlide.parentNode, indexv );
// Remember the state before this slide
var stateBefore = state.concat();
// Reset the state array
state.length = 0;

Hakim El Hattab
committed
var indexhBefore = indexh || 0,
indexvBefore = indexv || 0;
// Activate and transition to the new slide
indexh = updateSlides( HORIZONTAL_SLIDES_SELECTOR, h === undefined ? indexh : h );
indexv = updateSlides( VERTICAL_SLIDES_SELECTOR, v === undefined ? indexv : v );

Hakim El Hattab
committed
// Update the visibility of slides now that the indices have changed
updateSlidesVisibility();

Hakim El Hattab
committed
// Apply the new state
stateLoop: for( var i = 0, len = state.length; i < len; i++ ) {
// Check if this state existed on the previous slide. If it
// did, we will avoid adding it repeatedly
for( var j = 0; j < stateBefore.length; j++ ) {
if( stateBefore[j] === state[i] ) {
stateBefore.splice( j, 1 );
continue stateLoop;
}
}

Hakim El Hattab
committed
document.documentElement.classList.add( state[i] );

Hakim El Hattab
committed
// Dispatch custom event matching the state's name
dispatchEvent( state[i] );
// Clean up the remains of the previous state
while( stateBefore.length ) {
document.documentElement.classList.remove( stateBefore.pop() );

Hakim El Hattab
committed
}
// If the overview is active, re-activate it to update positions
if( isOverview() ) {
activateOverview();
}

Hakim El Hattab
committed
// Find the current horizontal slide and any possible vertical slides
// within it
var currentHorizontalSlide = horizontalSlides[ indexh ],
currentVerticalSlides = currentHorizontalSlide.querySelectorAll( 'section' );

Hakim El Hattab
committed

Hakim El Hattab
committed
// Store references to the previous and current slides
currentSlide = currentVerticalSlides[ indexv ] || currentHorizontalSlide;

Hakim El Hattab
committed
// Show fragment, if specified
if( typeof f !== 'undefined' ) {

Hakim El Hattab
committed
// Dispatch an event if the slide changed
var slideChanged = ( indexh !== indexhBefore || indexv !== indexvBefore );
if( slideChanged ) {
dispatchEvent( 'slidechanged', {

Hakim El Hattab
committed
'indexv': indexv,

Hakim El Hattab
committed
'previousSlide': previousSlide,
'currentSlide': currentSlide,
'origin': o
} );
}

Hakim El Hattab
committed
else {
// Ensure that the previous slide is never the same as the current
previousSlide = null;
}

Hakim El Hattab
committed
// Solves an edge case where the previous slide maintains the
// 'present' class when navigating between adjacent vertical

Hakim El Hattab
committed
// stacks
if( previousSlide ) {
previousSlide.classList.remove( 'present' );

Nawaz
committed
previousSlide.setAttribute( 'aria-hidden', 'true' );
// Reset all slides upon navigate to home
// Issue: #285

Hakim El Hattab
committed
if ( dom.wrapper.querySelector( HOME_SLIDE_SELECTOR ).classList.contains( 'present' ) ) {
// Launch async task
setTimeout( function () {

Hakim El Hattab
committed
var slides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.stack') ), i;
for( i in slides ) {
if( slides[i] ) {
// Reset stack
setPreviousVerticalIndex( slides[i], 0 );
}
}
}, 0 );
}

Hakim El Hattab
committed
}
// Handle embedded content
if( slideChanged || !previousSlide ) {
stopEmbeddedContent( previousSlide );
startEmbeddedContent( currentSlide );
}
// Announce the current slide contents, for screen readers
dom.statusDiv.textContent = currentSlide.textContent;
updateControls();
updateProgress();
updateBackground();
updateSlideNumber();
// Update the URL hash
writeURL();
/**
* Syncs the presentation with the current DOM. Useful
* when new slides or control elements are added or when
* the configuration has changed.
*/
function sync() {
// Subscribe to input
removeEventListeners();
addEventListeners();
// Force a layout to make sure the current config is accounted for
layout();
// Reflect the current autoSlide value
autoSlide = config.autoSlide;
// Start auto-sliding if it's enabled
cueAutoSlide();
// Re-create the slide backgrounds
createBackgrounds();
// Write the current hash to the URL
writeURL();
sortAllFragments();
updateControls();
updateProgress();
updateBackground( true );
updateSlideNumber();
updateSlidesVisibility();
formatEmbeddedContent();
* Resets all vertical slides so that only the first
* is visible.
function resetVerticalSlides() {

Hakim El Hattab
committed
var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
horizontalSlides.forEach( function( horizontalSlide ) {
var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
verticalSlides.forEach( function( verticalSlide, y ) {
if( y > 0 ) {
verticalSlide.classList.remove( 'present' );
verticalSlide.classList.remove( 'past' );
verticalSlide.classList.add( 'future' );

Nawaz
committed
verticalSlide.setAttribute( 'aria-hidden', 'true' );
}
} );
} );
}
/**
* Sorts and formats all of fragments in the
* presentation.
*/
function sortAllFragments() {

Hakim El Hattab
committed
var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
horizontalSlides.forEach( function( horizontalSlide ) {
var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
verticalSlides.forEach( function( verticalSlide, y ) {
sortFragments( verticalSlide.querySelectorAll( '.fragment' ) );
} );
if( verticalSlides.length === 0 ) sortFragments( horizontalSlide.querySelectorAll( '.fragment' ) );
} );
}

Hakim El Hattab
committed
/**
* Updates one dimension of slides by showing the slide
* with the specified index.

Hakim El Hattab
committed
* @param {String} selector A CSS selector that will fetch
* the group of slides we are working with
* @param {Number} index The index of the slide that should be
* shown

Hakim El Hattab
committed
* @return {Number} The index of the slide that is now shown,
* might differ from the passed in index if it was out of

Hakim El Hattab
committed
* bounds.
*/
function updateSlides( selector, index ) {

Hakim El Hattab
committed
// Select all slides and convert the NodeList result to
// an array

Hakim El Hattab
committed
var slides = toArray( dom.wrapper.querySelectorAll( selector ) ),

Hakim El Hattab
committed
slidesLength = slides.length;
var printMode = isPrintingPDF();

Hakim El Hattab
committed
if( slidesLength ) {
// Should the index loop?
if( config.loop ) {
index %= slidesLength;
if( index < 0 ) {
index = slidesLength + index;
}
}

Hakim El Hattab
committed
// Enforce max and minimum index bounds
index = Math.max( Math.min( index, slidesLength - 1 ), 0 );

Hakim El Hattab
committed
for( var i = 0; i < slidesLength; i++ ) {
var element = slides[i];
var reverse = config.rtl && !isVerticalSlide( element );
element.classList.remove( 'past' );
element.classList.remove( 'present' );
element.classList.remove( 'future' );

Hakim El Hattab
committed
// http://www.w3.org/html/wg/drafts/html/master/editing.html#the-hidden-attribute
element.setAttribute( 'hidden', '' );

Nawaz
committed
element.setAttribute( 'aria-hidden', 'true' );
// 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;
}

Hakim El Hattab
committed
if( i < index ) {
// Any element previous to index is given the 'past' class
element.classList.add( reverse ? 'future' : 'past' );
Loading
Loading full blame...