Newer
Older
* http://lab.hakim.se/reveal-js
* MIT licensed
* Copyright (C) 2013 Hakim El Hattab, http://hakim.se
var Reveal = (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-child',
// 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,
// Push each slide change to the browser history

Hakim El Hattab
committed
// Enable keyboard shortcuts for navigation
keyboard: true,
// Enable the slide overview mode
overview: true,

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,
// 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,
// Enable slide navigation via mouse wheel

Hakim El Hattab
committed
mouseWheel: false,

Hakim El Hattab
committed
// Apply a 3D roll to links on hover

Hakim El Hattab
committed

Hakim El Hattab
committed
// Opens links in an iframe preview overlay
previewLinks: false,

Hakim El Hattab
committed
// Transition style
transition: 'default', // default/cube/page/concave/zoom/linear/fade/none
// Transition speed
transitionSpeed: 'default', // default/fast/slow
// Transition style for full page slide backgrounds
backgroundTransition: 'default', // default/linear
// Script dependencies to load
dependencies: []
// The current auto-slide duration
autoSlide = 0,
// The horizontal and vertical index of the currently active slide

Hakim El Hattab
committed
indexh = 0,
indexv = 0,
// The previous and current slide HTML elements
previousSlide,
currentSlide,
// 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,
dom = {},
// Detect support for CSS 3D transforms

hakimel
committed
supports3DTransforms = 'WebkitPerspective' in document.body.style ||
'MozPerspective' in document.body.style ||
'msPerspective' in document.body.style ||
'OPerspective' in document.body.style ||
'perspective' in document.body.style,
// Detect support for CSS 2D transforms
supports2DTransforms = 'WebkitTransform' in document.body.style ||
'MozTransform' in document.body.style ||
'msTransform' in document.body.style ||
'OTransform' in document.body.style ||
'transform' in document.body.style,

Hakim El Hattab
committed
mouseWheelTimeout = 0,

Hakim El Hattab
committed
// An interval used to automatically move on to the next slide
autoSlideTimeout = 0,

Hakim El Hattab
committed
// Delays updates to the URL due to a Chrome thumbnailer bug
activateOverviewTimeout = 0,
// Flags if the interaction event listeners are bound
eventsAreBound = false,
// Holds information about the currently ongoing touch input
touch = {
startX: 0,
startY: 0,
startSpan: 0,
startCount: 0,
handled: false,
threshold: 80
* Starts up the presentation if the client is capable.
function initialize( options ) {
if( !supports2DTransforms && !supports3DTransforms ) {
document.body.setAttribute( 'class', 'no-transforms' );
// If the browser doesn't support core features we won't be
// using JavaScript to control the presentation
// Force a layout when the whole page, incl fonts, has loaded
window.addEventListener( 'load', layout, false );
// 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
}
/**
* 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() {

Hakim El Hattab
committed
// Cache references to key DOM elements
dom.theme = document.querySelector( '#theme' );
dom.wrapper = document.querySelector( '.reveal' );
dom.slides = document.querySelector( '.reveal .slides' );

Hakim El Hattab
committed
// Background element
if( !document.querySelector( '.reveal .backgrounds' ) ) {
dom.background = document.createElement( 'div' );
dom.background.classList.add( 'backgrounds' );
dom.wrapper.appendChild( dom.background );
}

Hakim El Hattab
committed
// Progress bar
if( !dom.wrapper.querySelector( '.progress' ) ) {

Hakim El Hattab
committed
var progressElement = document.createElement( 'div' );
progressElement.classList.add( 'progress' );
progressElement.innerHTML = '<span></span>';
dom.wrapper.appendChild( progressElement );
}
// Arrow controls
if( !dom.wrapper.querySelector( '.controls' ) ) {

Hakim El Hattab
committed
var controlsElement = document.createElement( 'aside' );
controlsElement.classList.add( 'controls' );

Hakim El Hattab
committed
controlsElement.innerHTML = '<div class="navigate-left"></div>' +
'<div class="navigate-right"></div>' +
'<div class="navigate-up"></div>' +
'<div class="navigate-down"></div>';

Hakim El Hattab
committed
dom.wrapper.appendChild( controlsElement );
}
// State background element [DEPRECATED]

Hakim El Hattab
committed
if( !dom.wrapper.querySelector( '.state-background' ) ) {
var stateBackgroundElement = document.createElement( 'div' );
stateBackgroundElement.classList.add( 'state-background' );
dom.wrapper.appendChild( stateBackgroundElement );

Hakim El Hattab
committed
}
// Overlay graphic which is displayed during the paused mode
if( !dom.wrapper.querySelector( '.pause-overlay' ) ) {
var pausedElement = document.createElement( 'div' );
pausedElement.classList.add( 'pause-overlay' );
dom.wrapper.appendChild( pausedElement );
}
// Cache references to elements
dom.progress = document.querySelector( '.reveal .progress' );
dom.progressbar = document.querySelector( '.reveal .progress span' );
dom.controls = document.querySelector( '.reveal .controls' );

Hakim El Hattab
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' ) );

Hakim El Hattab
committed
}
/**
* 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() {
if( isPrintingPDF() ) {
document.body.classList.add( 'print-pdf' );
}
// 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' ),
backgroundColor: slide.getAttribute( 'data-background-color' ),
backgroundRepeat: slide.getAttribute( 'data-background-repeat' ),
backgroundPosition: slide.getAttribute( 'data-background-position' )
};
var element = document.createElement( 'div' );
element.className = 'slide-background';
if( data.background ) {
// Auto-wrap image urls in url(...)
if( /\.(png|jpg|jpeg|gif|bmp)$/gi.test( data.background ) ) {
element.style.backgroundImage = 'url('+ data.background +')';
}
else {
element.style.background = data.background;
}
}
// 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;
container.appendChild( element );
return element;
}
// Iterate over all horizontal slides
toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) {
var backgroundStack;
if( isPrintingPDF() ) {
backgroundStack = _createBackground( slideh, slideh );
}
else {
backgroundStack = _createBackground( slideh, dom.background );
}
// Iterate over all vertical slides
toArray( slideh.querySelectorAll( 'section' ) ).forEach( function( slidev ) {
if( isPrintingPDF() ) {
_createBackground( slidev, slidev );
}
else {
_createBackground( slidev, backgroundStack );
}
} );
} );
}

Hakim El Hattab
committed
/**
* Hides the address bar if we're on a mobile device.
*/
function hideAddressBar() {
if( /iphone|ipod|android/gi.test( navigator.userAgent ) && !/crios/gi.test( navigator.userAgent ) ) {
// Events that should trigger the address bar to hide
window.addEventListener( 'load', removeAddressBar, false );
window.addEventListener( 'orientationchange', removeAddressBar, false );
}
* Loads the dependencies of reveal.js. Dependencies are
* defined via the configuration option 'dependencies'
* and will be loaded prior to starting/binding reveal.js.
* Some dependencies may have an 'async' flag, if so they
* will load after reveal.js has been started up.
*/
function load() {
var scripts = [],
scriptsAsync = [];
for( var i = 0, len = config.dependencies.length; i < len; i++ ) {
var s = config.dependencies[i];
// Load if there's no condition or the condition is truthy
if( !s.condition || s.condition() ) {
if( s.async ) {
scriptsAsync.push( s.src );
}
else {
scripts.push( s.src );
}
// Extension may contain callback functions
if( typeof s.callback === 'function' ) {
head.ready( s.src.match( /([\w\d_\-]*)\.?js$|[^\\\/]*$/i )[0], s.callback );
}
}
}
// Called once synchronous scripts finish loading
function proceed() {

Hakim El Hattab
committed
if( scriptsAsync.length ) {
// Load asynchronous scripts
head.js.apply( null, scriptsAsync );
}
start();
}
if( scripts.length ) {
head.ready( proceed );
// Load synchronous scripts
head.js.apply( null, scripts );
}
else {
proceed();
}
}
/**
* Starts up reveal.js by binding input events and navigating
* to the current URL deeplink if there is one.
*/
function start() {
// Make sure we've got all the DOM elements we need
setupDOM();
// Updates the presentation to match the current configuration values
configure();
// Read the initial hash
readURL();
// Notify listeners that the presentation is ready but use a 1ms
// timeout to ensure it's not fired synchronously after #initialize()
setTimeout( function() {
dispatchEvent( 'ready', {
'indexh': indexh,
'indexv': indexv,
'currentSlide': currentSlide
} );
}, 1 );
}
/**
* Applies the configuration settings from the config
* object. May be called multiple times.
function configure( options ) {
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
if( supports3DTransforms === 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 );

Ben Houston
committed
dom.controls.style.display = ( config.controls && dom.controls ) ? 'block' : 'none';

Ben Houston
committed

Ben Houston
committed
dom.progress.style.display = ( config.progress && dom.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' );
}
document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
document.addEventListener( 'mousewheel', onDocumentMouseScroll, false );
else {
document.removeEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
document.removeEventListener( 'mousewheel', onDocumentMouseScroll, false );
}

Hakim El Hattab
committed
// Rolling 3D links
if( config.rollingLinks ) {

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

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

Hakim El Hattab
committed
// Load the theme in the config, if it's not already loaded
if( config.theme && dom.theme ) {
var themeURL = dom.theme.getAttribute( 'href' );
var themeFinder = /[^\/]*?(?=\.css)/;
var themeName = themeURL.match(themeFinder)[0];
if( config.theme !== themeName ) {
themeURL = themeURL.replace(themeFinder, config.theme);
dom.theme.setAttribute( 'href', themeURL );
}
}

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
if( window.navigator.msPointerEnabled ) {
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 );
}
if ( config.progress && dom.progress ) {

Hakim El Hattab
committed
dom.progress.addEventListener( 'click', onProgressClicked, false );
[ 'touchstart', 'click' ].forEach( function( eventName ) {
dom.controlsLeft.forEach( function( el ) { el.addEventListener( eventName, onNavigateLeftClicked, false ); } );
dom.controlsRight.forEach( function( el ) { el.addEventListener( eventName, onNavigateRightClicked, false ); } );
dom.controlsUp.forEach( function( el ) { el.addEventListener( eventName, onNavigateUpClicked, false ); } );
dom.controlsDown.forEach( function( el ) { el.addEventListener( eventName, onNavigateDownClicked, false ); } );
dom.controlsPrev.forEach( function( el ) { el.addEventListener( eventName, onNavigatePrevClicked, false ); } );
dom.controlsNext.forEach( function( el ) { el.addEventListener( eventName, onNavigateNextClicked, false ); } );
} );

Hakim El Hattab
committed
/**
* Unbinds all event listeners.
*/
function removeEventListeners() {
eventsAreBound = false;
document.removeEventListener( 'keydown', onDocumentKeyDown, 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.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 );
}
/**
* 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 );
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
/**
* 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( child.style.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;
}
/**
* Checks if this instance is being used to print a PDF.
*/
function isPrintingPDF() {
return ( /print-pdf/gi ).test( window.location.search );
}
* Causes the address bar to hide on mobile devices,
* more vertical space ftw.
*/
function removeAddressBar() {

hakimel
committed
if( window.orientation === 0 ) {
document.documentElement.style.overflow = 'scroll';
document.body.style.height = '120%';
}
else {
document.documentElement.style.overflow = '';
document.body.style.height = '100%';
}
setTimeout( function() {
window.scrollTo( 0, 1 );

hakimel
committed
}, 10 );
* Dispatches an event of the specified type from the
* reveal DOM element.
*/
function dispatchEvent( type, properties ) {
var event = document.createEvent( "HTMLEvents", 1, 2 );
event.initEvent( type, true, true );
extend( event, properties );
dom.wrapper.dispatchEvent( event );
/**
* Wrap all links in 3D goodness.
*/

Hakim El Hattab
committed
function enableRollingLinks() {

hakimel
committed
if( supports3DTransforms && !( 'msPerspective' in document.body.style ) ) {
var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a:not(.image)' );
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.
*/

Hakim El Hattab
committed
function disableRollingLinks() {
var anchors = document.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
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
/**
* 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 openPreview( url ) {
closePreview();
dom.preview = document.createElement( 'div' );
dom.preview.classList.add( 'preview-link-overlay' );
dom.wrapper.appendChild( dom.preview );
dom.preview.innerHTML = [
'<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.preview.querySelector( 'iframe' ).addEventListener( 'load', function( event ) {
dom.preview.classList.add( 'loaded' );
}, false );
dom.preview.querySelector( '.close' ).addEventListener( 'click', function( event ) {
closePreview();
event.preventDefault();
}, false );
dom.preview.querySelector( '.external' ).addEventListener( 'click', function( event ) {
closePreview();
}, false );
setTimeout( function() {
dom.preview.classList.add( 'visible' );
}, 1 );
}
/**
* Closes the iframe preview window.
*/
function closePreview() {
if( dom.preview ) {
dom.preview.setAttribute( 'src', '' );
dom.preview.parentNode.removeChild( dom.preview );
dom.preview = null;
}
}

Hakim El Hattab
committed
/**
* Return a sorted fragments list, ordered by an increasing
* "data-fragment-index" attribute.
*
* Fragments will be revealed in the order that they are returned by
* this function, so you can use the index attributes to control the

Hakim El Hattab
committed
* order of fragment appearance.
*
* To maintain a sensible default fragment order, fragments are presumed
* to be passed in document order. This function adds a "fragment-index"
* attribute to each node if such an attribute is not already present,
* and sets that attribute to an integer value which is the position of
* the fragment within the fragments list.
*/
function sortFragments( fragments ) {
var a = toArray( fragments );
a.forEach( function( el, idx ) {
if( !el.hasAttribute( 'data-fragment-index' ) ) {
el.setAttribute( 'data-fragment-index', idx );
}
} );
a.sort( function( l, r ) {
return l.getAttribute( 'data-fragment-index' ) - r.getAttribute( 'data-fragment-index');
} );

Hakim El Hattab
committed
}
/**
* Applies JavaScript-controlled layout rules to the
* presentation.
*/
function layout() {
if( dom.wrapper && !isPrintingPDF() ) {

hakimel
committed
// Available space to scale within
var availableWidth = dom.wrapper.offsetWidth,
availableHeight = dom.wrapper.offsetHeight;

hakimel
committed
availableWidth -= ( availableHeight * config.margin );
availableHeight -= ( availableHeight * config.margin );

hakimel
committed
// Dimensions of the content
var slideWidth = config.width,
slideHeight = config.height;

hakimel
committed
// Slide width may be a percentage of available width
if( typeof slideWidth === 'string' && /%$/.test( slideWidth ) ) {
slideWidth = parseInt( slideWidth, 10 ) / 100 * availableWidth;
}

Hakim El Hattab
committed
// Slide height may be a percentage of available height
if( typeof slideHeight === 'string' && /%$/.test( slideHeight ) ) {
slideHeight = parseInt( slideHeight, 10 ) / 100 * availableHeight;
}

Hakim El Hattab
committed
dom.slides.style.width = slideWidth + 'px';
dom.slides.style.height = slideHeight + 'px';

hakimel
committed
// Determine scale of content to fit within available space
scale = Math.min( availableWidth / slideWidth, availableHeight / slideHeight );
// Respect max/min scale settings
scale = Math.max( scale, config.minScale );
scale = Math.min( scale, config.maxScale );

hakimel
committed
// 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 ) ) {
dom.slides.style.zoom = scale;
}
// Apply scale transform as a fallback
else {
var transform = 'translate(-50%, -50%) scale('+ scale +') translate(50%, 50%)';
dom.slides.style.WebkitTransform = transform;
dom.slides.style.MozTransform = transform;
dom.slides.style.msTransform = transform;
dom.slides.style.OTransform = transform;
dom.slides.style.transform = transform;
}

Hakim El Hattab
committed
// Select all slides, vertical and horizontal
var slides = toArray( document.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 ) {
// Vertical stacks are not centred since their section
// children will be
if( slide.classList.contains( 'stack' ) ) {
slide.style.top = 0;
}
else {

Hakim El Hattab
committed
slide.style.top = Math.max( - ( getAbsoluteHeight( slide ) / 2 ) - 20, -slideHeight / 2 ) + 'px';
else {
slide.style.top = '';
}

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' );
dom.wrapper.classList.add( 'overview' );
dom.wrapper.classList.remove( 'exit-overview' );
clearTimeout( activateOverviewTimeout );
// Not the pretties solution, but need to let the overview
// class apply first so that slides are measured accurately
// before we can position them
activateOverviewTimeout = setTimeout( function(){
var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
for( var i = 0, len1 = horizontalSlides.length; i < len1; i++ ) {
var hslide = horizontalSlides[i],
hoffset = config.rtl ? -105 : 105,
htransform = 'translateZ(-2500px) translate(' + ( ( i - indexh ) * hoffset ) + '%, 0%)';
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
hslide.setAttribute( 'data-index-h', i );
hslide.style.display = 'block';
hslide.style.WebkitTransform = htransform;
hslide.style.MozTransform = htransform;
hslide.style.msTransform = htransform;
hslide.style.OTransform = htransform;
hslide.style.transform = htransform;
if( hslide.classList.contains( 'stack' ) ) {
var verticalSlides = hslide.querySelectorAll( 'section' );
for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) {
var verticalIndex = i === indexh ? indexv : getPreviousVerticalIndex( hslide );
var vslide = verticalSlides[j],
vtransform = 'translate(0%, ' + ( ( j - verticalIndex ) * 105 ) + '%)';
vslide.setAttribute( 'data-index-h', i );
vslide.setAttribute( 'data-index-v', j );
vslide.style.display = 'block';
vslide.style.WebkitTransform = vtransform;
vslide.style.MozTransform = vtransform;
vslide.style.msTransform = vtransform;
vslide.style.OTransform = vtransform;
vslide.style.transform = vtransform;
// Navigate to this slide on click
vslide.addEventListener( 'click', onOverviewSlideClicked, true );
}

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

Hakim El Hattab
committed
}
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 ) {
clearTimeout( activateOverviewTimeout );
dom.wrapper.classList.remove( 'overview' );
// Temporarily add a class so that transitions can do different things
// depending on whether they are exiting/entering overview, or just
// moving from slide to slide
dom.wrapper.classList.add( 'exit-overview' );
deactivateOverviewTimeout = setTimeout( function () {
dom.wrapper.classList.remove( 'exit-overview' );
}, 10);
// Select all slides
var slides = toArray( document.querySelectorAll( SLIDES_SELECTOR ) );
for( var i = 0, len = slides.length; i < len; i++ ) {
var element = slides[i];
element.style.display = '';
// Resets all transforms to use the external styles
element.style.WebkitTransform = '';
element.style.MozTransform = '';
element.style.msTransform = '';
element.style.OTransform = '';
element.style.transform = '';
element.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.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() {
var wasPaused = dom.wrapper.classList.contains( 'paused' );
cancelAutoSlide();

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

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

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
}
* 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
var horizontalSlides = document.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,
indexvBefore = indexv;
// 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
// 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();
}
// Update the URL hash after a delay since updating it mid-transition
// is likely to cause visual lag
writeURL( 1500 );

Hakim El Hattab
committed

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
var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment' ) );
toArray( fragments ).forEach( function( fragment, indexf ) {
if( indexf < f ) {
fragment.classList.add( 'visible' );
}
else {
fragment.classList.remove( 'visible' );
}
} );
}

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' );
// Reset all slides upon navigate to home
// Issue: #285
if ( document.querySelector( HOME_SLIDE_SELECTOR ).classList.contains( 'present' ) ) {
// Launch async task
setTimeout( function () {
var slides = toArray( document.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
stopEmbeddedContent( previousSlide );
startEmbeddedContent( currentSlide );
}
updateControls();
updateProgress();
updateBackground();
/**
* 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();
updateControls();
updateProgress();
updateBackground();

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
var slides = toArray( document.querySelectorAll( selector ) ),

Hakim El Hattab
committed
slidesLength = slides.length;

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];
// Optimization; hide all slides that are three or more steps

Hakim El Hattab
committed
// away from the present slide
if( isOverview() === false ) {

Hakim El Hattab
committed
// The distance loops so that it measures 1 between the first
// and last slides
var distance = Math.abs( ( index - i ) % ( slidesLength - 3 ) ) || 0;
element.style.display = distance > 3 ? 'none' : 'block';
}
var reverse = config.rtl && !isVerticalSlide( element );
element.classList.remove( 'past' );
element.classList.remove( 'present' );
element.classList.remove( 'future' );

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

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

Hakim El Hattab
committed
}
else if( i > index ) {
// Any element subsequent to index is given the 'future' class
element.classList.add( reverse ? 'past' : 'future' );

Hakim El Hattab
committed
}
// If this element contains vertical slides
if( element.querySelector( 'section' ) ) {
element.classList.add( 'stack' );

Hakim El Hattab
committed
}
}
// Mark the current slide as present
slides[index].classList.add( 'present' );
slides[index].removeAttribute( 'hidden' );

Hakim El Hattab
committed
// If this slide has a state associated with it, add it
// onto the current state of the deck
var slideState = slides[index].getAttribute( 'data-state' );
if( slideState ) {
state = state.concat( slideState.split( ' ' ) );
}
// If this slide has a data-autoslide attribtue associated use this as

Hakim El Hattab
committed
// autoSlide value otherwise use the global configured time
var slideAutoSlide = slides[index].getAttribute( 'data-autoslide' );
if( slideAutoSlide ) {
autoSlide = parseInt( slideAutoSlide, 10 );

Hakim El Hattab
committed
}
}
else {
// Since there are no slides we can't be anywhere beyond the

Hakim El Hattab
committed
// zeroth index
index = 0;
}

Hakim El Hattab
committed
return index;

Hakim El Hattab
committed
}
* Updates the progress bar to reflect the current slide.
*/
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;
// 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;
}
}
// 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';
}
}
/**
* Updates the state of all control/navigation arrows.
*/
function updateControls() {

Hakim El Hattab
committed
if ( config.controls && dom.controls ) {

Hakim El Hattab
committed
var routes = availableRoutes();
var fragments = availableFragments();

Hakim El Hattab
committed
// Remove the 'enabled' class from all directions

Hakim El Hattab
committed
dom.controlsLeft.concat( dom.controlsRight )
.concat( dom.controlsUp )
.concat( dom.controlsDown )
.concat( dom.controlsPrev )
.concat( dom.controlsNext ).forEach( function( node ) {

Hakim El Hattab
committed
node.classList.remove( 'enabled' );
node.classList.remove( 'fragmented' );

Hakim El Hattab
committed
} );
// Add the 'enabled' class to the available routes
if( routes.left ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'enabled' ); } );
if( routes.right ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'enabled' ); } );
if( routes.up ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'enabled' ); } );
if( routes.down ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'enabled' ); } );

Hakim El Hattab
committed
// Prev/next buttons
if( routes.left || routes.up ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'enabled' ); } );
if( routes.right || routes.down ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'enabled' ); } );

Hakim El Hattab
committed
// Highlight fragment directions
if( currentSlide ) {
// Always apply fragment decorator to prev/next buttons
if( fragments.prev ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
if( fragments.next ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
// Apply fragment decorators to directional buttons based on
// what slide axis they are in
if( isVerticalSlide( currentSlide ) ) {
if( fragments.prev ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
if( fragments.next ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
}
else {
if( fragments.prev ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
if( fragments.next ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
}
}
* Updates the background elements to reflect the current
* slide.
*/
function updateBackground() {
// Update the classes of all backgrounds to match the
// states of their slides (past/present/future)
toArray( dom.background.childNodes ).forEach( function( backgroundh, h ) {
// Reverse past/future classes when in RTL mode
var horizontalPast = config.rtl ? 'future' : 'past',
horizontalFuture = config.rtl ? 'past' : 'future';
backgroundh.className = 'slide-background ' + ( h < indexh ? horizontalPast : h > indexh ? horizontalFuture : 'present' );
toArray( backgroundh.childNodes ).forEach( function( backgroundv, v ) {
backgroundv.className = 'slide-background ' + ( v < indexv ? 'past' : v > indexv ? 'future' : 'present' );
} );
} );
// Allow the first background to apply without transition
setTimeout( function() {
dom.background.classList.remove( 'no-transition' );
}, 1 );
* 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 routes = {
left: indexh > 0 || config.loop,
right: indexh < horizontalSlides.length - 1 || config.loop,
up: indexv > 0,
down: indexv < verticalSlides.length - 1
};
// reverse horizontal controls for rtl
if( config.rtl ) {
var left = routes.left;
routes.left = routes.right;
routes.right = left;
}
return routes;
/**
* Returns an object describing the available fragment
* directions.
*
* @return {Object} two boolean properties: prev/next
*/
function availableFragments() {
if( currentSlide && config.fragments ) {
var fragments = currentSlide.querySelectorAll( '.fragment' );
var hiddenFragments = currentSlide.querySelectorAll( '.fragment:not(.visible)' );
return {
prev: fragments.length - hiddenFragments.length > 0,
next: !!hiddenFragments.length
};
}
else {
return { prev: false, next: false };
}
}
/**
* Start playback of any embedded content inside of
* the targeted slide.
*/
function startEmbeddedContent( slide ) {
if( slide ) {
// HTML5 media elements
toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
if( el.hasAttribute( 'data-autoplay' ) ) {
el.play();
}
} );
// YouTube embeds
toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
if( el.hasAttribute( 'data-autoplay' ) ) {
el.contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}', '*');
}
});
}
}
/**
* Stop playback of any embedded content inside of
* the targeted slide.
*/
function stopEmbeddedContent( slide ) {
if( slide ) {
// HTML5 media elements
toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
if( !el.hasAttribute( 'data-ignore' ) ) {
el.pause();
}
} );
// YouTube embeds
toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) {
el.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*');
}
});
}
}
/**
* Reads the current URL (hash) and navigates accordingly.
*/
function readURL() {
var hash = window.location.hash;
// Attempt to parse the hash as either an index or name
var bits = hash.slice( 2 ).split( '/' ),
name = hash.replace( /#|\//gi, '' );
// 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

Hakim El Hattab
committed
var element = document.querySelector( '#' + name );

Hakim El Hattab
committed
if( element ) {
// Find the position of the named slide and navigate to it

Hakim El Hattab
committed
var indices = Reveal.getIndices( element );
slide( indices.h, indices.v );
}
// If the slide doesn't exist, navigate to the current slide
else {

Hakim El Hattab
committed
slide( indexh, indexv );
}
}
else {
// Read the index components of the hash
var h = parseInt( bits[0], 10 ) || 0,
v = parseInt( bits[1], 10 ) || 0;

Tobi Reiss
committed

Hakim El Hattab
committed
slide( h, v );
/**
* Updates the page URL (hash) to reflect the current
* @param {Number} delay The time in ms to wait before
* writing the hash
function writeURL( delay ) {
if( config.history ) {
// Make sure there's never more than one timeout running
clearTimeout( writeURLTimeout );
// If a delay is specified, timeout this call
if( typeof delay === 'number' ) {
writeURLTimeout = setTimeout( writeURL, delay );

Hakim El Hattab
committed
}
else {
// If the current slide has an ID, use that as a named link
if( currentSlide && typeof currentSlide.getAttribute( 'id' ) === 'string' ) {
url = '/' + currentSlide.getAttribute( 'id' );
}
// Otherwise use the /h/v index
else {
if( indexh > 0 || indexv > 0 ) url += indexh;
if( indexv > 0 ) url += '/' + indexv;
}
window.location.hash = url;
}

Hakim El Hattab
committed
/**
* Retrieves the h/v location of the current, or specified,

Hakim El Hattab
committed
* slide.
*
* @param {HTMLElement} slide If specified, the returned
* index will be for this slide rather than the currently

Hakim El Hattab
committed
* active one

Hakim El Hattab
committed
* @return {Object} { h: <int>, v: <int>, f: <int> }

Hakim El Hattab
committed
*/
function getIndices( slide ) {

Hakim El Hattab
committed
// By default, return the current indices
var h = indexh,

Hakim El Hattab
committed
v = indexv,
f;

Hakim El Hattab
committed
// If a slide is specified, return the indices of that slide
if( slide ) {
var isVertical = isVerticalSlide( slide );

Hakim El Hattab
committed
var slideh = isVertical ? slide.parentNode : slide;
// Select all horizontal slides
var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );

Hakim El Hattab
committed
// Now that we know which the horizontal slide is, get its index
h = Math.max( horizontalSlides.indexOf( slideh ), 0 );
// If this is a vertical slide, grab the vertical index
if( isVertical ) {
v = Math.max( toArray( slide.parentNode.querySelectorAll( 'section' ) ).indexOf( slide ), 0 );

Hakim El Hattab
committed
}
}

Hakim El Hattab
committed
if( !slide && currentSlide ) {
var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
if( visibleFragments.length ) {
f = visibleFragments.length;
}
}
return { h: h, v: v, f: f };

Hakim El Hattab
committed
}
* @return {Boolean} true if there was a next fragment,
* false otherwise
*/
if( currentSlide && config.fragments ) {
var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment:not(.visible)' ) );

Hakim El Hattab
committed
if( fragments.length ) {
// Find the index of the next fragment
var index = fragments[0].getAttribute( 'data-fragment-index' );
// Find all fragments with the same index
fragments = currentSlide.querySelectorAll( '.fragment[data-fragment-index="'+ index +'"]' );
toArray( fragments ).forEach( function( element ) {
element.classList.add( 'visible' );
// Notify subscribers of the change
dispatchEvent( 'fragmentshown', { fragment: element } );
updateControls();
}
return false;
}
/**
* Navigate to the previous slide fragment.
* @return {Boolean} true if there was a previous fragment,
* false otherwise
*/
function previousFragment() {
if( currentSlide && config.fragments ) {
var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment.visible' ) );

Hakim El Hattab
committed
if( fragments.length ) {
// Find the index of the previous fragment
var index = fragments[ fragments.length - 1 ].getAttribute( 'data-fragment-index' );
// Find all fragments with the same index
fragments = currentSlide.querySelectorAll( '.fragment[data-fragment-index="'+ index +'"]' );
toArray( fragments ).forEach( function( f ) {
f.classList.remove( 'visible' );
// Notify subscribers of the change
dispatchEvent( 'fragmenthidden', { fragment: f } );
updateControls();
return true;
}
}
return false;

Hakim El Hattab
committed
/**
* Cues a new automated slide if enabled in the config.
*/

Hakim El Hattab
committed
function cueAutoSlide() {

Hakim El Hattab
committed
clearTimeout( autoSlideTimeout );
// Cue the next auto-slide if enabled
if( autoSlide && !isPaused() && !isOverview() ) {
autoSlideTimeout = setTimeout( navigateNext, autoSlide );

Hakim El Hattab
committed
}

Hakim El Hattab
committed
}
/**
* Cancels any ongoing request to auto-slide.
*/
function cancelAutoSlide() {
clearTimeout( autoSlideTimeout );
}
// Reverse for RTL
if( config.rtl ) {
if( ( isOverview() || nextFragment() === false ) && availableRoutes().left ) {
slide( indexh + 1 );
}
}
// Normal navigation
else if( ( isOverview() || previousFragment() === false ) && availableRoutes().left ) {
slide( indexh - 1 );
// Reverse for RTL
if( config.rtl ) {
if( ( isOverview() || previousFragment() === false ) && availableRoutes().right ) {
slide( indexh - 1 );
}
}
// Normal navigation
else if( ( isOverview() || nextFragment() === false ) && availableRoutes().right ) {
slide( indexh + 1 );
// Prioritize hiding fragments
if( ( isOverview() || previousFragment() === false ) && availableRoutes().up ) {
slide( indexh, indexv - 1 );
// Prioritize revealing fragments
if( ( isOverview() || nextFragment() === false ) && availableRoutes().down ) {
slide( indexh, indexv + 1 );
/**
* Navigates backwards, prioritized in the following order:
* 1) Previous fragment
* 2) Previous vertical slide
* 3) Previous horizontal slide
*/
function navigatePrev() {
// Prioritize revealing fragments
if( previousFragment() === false ) {
if( availableRoutes().up ) {
navigateUp();
}
else {
// Fetch the previous horizontal slide, if there is one
var previousSlide = document.querySelector( HORIZONTAL_SLIDES_SELECTOR + '.past:nth-child(' + indexh + ')' );
if( previousSlide ) {
var v = ( previousSlide.querySelectorAll( 'section' ).length - 1 ) || undefined;
var h = indexh - 1;
slide( h, v );
}
}
}
}
/**
* Same as #navigatePrev() but navigates forwards.
*/
function navigateNext() {
// Prioritize revealing fragments
if( nextFragment() === false ) {
availableRoutes().down ? navigateDown() : navigateRight();
}

Hakim El Hattab
committed
// If auto-sliding is enabled we need to cue up

Hakim El Hattab
committed
// another timeout
cueAutoSlide();

Hakim El Hattab
committed
// --------------------------------------------------------------------//
// ----------------------------- EVENTS -------------------------------//
// --------------------------------------------------------------------//
/**
* Handler for the document level 'keydown' event.

Hakim El Hattab
committed
* @param {Object} event
*/
function onDocumentKeyDown( 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' ) );
// 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;

Hakim El Hattab
committed
// While paused only allow "unpausing" keyboard events (b and .)
if( isPaused() && [66,190,191].indexOf( event.keyCode ) === -1 ) {
return false;
}
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
var triggered = false;
// 1. User defined key bindings
if( typeof config.keyboard === 'object' ) {
for( var key in config.keyboard ) {
// Check if this binding matches the pressed key
if( parseInt( key, 10 ) === event.keyCode ) {
var value = config.keyboard[ key ];
// Calback function
if( typeof value === 'function' ) {
value.apply( null, [ event ] );
}
// String shortcuts to reveal.js API
else if( typeof value === 'string' && typeof Reveal[ value ] === 'function' ) {
Reveal[ value ].call();
}
triggered = true;
}
}
}
// 2. System defined key bindings
if( triggered === false ) {
// Assume true and try to prove false
triggered = true;
switch( event.keyCode ) {
// p, page up
case 80: case 33: navigatePrev(); break;
// n, page down
case 78: case 34: navigateNext(); break;
// h, left
case 72: case 37: navigateLeft(); break;
// l, right
case 76: case 39: navigateRight(); break;
// k, up
case 75: case 38: navigateUp(); break;
// j, down
case 74: case 40: navigateDown(); break;
// home
case 36: slide( 0 ); break;
// end
case 35: slide( Number.MAX_VALUE ); break;
// space
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;
// f
case 70: enterFullscreen(); break;
default:
triggered = false;
}

Hakim El Hattab
committed
}
// If the input resulted in a triggered action we should prevent

Hakim El Hattab
committed
// the browsers default behavior
if( triggered ) {
event.preventDefault();
}
else if ( event.keyCode === 27 && supports3DTransforms ) {
toggleOverview();

Hakim El Hattab
committed
event.preventDefault();
}
// If auto-sliding is enabled we need to cue up

Hakim El Hattab
committed
// another timeout
cueAutoSlide();
}
/**
* Handler for the 'touchstart' event, enables support for
* swipe and pinch gestures.

Hakim El Hattab
committed
*/
function onTouchStart( event ) {

Hakim El Hattab
committed
touch.startX = event.touches[0].clientX;
touch.startY = event.touches[0].clientY;
touch.startCount = event.touches.length;
// If there's two touches we need to memorize the distance

Hakim El Hattab
committed
// between those two points to detect pinching
Loading
Loading full blame...