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,
// Flags if the presentation is running in an embedded mode,
// i.e. contained within a limited portion of the screen
embedded: false,
// 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: false,

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,

Espen Hovlandsdal
committed
// Focuses body when page changes visiblity to ensure keyboard shortcuts work
focusBodyOnPageVisiblityChange: true,

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/none

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
dependencies: []
// Flags if reveal.js is loaded (has dispatched the 'ready' event)
loaded = false,
// The current auto-slide duration
autoSlide = 0,
// The horizontal and vertical index of the currently active slide

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

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

Hakim El Hattab
committed

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,
// A visual component used to control auto slide playback
autoSlidePlayer,
// Holds information about the currently ongoing touch input
touch = {
startX: 0,
startY: 0,
startSpan: 0,
startCount: 0,
* 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' );
// 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 );
extend( config, Reveal.getQueryHash() );

Hakim El Hattab
committed
// Hide the address bar in mobile browsers
hideAddressBar();

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

Hakim El Hattab
committed
features.requestAnimationFrame = typeof window.requestAnimationFrame === 'function';
features.canvas = !!document.createElement( 'canvas' ).getContext;
isMobileDevice = navigator.userAgent.match( /(iphone|ipod|android)/gi );
}
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
/**
* 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() {
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();
// Decorate the slide DOM elements with state classes (past/future)
setupSlides();
// 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() {
// Enable transitions now that we're loaded
dom.slides.classList.remove( 'no-transition' );
loaded = true;
dispatchEvent( 'ready', {
'indexh': indexh,
'indexv': indexv,
'currentSlide': currentSlide
} );
}, 1 );
}
/**
* Iterates through and decorates slides DOM elements with
* appropriate classes.
*/
function setupSlides() {
var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
horizontalSlides.forEach( function( horizontalSlide ) {
var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
verticalSlides.forEach( function( verticalSlide, y ) {
if( y > 0 ) verticalSlide.classList.add( 'future' );
} );
} );
}

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
// 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
// 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' );
// 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 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 ) {
node = document.createElement( tagname );
node.classList.add( classname );
/**
* 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' ),
backgroundImage: slide.getAttribute( 'data-background-image' ),
backgroundColor: slide.getAttribute( 'data-background-color' ),
backgroundRepeat: slide.getAttribute( 'data-background-repeat' ),
backgroundPosition: slide.getAttribute( 'data-background-position' ),
backgroundTransition: slide.getAttribute( 'data-background-transition' )
var element = document.createElement( 'div' );
element.className = 'slide-background';
if( data.background ) {
// Auto-wrap image urls in url(...)
if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)$/gi.test( data.background ) ) {
element.style.backgroundImage = 'url('+ data.background +')';
}
else {
element.style.background = data.background;
}
}
// Additional and optional background properties
if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize;
if( data.backgroundImage ) element.style.backgroundImage = 'url("' + data.backgroundImage + '")';
if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;
if( data.backgroundRepeat ) element.style.backgroundRepeat = data.backgroundRepeat;
if( data.backgroundPosition ) element.style.backgroundPosition = data.backgroundPosition;
if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition );
container.appendChild( element );
return element;
}
// Iterate over all horizontal slides
toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) {
var backgroundStack;
if( isPrintingPDF() ) {
backgroundStack = _createBackground( slideh, slideh );
}
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
// 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' );
}
* 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

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' );
}
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
if( config.autoSlide && config.autoSlideStoppable ) {
autoSlidePlayer = new Playback( dom.wrapper, function() {
return 0.5;
} );
autoSlidePlayer.setPlaying( true );
}
else if( autoSlidePlayer ) {
autoSlidePlayer.destroy();
autoSlidePlayer = null;
}

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 );
}

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

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

Espen Hovlandsdal
committed
if( config.focusBodyOnPageVisiblityChange ) {
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
}
[ '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 );
/**
* 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;
}
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
/**
* 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;
}

Hakim El Hattab
committed
/**
* Returns the remaining height within the parent of the
* target element after subtracting the height of all

Hakim El Hattab
committed
* siblings.
*
* remaining height = [parent height] - [ siblings height]
*/
function getRemainingHeight( element, height ) {

Hakim El Hattab
committed
height = height || 0;

Hakim El Hattab
committed
if( element ) {
var parent = element.parentNode;
var siblings = parent.childNodes;
// Subtract the height of each sibling

Hakim El Hattab
committed
toArray( siblings ).forEach( function( sibling ) {
if( typeof sibling.offsetHeight === 'number' && sibling !== element ) {
var styles = window.getComputedStyle( sibling ),
marginTop = parseInt( styles.marginTop, 10 ),
marginBottom = parseInt( styles.marginBottom, 10 );

Hakim El Hattab
committed
height -= sibling.offsetHeight + marginTop + marginBottom;
}
} );
var elementStyles = window.getComputedStyle( element );
// Subtract the margins of the target element
height -= parseInt( elementStyles.marginTop, 10 ) +
parseInt( elementStyles.marginBottom, 10 );

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() {
// Portrait and not Chrome for iOS
if( window.orientation === 0 && !/crios/gi.test( navigator.userAgent ) ) {

hakimel
committed
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.
*/
function enableRollingLinks() {

Hakim El Hattab
committed
if( features.transforms3d && !( 'msPerspective' in document.body.style ) ) {
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
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.
*/
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
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
/**
* 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
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
* 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,
slidePadding = 20; // TODO Dig this out of DOM

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

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 {
transformElement( dom.slides, 'translate(-50%, -50%) scale('+ scale +') translate(50%, 50%)' );

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 {
slide.style.top = Math.max( - ( getAbsoluteHeight( slide ) / 2 ) - slidePadding, -slideHeight / 2 ) + '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 - ( padding * 2 ) ) );
// 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';
}
} );
}

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' );
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;
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 ) + '%)' );
// 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
}

Hakim El Hattab
committed
updateSlidesVisibility();
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( 'overview-deactivating' );
deactivateOverviewTimeout = setTimeout( function () {
dom.wrapper.classList.remove( 'overview-deactivating' );
}, 1 );
// Select all slides
toArray( document.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() {
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 || 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
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();
// 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();
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];
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' );
var fragments = toArray( element.querySelectorAll( '.fragment.visible' ) );
// No fragments in future slides should be visible ahead of time
while( fragments.length ) {
fragments.pop().classList.remove( 'visible' );
}

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 attribute 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
}

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
}

Hakim El Hattab
committed
/**
* Optimization method; hide all slides that are far away
* from the present slide.
*/
function updateSlidesVisibility() {
// Select all slides and convert the NodeList result to
// an array
var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ),
horizontalSlidesLength = horizontalSlides.length,
distanceX,
distanceY;

Hakim El Hattab
committed
if( horizontalSlidesLength ) {
// The number of steps away from the present slide that will
// be visible
var viewDistance = isOverview() ? 10 : config.viewDistance;

Hakim El Hattab
committed
// Limit view distance on weaker devices
if( isMobileDevice ) {

Hakim El Hattab
committed
}
for( var x = 0; x < horizontalSlidesLength; x++ ) {
var horizontalSlide = horizontalSlides[x];
var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) ),
verticalSlidesLength = verticalSlides.length;
// Loops so that it measures 1 between the first and last slides
distanceX = Math.abs( ( indexh - x ) % ( horizontalSlidesLength - viewDistance ) ) || 0;
// Show the horizontal slide if it's within the view distance
horizontalSlide.style.display = distanceX > viewDistance ? 'none' : 'block';

Hakim El Hattab
committed

Hakim El Hattab
committed
var oy = getPreviousVerticalIndex( horizontalSlide );

Hakim El Hattab
committed
for( var y = 0; y < verticalSlidesLength; y++ ) {
var verticalSlide = verticalSlides[y];
distanceY = x === indexh ? Math.abs( indexv - y ) : Math.abs( y - oy );

Hakim El Hattab
committed
verticalSlide.style.display = ( distanceX + distanceY ) > viewDistance ? 'none' : 'block';

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() {
var routes = availableRoutes();
var fragments = availableFragments();
// Remove the 'enabled' class from all directions
dom.controlsLeft.concat( dom.controlsRight )
.concat( dom.controlsUp )
.concat( dom.controlsDown )
.concat( dom.controlsPrev )
.concat( dom.controlsNext ).forEach( function( node ) {
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' ); } );
}

Hakim El Hattab
committed
* Updates the background elements to reflect the current
*/
function updateBackground() {

Hakim El Hattab
committed
// 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 );
/**
* Updates the position of the parallax background based
* on the current slide index.
*/
function updateParallax() {
if( config.parallaxBackgroundImage ) {
var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
var backgroundSize = dom.background.style.backgroundSize.split( ' ' ),
backgroundWidth, backgroundHeight;
if( backgroundSize.length === 1 ) {
backgroundWidth = backgroundHeight = parseInt( backgroundSize[0], 10 );
}
else {
backgroundWidth = parseInt( backgroundSize[0], 10 );
backgroundHeight = parseInt( backgroundSize[1], 10 );
}
var slideWidth = dom.background.offsetWidth;
var horizontalSlideCount = horizontalSlides.length;
var horizontalOffset = -( backgroundWidth - slideWidth ) / ( horizontalSlideCount-1 ) * indexh;
var slideHeight = dom.background.offsetHeight;
var verticalSlideCount = verticalSlides.length;
var verticalOffset = verticalSlideCount > 0 ? -( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 ) * indexv : 0;
dom.background.style.backgroundPosition = horizontalOffset + 'px ' + verticalOffset + 'px';
* 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 || 0, indexv || 0 );
}
}
else {
// Read the index components of the hash
var h = parseInt( bits[0], 10 ) || 0,
v = parseInt( bits[1], 10 ) || 0;

Tobi Reiss
committed
if( h !== indexh || v !== indexv ) {
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 hasFragments = currentSlide.querySelectorAll( '.fragment' ).length > 0;
if( hasFragments ) {
var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );

Hakim El Hattab
committed
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
Loading
Loading full blame...