Newer
Older

Hakim El Hattab
committed
var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
// If no vertical index is specified and the upcoming slide is a
// stack, resume at its previous vertical index
if( v === undefined && !isOverview() ) {

Hakim El Hattab
committed
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
}
// Update the overview if it's currently active
if( isOverview() ) {

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

Hakim El Hattab
committed

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

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

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

Hakim El Hattab
committed
'indexv': indexv,

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Nawaz
committed
element.setAttribute( 'aria-hidden', 'true' );
// If this element contains vertical slides
if( element.querySelector( 'section' ) ) {
element.classList.add( 'stack' );
}
// If we're printing static slides, all slides are "present"
if( printMode ) {
element.classList.add( 'present' );
continue;
}

Hakim El Hattab
committed
if( i < index ) {
// Any element previous to index is given the 'past' class
element.classList.add( reverse ? 'future' : 'past' );
if( config.fragments ) {
var pastFragments = toArray( element.querySelectorAll( '.fragment' ) );
// Show all fragments on prior slides
while( pastFragments.length ) {
var pastFragment = pastFragments.pop();
pastFragment.classList.add( 'visible' );
pastFragment.classList.remove( 'current-fragment' );
}

Hakim El Hattab
committed
}
else if( i > index ) {
// Any element subsequent to index is given the 'future' class
element.classList.add( reverse ? 'past' : 'future' );
if( config.fragments ) {
var futureFragments = toArray( element.querySelectorAll( '.fragment.visible' ) );
// No fragments in future slides should be visible ahead of time
while( futureFragments.length ) {
var futureFragment = futureFragments.pop();
futureFragment.classList.remove( 'visible' );
futureFragment.classList.remove( 'current-fragment' );
}

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

Nawaz
committed
slides[index].removeAttribute( 'aria-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( ' ' ) );
}
}
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

Hakim El Hattab
committed
var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ),

Hakim El Hattab
committed
horizontalSlidesLength = horizontalSlides.length,
distanceX,
distanceY;

Hakim El Hattab
committed
if( horizontalSlidesLength && typeof indexh !== 'undefined' ) {

Hakim El Hattab
committed
// 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
}
// Limit view distance on weaker devices
if( isPrintingPDF() ) {
viewDistance = Number.MAX_VALUE;
}

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 || 0 ) - x ) % ( horizontalSlidesLength - viewDistance ) ) || 0;
// Show the horizontal slide if it's within the view distance
if( distanceX < viewDistance ) {
showSlide( horizontalSlide );
}
else {
hideSlide( horizontalSlide );

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 || 0 ) ? Math.abs( ( indexv || 0 ) - y ) : Math.abs( y - oy );

Hakim El Hattab
committed
if( distanceX + distanceY < viewDistance ) {
showSlide( verticalSlide );
}
else {
hideSlide( verticalSlide );

Hakim El Hattab
committed
}
}
}
}
}
* Updates the progress bar to reflect the current slide.
*/
function updateProgress() {
// Update progress if enabled
if( config.progress && dom.progressbar ) {
dom.progressbar.style.width = getProgress() * dom.wrapper.offsetWidth + 'px';
/**
* Updates the slide number div to reflect the current slide.
*
* Slide number format can be defined as a string using the
* following variables:
* h: current slide's horizontal index
* v: current slide's vertical index
* c: current slide index (flattened)
* t: total number of slides (flattened)
*/
function updateSlideNumber() {
// Update slide number if enabled

Hakim El Hattab
committed
if( config.slideNumber && dom.slideNumber) {
// Default to only showing the current slide number
var format = 'c';
// Check if a custom slide number format is available
if( typeof config.slideNumber === 'string' ) {
format = config.slideNumber;
}
var totalSlides = getTotalSlides();
dom.slideNumber.innerHTML = format.replace( /h/g, indexh )
.replace( /v/g, indexv )
.replace( /c/g, Math.round( getProgress() * totalSlides ) + 1 )
.replace( /t/g, totalSlides + 1 );
}
}
/**
* 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
*
* @param {Boolean} includeAll If true, the backgrounds of
* all vertical slides (not just the present) will be updated.
function updateBackground( includeAll ) {
var currentBackground = null;
// Reverse past/future classes when in RTL mode
var horizontalPast = config.rtl ? 'future' : 'past',
horizontalFuture = config.rtl ? 'past' : 'future';

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 ) {
backgroundh.classList.remove( 'past' );
backgroundh.classList.remove( 'present' );
backgroundh.classList.remove( 'future' );
backgroundh.classList.add( horizontalPast );
}
else if ( h > indexh ) {
backgroundh.classList.add( horizontalFuture );
backgroundh.classList.add( 'present' );
// Store a reference to the current background element
currentBackground = backgroundh;
}
if( includeAll || h === indexh ) {
toArray( backgroundh.querySelectorAll( '.slide-background' ) ).forEach( function( backgroundv, v ) {
backgroundv.classList.remove( 'past' );
backgroundv.classList.remove( 'present' );
backgroundv.classList.remove( 'future' );
if( v < indexv ) {
backgroundv.classList.add( 'past' );
}
else if ( v > indexv ) {
backgroundv.classList.add( 'future' );
backgroundv.classList.add( 'present' );
// Only if this is the present horizontal and vertical slide
if( h === indexh ) currentBackground = backgroundv;
}
} );
// Stop any currently playing video background
if( previousBackground ) {
var previousVideo = previousBackground.querySelector( 'video' );
if( previousVideo ) previousVideo.pause();
}
if( currentBackground ) {
// Start video playback
var currentVideo = currentBackground.querySelector( 'video' );
if( currentVideo ) {
currentVideo.currentTime = 0;
currentVideo.play();
}
var backgroundImageURL = currentBackground.style.backgroundImage || '';
// Restart GIFs (doesn't work in Firefox)
if( /\.gif/i.test( backgroundImageURL ) ) {
currentBackground.style.backgroundImage = '';
window.getComputedStyle( currentBackground ).opacity;
currentBackground.style.backgroundImage = backgroundImageURL;
}
// Don't transition between identical backgrounds. This
// prevents unwanted flicker.
var previousBackgroundHash = previousBackground ? previousBackground.getAttribute( 'data-background-hash' ) : null;
var currentBackgroundHash = currentBackground.getAttribute( 'data-background-hash' );
if( currentBackgroundHash && currentBackgroundHash === previousBackgroundHash && currentBackground !== previousBackground ) {
dom.background.classList.add( 'no-transition' );
}
previousBackground = currentBackground;
// If there's a background brightness flag for this slide,
// bubble it to the .reveal container
if( currentSlide ) {
[ 'has-light-background', 'has-dark-background' ].forEach( function( classToBubble ) {
if( currentSlide.classList.contains( classToBubble ) ) {
dom.wrapper.classList.add( classToBubble );
}
else {
dom.wrapper.classList.remove( classToBubble );
}
} );
// 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 ) {

Hakim El Hattab
committed
var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
verticalSlides = dom.wrapper.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 > 1 ? -( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 ) * indexv : 0;
dom.background.style.backgroundPosition = horizontalOffset + 'px ' + verticalOffset + 'px';
* Called when the given slide is within the configured view
* distance. Shows the slide element and loads any content
* that is set to load lazily (data-src).
function showSlide( slide ) {
slide.style.display = 'block';
// Media elements with data-src attributes
toArray( slide.querySelectorAll( 'img[data-src], video[data-src], audio[data-src], iframe[data-src]' ) ).forEach( function( element ) {
element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
element.removeAttribute( 'data-src' );
} );
// Media elements with <source> children
toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( media ) {
toArray( media.querySelectorAll( 'source[data-src]' ) ).forEach( function( source ) {
source.setAttribute( 'src', source.getAttribute( 'data-src' ) );
source.removeAttribute( 'data-src' );
sources += 1;
} );
// If we rewrote sources for this video/audio element, we need
// to manually tell it to load from its new origin
// Show the corresponding background element
var indices = getIndices( slide );
var background = getSlideBackground( indices.h, indices.v );
if( background ) {
background.style.display = 'block';
// If the background contains media, load it
if( background.hasAttribute( 'data-loaded' ) === false ) {
background.setAttribute( 'data-loaded', 'true' );
var backgroundImage = slide.getAttribute( 'data-background-image' ),
backgroundVideo = slide.getAttribute( 'data-background-video' ),
backgroundIframe = slide.getAttribute( 'data-background-iframe' );
// Images
if( backgroundImage ) {
background.style.backgroundImage = 'url('+ backgroundImage +')';
}
// Videos
else if ( backgroundVideo && !isSpeakerNotes() ) {
var video = document.createElement( 'video' );
// Support comma separated lists of video sources
backgroundVideo.split( ',' ).forEach( function( source ) {
video.innerHTML += '<source src="'+ source +'">';
} );
// Iframes
else if ( backgroundIframe ) {
var iframe = document.createElement( 'iframe' );
iframe.setAttribute( 'src', backgroundIframe );
iframe.style.width = '100%';
iframe.style.height = '100%';
iframe.style.maxHeight = '100%';
iframe.style.maxWidth = '100%';
background.appendChild( iframe );
}
/**
* Called when the given slide is moved outside of the
* configured view distance.
*/
function hideSlide( slide ) {
slide.style.display = 'none';
// Hide the corresponding background element
var indices = getIndices( slide );
var background = getSlideBackground( indices.h, indices.v );
if( background ) {
background.style.display = 'none';
}
* Determine what available routes there are for navigation.
* @return {Object} containing four booleans: left/right/up/down
*/
function availableRoutes() {

Hakim El Hattab
committed
var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
verticalSlides = dom.wrapper.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 };
}
}
/**
* Enforces origin-specific format rules for embedded media.
*/
function formatEmbeddedContent() {
// YouTube frames must include "?enablejsapi=1"
toArray( dom.slides.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
var src = el.getAttribute( 'src' );
if( !/enablejsapi\=1/gi.test( src ) ) {
el.setAttribute( 'src', src + ( !/\?/.test( src ) ? '?' : '&' ) + 'enablejsapi=1' );
}
});
// Vimeo frames must include "?api=1"
toArray( dom.slides.querySelectorAll( 'iframe[src*="player.vimeo.com/"]' ) ).forEach( function( el ) {
var src = el.getAttribute( 'src' );
if( !/api\=1/gi.test( src ) ) {
el.setAttribute( 'src', src + ( !/\?/.test( src ) ? '?' : '&' ) + 'api=1' );
}
/**
* Start playback of any embedded content inside of
* the targeted slide.
*/
function startEmbeddedContent( slide ) {
if( slide && !isSpeakerNotes() ) {
// Restart GIFs
toArray( slide.querySelectorAll( 'img[src$=".gif"]' ) ).forEach( function( el ) {
// Setting the same unchanged source like this was confirmed
// to work in Chrome, FF & Safari
el.setAttribute( 'src', el.getAttribute( 'src' ) );
} );
// HTML5 media elements
toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
if( el.hasAttribute( 'data-autoplay' ) ) {
el.play();
}
} );

Brad Gessler
committed
// iframe embeds
toArray( slide.querySelectorAll( 'iframe' ) ).forEach( function( el ) {
el.contentWindow.postMessage( 'slide:start', '*' );

Brad Gessler
committed
});
// 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":""}', '*' );
}
});
// Vimeo embeds
toArray( slide.querySelectorAll( 'iframe[src*="player.vimeo.com/"]' ) ).forEach( function( el ) {
if( el.hasAttribute( 'data-autoplay' ) ) {
el.contentWindow.postMessage( '{"method":"play"}', '*' );
}
});
}
}
/**
* Stop playback of any embedded content inside of
* the targeted slide.
*/
function stopEmbeddedContent( slide ) {
// HTML5 media elements
toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
if( !el.hasAttribute( 'data-ignore' ) ) {
el.pause();
}
} );

Brad Gessler
committed
// iframe embeds
toArray( slide.querySelectorAll( 'iframe' ) ).forEach( function( el ) {
el.contentWindow.postMessage( 'slide:stop', '*' );

Brad Gessler
committed
});
// 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":""}', '*' );
}
});
// Vimeo embeds
toArray( slide.querySelectorAll( 'iframe[src*="player.vimeo.com/"]' ) ).forEach( function( el ) {
if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) {
el.contentWindow.postMessage( '{"method":"pause"}', '*' );
}
});
}
}
/**
* Returns a value ranging from 0-1 that represents
* how far into the presentation we have navigated.
*/
function getProgress() {

Hakim El Hattab
committed
var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
// The number of past and total slides
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
var pastCount = 0;
// Step through all slides and count the past ones
mainLoop: for( var i = 0; i < horizontalSlides.length; i++ ) {
var horizontalSlide = horizontalSlides[i];
var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
for( var j = 0; j < verticalSlides.length; j++ ) {
// Stop as soon as we arrive at the present
if( verticalSlides[j].classList.contains( 'present' ) ) {
break mainLoop;
}
pastCount++;
}
// Stop as soon as we arrive at the present
if( horizontalSlide.classList.contains( 'present' ) ) {
break;
}
// Don't count the wrapping section for vertical slides
if( horizontalSlide.classList.contains( 'stack' ) === false ) {
pastCount++;
}
}
if( currentSlide ) {
var allFragments = currentSlide.querySelectorAll( '.fragment' );
// If there are fragments in the current slide those should be
// accounted for in the progress.
if( allFragments.length > 0 ) {
var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
// This value represents how big a portion of the slide progress
// that is made up by its fragments (0-1)
var fragmentWeight = 0.9;
// Add fragment progress to the past slide count
pastCount += ( visibleFragments.length / allFragments.length ) * fragmentWeight;
}
}
return pastCount / ( totalCount - 1 );
}
/**
* Checks if this presentation is running inside of the
* speaker notes window.
*/
function isSpeakerNotes() {
return !!window.location.search.match( /receiver/gi );
}
/**
* 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 ) {
// Ensure the named link is a valid HTML ID attribute