Newer
Older
// 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 -------------------------------//
// --------------------------------------------------------------------//

Hakim El Hattab
committed
/**
* Called by all event handlers that are based on user
* input.
*/
function onUserInput( event ) {
if( config.autoSlideStoppable ) {

Hakim El Hattab
committed
}
}

Hakim El Hattab
committed
/**
* Handler for the document level 'keydown' event.
*/
function onDocumentKeyDown( event ) {
// If there's a condition specified and it returns false,
// ignore this event
if( typeof config.keyboardCondition === 'function' && config.keyboardCondition() === false ) {
return true;
}
// Remember if auto-sliding was paused so we can toggle it
var autoSlideWasPaused = autoSlidePaused;
onUserInput( event );
// Check if there's a focused element that could be using
// the keyboard
var activeElement = document.activeElement;
var hasFocus = !!( document.activeElement && ( document.activeElement.type || document.activeElement.href || document.activeElement.contentEditable !== 'inherit' ) );
// 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;
}
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 ];
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
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;
// two-spot, semicolon, b, period, Logitech presenter tools "black screen" button
case 58: case 59: case 66: case 190: case 191: togglePause(); break;
// f
case 70: enterFullscreen(); break;
case 65: if ( config.autoSlideStoppable ) toggleAutoSlide( autoSlideWasPaused ); 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();
}

Hakim El Hattab
committed
else if ( ( event.keyCode === 27 || event.keyCode === 79 ) && features.transforms3d ) {
if( dom.preview ) {
closePreview();
}
else {
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
if( event.touches.length === 2 && config.overview ) {
touch.startSpan = distanceBetween( {
x: event.touches[1].clientX,
y: event.touches[1].clientY
}, {
x: touch.startX,
y: touch.startY
} );
}

Hakim El Hattab
committed
}

Hakim El Hattab
committed
/**
* Handler for the 'touchmove' event.

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

Hakim El Hattab
committed
// Each touch should only trigger one action
onUserInput( event );

Hakim El Hattab
committed
var currentX = event.touches[0].clientX;
var currentY = event.touches[0].clientY;
// If the touch started with two points and still has

Hakim El Hattab
committed
// two active touches; test for the pinch gesture
if( event.touches.length === 2 && touch.startCount === 2 && config.overview ) {
// The current distance in pixels between the two touch points
var currentSpan = distanceBetween( {
x: event.touches[1].clientX,
y: event.touches[1].clientY
}, {
x: touch.startX,
y: touch.startY
} );
// If the span is larger than the desire amount we've got

Hakim El Hattab
committed
// ourselves a pinch
if( Math.abs( touch.startSpan - currentSpan ) > touch.threshold ) {

Hakim El Hattab
committed
if( currentSpan < touch.startSpan ) {
activateOverview();
}
else {
deactivateOverview();
}
}
event.preventDefault();
}
// There was only one touch point, look for a swipe
else if( event.touches.length === 1 && touch.startCount !== 2 ) {
var deltaX = currentX - touch.startX,
deltaY = currentY - touch.startY;
if( deltaX > touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {

Hakim El Hattab
committed
navigateLeft();

Hakim El Hattab
committed
else if( deltaX < -touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {

Hakim El Hattab
committed
navigateRight();

Hakim El Hattab
committed
else if( deltaY > touch.threshold ) {

Hakim El Hattab
committed
navigateUp();

Hakim El Hattab
committed
else if( deltaY < -touch.threshold ) {

Hakim El Hattab
committed
navigateDown();
}
// If we're embedded, only block touch events if they have
// triggered an action
if( config.embedded ) {
if( touch.captured || isVerticalSlide( currentSlide ) ) {
event.preventDefault();
}
}
// Not embedded? Block them all to avoid needless tossing
// around of the viewport in iOS
else {
event.preventDefault();
}

Hakim El Hattab
committed
}
}
// There's a bug with swiping on some Android devices unless

Hakim El Hattab
committed
// the default action is always prevented
else if( navigator.userAgent.match( /android/gi ) ) {
event.preventDefault();
}

Hakim El Hattab
committed
}
/**
* Handler for the 'touchend' event.

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

Hakim El Hattab
committed
}
/**
* Convert pointer down to touch start.
*/
function onPointerDown( event ) {
if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
}
}
/**
* Convert pointer move to touch move.
*/
function onPointerMove( event ) {
if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
}
}
/**
* Convert pointer up to touch end.
*/
function onPointerUp( event ) {
if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
event.touches = [{ clientX: event.clientX, clientY: event.clientY }];

Hakim El Hattab
committed
/**
* Handles mouse wheel scrolling, throttled to avoid skipping
* multiple slides.

Hakim El Hattab
committed
*/
function onDocumentMouseScroll( event ) {
if( Date.now() - lastMouseWheelStep > 600 ) {
lastMouseWheelStep = Date.now();

Hakim El Hattab
committed
var delta = event.detail || -event.wheelDelta;
if( delta > 0 ) {
navigateNext();
}
else {
navigatePrev();
}

Hakim El Hattab
committed
}
* Clicking on the progress bar results in a navigation to the
* closest approximate horizontal slide using this equation:
*
* ( clickX / presentationWidth ) * numberOfSlides
*/

Hakim El Hattab
committed
function onProgressClicked( event ) {
onUserInput( event );

Hakim El Hattab
committed
event.preventDefault();
var slidesTotal = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
var slideIndex = Math.floor( ( event.clientX / dom.wrapper.offsetWidth ) * slidesTotal );
slide( slideIndex );

Hakim El Hattab
committed
/**
* Event handler for navigation control buttons.

Hakim El Hattab
committed
*/
function onNavigateLeftClicked( event ) { event.preventDefault(); onUserInput(); navigateLeft(); }
function onNavigateRightClicked( event ) { event.preventDefault(); onUserInput(); navigateRight(); }
function onNavigateUpClicked( event ) { event.preventDefault(); onUserInput(); navigateUp(); }
function onNavigateDownClicked( event ) { event.preventDefault(); onUserInput(); navigateDown(); }
function onNavigatePrevClicked( event ) { event.preventDefault(); onUserInput(); navigatePrev(); }
function onNavigateNextClicked( event ) { event.preventDefault(); onUserInput(); navigateNext(); }

Hakim El Hattab
committed

Hakim El Hattab
committed
/**
* Handler for the window level 'hashchange' event.
*/
function onWindowHashChange( event ) {

Hakim El Hattab
committed
readURL();

Hakim El Hattab
committed
}
/**
* Handler for the window level 'resize' event.
*/
function onWindowResize( event ) {

Espen Hovlandsdal
committed
/**
* Handle for the window level 'visibilitychange' event.
*/
function onPageVisibilityChange( event ) {
var isHidden = document.webkitHidden ||
document.msHidden ||
document.hidden;
// If, after clicking a link or similar and we're coming back,
// focus the document.body to ensure we can use keyboard shortcuts
if( isHidden === false && document.activeElement !== document.body ) {
document.activeElement.blur();
document.body.focus();
}
}

Hakim El Hattab
committed
/**
* Invoked when a slide is and we're in the overview.
*/
function onOverviewSlideClicked( event ) {
// TODO There's a bug here where the event listeners are not

Hakim El Hattab
committed
// removed after deactivating the overview.
if( eventsAreBound && isOverview() ) {

Hakim El Hattab
committed
event.preventDefault();
var element = event.target;
while( element && !element.nodeName.match( /section/gi ) ) {
element = element.parentNode;
}
if( element && !element.classList.contains( 'disabled' ) ) {
deactivateOverview();
if( element.nodeName.match( /section/gi ) ) {
var h = parseInt( element.getAttribute( 'data-index-h' ), 10 ),
v = parseInt( element.getAttribute( 'data-index-v' ), 10 );
slide( h, v );
}

Hakim El Hattab
committed
}

Hakim El Hattab
committed
}

Hakim El Hattab
committed
/**
* Handles clicks on links that are set to preview in the
* iframe overlay.
*/
function onPreviewLinkClicked( event ) {
var url = event.target.getAttribute( 'href' );
if( url ) {
openPreview( url );
event.preventDefault();
}
}
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
/**
* Handles click on the auto-sliding controls element.
*/
function onAutoSlidePlayerClick( event ) {
// Replay
if( Reveal.isLastSlide() && config.loop === false ) {
slide( 0, 0 );
resumeAutoSlide();
}
// Resume
else if( autoSlidePaused ) {
resumeAutoSlide();
}
// Pause
else {
pauseAutoSlide();
}
}
// --------------------------------------------------------------------//
// ------------------------ PLAYBACK COMPONENT ------------------------//
// --------------------------------------------------------------------//
/**
* Constructor for the playback component, which displays
* play/pause/progress controls.
*
* @param {HTMLElement} container The component will append
* itself to this
* @param {Function} progressCheck A method which will be
* called frequently to get the current progress on a range
* of 0-1
*/
function Playback( container, progressCheck ) {
// Cosmetics
this.diameter = 50;
// Flags if we are currently playing
// Current progress on a 0-1 range
// Used to loop the animation smoothly
this.progressOffset = 1;
this.container = container;
this.progressCheck = progressCheck;
this.canvas = document.createElement( 'canvas' );
this.canvas.className = 'playback';
this.canvas.width = this.diameter;
this.canvas.height = this.diameter;
this.context = this.canvas.getContext( '2d' );
this.container.appendChild( this.canvas );
this.render();
}
Playback.prototype.setPlaying = function( value ) {
var wasPlaying = this.playing;
this.playing = value;
// Start repainting if we weren't already
if( !wasPlaying && this.playing ) {
this.animate();
else {
this.render();
}
Playback.prototype.animate = function() {
var progressBefore = this.progress;
this.progress = this.progressCheck();
// When we loop, offset the progress so that it eases
// smoothly rather than immediately resetting
if( progressBefore > 0.8 && this.progress < 0.2 ) {
this.progressOffset = this.progress;
}
this.render();
if( this.playing ) {
features.requestAnimationFrameMethod.call( window, this.animate.bind( this ) );
/**
* Renders the current progress and playback state.
*/
Playback.prototype.render = function() {
var progress = this.playing ? this.progress : 0,
radius = ( this.diameter / 2 ) - this.thickness,
x = this.diameter / 2,
y = this.diameter / 2,
iconSize = 14;
// Ease towards 1
this.progressOffset += ( 1 - this.progressOffset ) * 0.1;
var endAngle = ( - Math.PI / 2 ) + ( progress * ( Math.PI * 2 ) );
var startAngle = ( - Math.PI / 2 ) + ( this.progressOffset * ( Math.PI * 2 ) );
this.context.clearRect( 0, 0, this.diameter, this.diameter );
// Solid background color
this.context.beginPath();
this.context.arc( x, y, radius + 2, 0, Math.PI * 2, false );
this.context.fillStyle = 'rgba( 0, 0, 0, 0.4 )';
this.context.fill();
// Draw progress track
this.context.beginPath();
this.context.arc( x, y, radius, 0, Math.PI * 2, false );
this.context.lineWidth = this.thickness;
this.context.strokeStyle = '#666';
this.context.stroke();
if( this.playing ) {
// Draw progress on top of track
this.context.beginPath();
this.context.arc( x, y, radius, startAngle, endAngle, false );
this.context.lineWidth = this.thickness;
this.context.strokeStyle = '#fff';
this.context.stroke();
}
this.context.translate( x - ( iconSize / 2 ), y - ( iconSize / 2 ) );
// Draw play/pause icons
if( this.playing ) {
this.context.fillStyle = '#fff';
this.context.fillRect( 0, 0, iconSize / 2 - 2, iconSize );
this.context.fillRect( iconSize / 2 + 2, 0, iconSize / 2 - 2, iconSize );
}
else {
this.context.beginPath();
this.context.translate( 2, 0 );
this.context.moveTo( 0, 0 );
this.context.lineTo( iconSize - 2, iconSize / 2 );
this.context.lineTo( 0, iconSize );
this.context.fillStyle = '#fff';
this.context.fill();
}
this.context.restore();
};
Playback.prototype.on = function( type, listener ) {
this.canvas.addEventListener( type, listener, false );
};
Playback.prototype.off = function( type, listener ) {
this.canvas.removeEventListener( type, listener, false );
};
Playback.prototype.destroy = function() {
this.playing = false;
if( this.canvas.parentNode ) {
this.container.removeChild( this.canvas );
}
};

Hakim El Hattab
committed
// --------------------------------------------------------------------//
// ------------------------------- API --------------------------------//
// --------------------------------------------------------------------//
initialize: initialize,
configure: configure,

Hakim El Hattab
committed
// Navigation methods
slide: slide,
left: navigateLeft,
right: navigateRight,
up: navigateUp,
down: navigateDown,
prev: navigatePrev,
next: navigateNext,

Hakim El Hattab
committed
// Fragment methods
navigateFragment: navigateFragment,
prevFragment: previousFragment,
nextFragment: nextFragment,

Hakim El Hattab
committed
// Deprecated aliases
navigateTo: slide,
navigateLeft: navigateLeft,
navigateRight: navigateRight,
navigateUp: navigateUp,

Hakim El Hattab
committed
navigateDown: navigateDown,
navigatePrev: navigatePrev,
navigateNext: navigateNext,

Hakim El Hattab
committed
// Forces an update in slide layout
layout: layout,
// Returns an object with the available routes as booleans (left/right/top/bottom)
availableRoutes: availableRoutes,
// Returns an object with the available fragments as booleans (prev/next)
availableFragments: availableFragments,

Hakim El Hattab
committed
// Toggles the overview mode on/off
toggleOverview: toggleOverview,

Hakim El Hattab
committed
// Toggles the "black screen" mode on/off
togglePause: togglePause,
// Toggles the auto slide mode on/off
toggleAutoSlide: toggleAutoSlide,
// State checks
isOverview: isOverview,
isPaused: isPaused,

Hakim El Hattab
committed
// Adds or removes all internal event listeners (such as keyboard)
addEventListeners: addEventListeners,
removeEventListeners: removeEventListeners,
// Facility for persisting and restoring the presentation state
getState: getState,
setState: setState,
// Presentation progress on range of 0-1
getProgress: getProgress,
// Returns the indices of the current, or specified, slide

Hakim El Hattab
committed
getIndices: getIndices,

Hakim El Hattab
committed
// Returns the slide element at the specified index
getSlide: getSlide,
// Returns the slide background element at the specified index
getSlideBackground: getSlideBackground,

Hakim El Hattab
committed
// Returns the previous slide element, may be null
getPreviousSlide: function() {
return previousSlide;

Hakim El Hattab
committed
},
// Returns the current slide element
getCurrentSlide: function() {
return currentSlide;

Hakim El Hattab
committed
},

Hakim El Hattab
committed
// Returns the current scale of the presentation content
getScale: function() {
return scale;
},
// Returns the current configuration object
getConfig: function() {
return config;
},
// Helper method, retrieves query string as a key/value hash
getQueryHash: function() {
var query = {};
location.search.replace( /[A-Z0-9]+?=([\w\.%-]*)/gi, function(a) {
query[ a.split( '=' ).shift() ] = a.split( '=' ).pop();
} );
// Basic deserialization
for( var i in query ) {
var value = query[ i ];
query[ i ] = deserialize( unescape( value ) );
}
return query;
},
// Returns true if we're currently on the first slide
isFirstSlide: function() {
return document.querySelector( SLIDES_SELECTOR + '.past' ) == null ? true : false;
},
// Returns true if we're currently on the last slide
isLastSlide: function() {
if( currentSlide ) {
// Does this slide has next a sibling?
if( currentSlide.nextElementSibling ) return false;
// If it's vertical, does its parent have a next sibling?
if( isVerticalSlide( currentSlide ) && currentSlide.parentNode.nextElementSibling ) return false;
return true;
return false;
// Checks if reveal.js has been loaded and is ready for use
isReady: function() {
return loaded;
},

Hakim El Hattab
committed
// Forward event binding to the reveal DOM element
addEventListener: function( type, listener, useCapture ) {
if( 'addEventListener' in window ) {
( dom.wrapper || document.querySelector( '.reveal' ) ).addEventListener( type, listener, useCapture );
}

Hakim El Hattab
committed
},
removeEventListener: function( type, listener, useCapture ) {
if( 'addEventListener' in window ) {
( dom.wrapper || document.querySelector( '.reveal' ) ).removeEventListener( type, listener, useCapture );
}
},
// Programatically triggers a keyboard event
triggerKey: function( keyCode ) {
onDocumentKeyDown( { keyCode: keyCode } );

Hakim El Hattab
committed
}