Newer
Older

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();
}
}
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
/**
* 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 at the specified index, y is optional
getSlide: function( x, y ) {
var horizontalSlide = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR )[ x ];
var verticalSlides = horizontalSlide && horizontalSlide.querySelectorAll( 'section' );
if( typeof y !== 'undefined' ) {
return verticalSlides ? verticalSlides[ y ] : undefined;
}
return horizontalSlide;
},

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

Hakim El Hattab
committed
}