aboutsummaryrefslogtreecommitdiffhomepage
path: root/js
diff options
context:
space:
mode:
Diffstat (limited to 'js')
-rw-r--r--js/reveal.js273
1 files changed, 231 insertions, 42 deletions
diff --git a/js/reveal.js b/js/reveal.js
index ff5ea53..c576c8c 100644
--- a/js/reveal.js
+++ b/js/reveal.js
@@ -3,7 +3,7 @@
* http://lab.hakim.se/reveal-js
* MIT licensed
*
- * Copyright (C) 2015 Hakim El Hattab, http://hakim.se
+ * Copyright (C) 2016 Hakim El Hattab, http://hakim.se
*/
(function( root, factory ) {
if( typeof define === 'function' && define.amd ) {
@@ -25,6 +25,9 @@
var Reveal;
+ // The reveal.js version
+ var VERSION = '3.2.0';
+
var SLIDES_SELECTOR = '.slides section',
HORIZONTAL_SLIDES_SELECTOR = '.slides>section',
VERTICAL_SLIDES_SELECTOR = '.slides>section.present>section',
@@ -92,6 +95,9 @@
// Flags if it should be possible to pause the presentation (blackout)
pause: true,
+ // Flags if speaker notes should be visible to all viewers
+ showNotes: 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
@@ -100,6 +106,9 @@
// Stop auto-sliding after user input
autoSlideStoppable: true,
+ // Use this method for navigation when auto-sliding (defaults to navigateNext)
+ autoSlideMethod: null,
+
// Enable slide navigation via mouse wheel
mouseWheel: false,
@@ -457,14 +466,18 @@
// Arrow controls
createSingletonNode( dom.wrapper, 'aside', 'controls',
- '<div class="navigate-left"></div>' +
- '<div class="navigate-right"></div>' +
- '<div class="navigate-up"></div>' +
- '<div class="navigate-down"></div>' );
+ '<button class="navigate-left" aria-label="previous slide"></button>' +
+ '<button class="navigate-right" aria-label="next slide"></button>' +
+ '<button class="navigate-up" aria-label="above slide"></button>' +
+ '<button class="navigate-down" aria-label="below slide"></button>' );
// Slide number
dom.slideNumber = createSingletonNode( dom.wrapper, 'div', 'slide-number', '' );
+ // Element containing notes that are visible to the audience
+ dom.speakerNotes = createSingletonNode( dom.wrapper, 'div', 'speaker-notes', null );
+ dom.speakerNotes.setAttribute( 'data-prevent-swipe', '' );
+
// Overlay graphic which is displayed during the paused mode
createSingletonNode( dom.wrapper, 'div', 'pause-overlay', null );
@@ -535,6 +548,19 @@
document.body.style.width = pageWidth + 'px';
document.body.style.height = pageHeight + 'px';
+ // Add each slide's index as attributes on itself, we need these
+ // indices to generate slide numbers below
+ toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( hslide, h ) {
+ hslide.setAttribute( 'data-index-h', h );
+
+ if( hslide.classList.contains( 'stack' ) ) {
+ toArray( hslide.querySelectorAll( 'section' ) ).forEach( function( vslide, v ) {
+ vslide.setAttribute( 'data-index-h', h );
+ vslide.setAttribute( 'data-index-v', v );
+ } );
+ }
+ } );
+
// Slide and slide background layout
toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
@@ -567,6 +593,34 @@
background.style.top = -top + 'px';
background.style.left = -left + 'px';
}
+
+ // Inject notes if `showNotes` is enabled
+ if( config.showNotes ) {
+ var notes = getSlideNotes( slide );
+ if( notes ) {
+ var notesSpacing = 8;
+ var notesElement = document.createElement( 'div' );
+ notesElement.classList.add( 'speaker-notes' );
+ notesElement.classList.add( 'speaker-notes-pdf' );
+ notesElement.innerHTML = notes;
+ notesElement.style.left = ( notesSpacing - left ) + 'px';
+ notesElement.style.bottom = ( notesSpacing - top ) + 'px';
+ notesElement.style.width = ( pageWidth - notesSpacing*2 ) + 'px';
+ slide.appendChild( notesElement );
+ }
+ }
+
+ // Inject slide numbers if `slideNumbers` are enabled
+ if( config.slideNumber ) {
+ var slideNumberH = parseInt( slide.getAttribute( 'data-index-h' ), 10 ) + 1,
+ slideNumberV = parseInt( slide.getAttribute( 'data-index-v' ), 10 ) + 1;
+
+ var numberElement = document.createElement( 'div' );
+ numberElement.classList.add( 'slide-number' );
+ numberElement.classList.add( 'slide-number-pdf' );
+ numberElement.innerHTML = formatSlideNumber( slideNumberH, '.', slideNumberV );
+ background.appendChild( numberElement );
+ }
}
} );
@@ -836,6 +890,7 @@
dom.controls.style.display = config.controls ? 'block' : 'none';
dom.progress.style.display = config.progress ? 'block' : 'none';
+ dom.slideNumber.style.display = config.slideNumber && !isPrintingPDF() ? 'block' : 'none';
if( config.rtl ) {
dom.wrapper.classList.add( 'rtl' );
@@ -856,6 +911,13 @@
resume();
}
+ if( config.showNotes ) {
+ dom.speakerNotes.classList.add( 'visible' );
+ }
+ else {
+ dom.speakerNotes.classList.remove( 'visible' );
+ }
+
if( config.mouseWheel ) {
document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
document.addEventListener( 'mousewheel', onDocumentMouseScroll, false );
@@ -1530,13 +1592,20 @@
transformSlides( { layout: '' } );
}
else {
- // Prefer zooming in desktop Chrome so that content remains crisp
- if( !isMobileDevice && /chrome/i.test( navigator.userAgent ) && typeof dom.slides.style.zoom !== 'undefined' ) {
+ // Use zoom to scale up in desktop Chrome so that content
+ // remains crisp. We don't use zoom to scale down since that
+ // can lead to shifts in text layout/line breaks.
+ if( scale > 1 && !isMobileDevice && /chrome/i.test( navigator.userAgent ) && typeof dom.slides.style.zoom !== 'undefined' ) {
dom.slides.style.zoom = scale;
+ dom.slides.style.left = '';
+ dom.slides.style.top = '';
+ dom.slides.style.bottom = '';
+ dom.slides.style.right = '';
transformSlides( { layout: '' } );
}
// Apply scale transform as a fallback
else {
+ dom.slides.style.zoom = '';
dom.slides.style.left = '50%';
dom.slides.style.top = '50%';
dom.slides.style.bottom = 'auto';
@@ -2154,6 +2223,7 @@
updateBackground();
updateParallax();
updateSlideNumber();
+ updateNotes();
// Update the URL hash
writeURL();
@@ -2195,6 +2265,7 @@
updateBackground( true );
updateSlideNumber();
updateSlidesVisibility();
+ updateNotes();
formatEmbeddedContent();
startEmbeddedContent( currentSlide );
@@ -2444,6 +2515,22 @@
}
/**
+ * Pick up notes from the current slide and display tham
+ * to the viewer.
+ *
+ * @see `showNotes` config value
+ */
+ function updateNotes() {
+
+ if( config.showNotes && dom.speakerNotes && currentSlide && !isPrintingPDF() ) {
+
+ dom.speakerNotes.innerHTML = getSlideNotes() || '';
+
+ }
+
+ }
+
+ /**
* Updates the progress bar to reflect the current slide.
*/
function updateProgress() {
@@ -2460,30 +2547,59 @@
/**
* 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)
+ * The following slide number formats are available:
+ * "h.v": horizontal . vertical slide number (default)
+ * "h/v": horizontal / vertical slide number
+ * "c": flattened slide number
+ * "c/t": flattened slide number / total slides
*/
function updateSlideNumber() {
// Update slide number if enabled
- if( config.slideNumber && dom.slideNumber) {
+ if( config.slideNumber && dom.slideNumber ) {
- // Default to only showing the current slide number
- var format = 'c';
+ var value = [];
+ var format = 'h.v';
- // Check if a custom slide number format is available
+ // Check if a custom number format is available
if( typeof config.slideNumber === 'string' ) {
format = config.slideNumber;
}
- dom.slideNumber.innerHTML = format.replace( /h/g, indexh )
- .replace( /v/g, indexv )
- .replace( /c/g, getSlidePastCount() + 1 )
- .replace( /t/g, getTotalSlides() );
+ switch( format ) {
+ case 'c':
+ value.push( getSlidePastCount() + 1 );
+ break;
+ case 'c/t':
+ value.push( getSlidePastCount() + 1, '/', getTotalSlides() );
+ break;
+ case 'h/v':
+ value.push( indexh + 1 );
+ if( isVerticalSlide() ) value.push( '/', indexv + 1 );
+ break;
+ default:
+ value.push( indexh + 1 );
+ if( isVerticalSlide() ) value.push( '.', indexv + 1 );
+ }
+
+ dom.slideNumber.innerHTML = formatSlideNumber( value[0], value[1], value[2] );
+ }
+
+ }
+
+ /**
+ * Applies HTML formatting to a slide number before it's
+ * written to the DOM.
+ */
+ function formatSlideNumber( a, delimiter, b ) {
+
+ if( typeof b === 'number' && !isNaN( b ) ) {
+ return '<span class="slide-number-a">'+ a +'</span>' +
+ '<span class="slide-number-delimiter">'+ delimiter +'</span>' +
+ '<span class="slide-number-b">'+ b +'</span>';
+ }
+ else {
+ return '<span class="slide-number-a">'+ a +'</span>';
}
}
@@ -2612,7 +2728,7 @@
// Start video playback
var currentVideo = currentBackground.querySelector( 'video' );
if( currentVideo ) {
- currentVideo.currentTime = 0;
+ if( currentVideo.currentTime > 0 ) currentVideo.currentTime = 0;
currentVideo.play();
}
@@ -2688,7 +2804,7 @@
horizontalOffsetMultiplier = config.parallaxBackgroundHorizontal;
}
else {
- horizontalOffsetMultiplier = ( backgroundWidth - slideWidth ) / ( horizontalSlideCount-1 );
+ horizontalOffsetMultiplier = horizontalSlideCount > 1 ? ( backgroundWidth - slideWidth ) / ( horizontalSlideCount-1 ) : 0;
}
horizontalOffset = horizontalOffsetMultiplier * indexh * -1;
@@ -2760,6 +2876,7 @@
var backgroundImage = slide.getAttribute( 'data-background-image' ),
backgroundVideo = slide.getAttribute( 'data-background-video' ),
backgroundVideoLoop = slide.hasAttribute( 'data-background-video-loop' ),
+ backgroundVideoMuted = slide.hasAttribute( 'data-background-video-muted' ),
backgroundIframe = slide.getAttribute( 'data-background-iframe' );
// Images
@@ -2774,6 +2891,10 @@
video.setAttribute( 'loop', '' );
}
+ if( backgroundVideoMuted ) {
+ video.muted = true;
+ }
+
// Support comma separated lists of video sources
backgroundVideo.split( ',' ).forEach( function( source ) {
video.innerHTML += '<source src="'+ source +'">';
@@ -3151,7 +3272,6 @@
// Attempt to create a named link based on the slide's ID
var id = currentSlide.getAttribute( 'id' );
if( id ) {
- id = id.toLowerCase();
id = id.replace( /[^a-zA-Z0-9\-\_\:\.]/g, '' );
}
@@ -3284,6 +3404,32 @@
}
/**
+ * Retrieves the speaker notes from a slide. Notes can be
+ * defined in two ways:
+ * 1. As a data-notes attribute on the slide <section>
+ * 2. As an <aside class="notes"> inside of the slide
+ */
+ function getSlideNotes( slide ) {
+
+ // Default to the current slide
+ slide = slide || currentSlide;
+
+ // Notes can be specified via the data-notes attribute...
+ if( slide.hasAttribute( 'data-notes' ) ) {
+ return slide.getAttribute( 'data-notes' );
+ }
+
+ // ... or using an <aside class="notes"> element
+ var notesElement = slide.querySelector( 'aside.notes' );
+ if( notesElement ) {
+ return notesElement.innerHTML;
+ }
+
+ return null;
+
+ }
+
+ /**
* Retrieves the current state of the presentation as
* an object. This state can then be restored at any
* time.
@@ -3553,7 +3699,10 @@
// - The overview isn't active
// - The presentation isn't over
if( autoSlide && !autoSlidePaused && !isPaused() && !isOverview() && ( !Reveal.isLastSlide() || availableFragments().next || config.loop === true ) ) {
- autoSlideTimeout = setTimeout( navigateNext, autoSlide );
+ autoSlideTimeout = setTimeout( function() {
+ typeof config.autoSlideMethod === 'function' ? config.autoSlideMethod() : navigateNext();
+ cueAutoSlide();
+ }, autoSlide );
autoSlideStartTime = Date.now();
}
@@ -3699,9 +3848,20 @@
}
}
- // If auto-sliding is enabled we need to cue up
- // another timeout
- cueAutoSlide();
+ }
+
+ /**
+ * Checks if the target element prevents the triggering of
+ * swipe navigation.
+ */
+ function isSwipePrevented( target ) {
+
+ while( target && typeof target.hasAttribute === 'function' ) {
+ if( target.hasAttribute( 'data-prevent-swipe' ) ) return true;
+ target = target.parentNode;
+ }
+
+ return false;
}
@@ -3764,8 +3924,20 @@
// keyboard modifier key is present
if( activeElementIsCE || activeElementIsInput || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
- // While paused only allow "unpausing" keyboard events (b and .)
- if( isPaused() && [66,190,191].indexOf( event.keyCode ) === -1 ) {
+ // While paused only allow resume keyboard events; 'b', '.''
+ var resumeKeyCodes = [66,190,191];
+ var key;
+
+ // Custom key bindings for togglePause should be able to resume
+ if( typeof config.keyboard === 'object' ) {
+ for( key in config.keyboard ) {
+ if( config.keyboard[key] === 'togglePause' ) {
+ resumeKeyCodes.push( parseInt( key, 10 ) );
+ }
+ }
+ }
+
+ if( isPaused() && resumeKeyCodes.indexOf( event.keyCode ) === -1 ) {
return false;
}
@@ -3774,7 +3946,7 @@
// 1. User defined key bindings
if( typeof config.keyboard === 'object' ) {
- for( var key in config.keyboard ) {
+ for( key in config.keyboard ) {
// Check if this binding matches the pressed key
if( parseInt( key, 10 ) === event.keyCode ) {
@@ -3866,6 +4038,8 @@
*/
function onTouchStart( event ) {
+ if( isSwipePrevented( event.target ) ) return true;
+
touch.startX = event.touches[0].clientX;
touch.startY = event.touches[0].clientY;
touch.startCount = event.touches.length;
@@ -3889,6 +4063,8 @@
*/
function onTouchMove( event ) {
+ if( isSwipePrevented( event.target ) ) return true;
+
// Each touch should only trigger one action
if( !touch.captured ) {
onUserInput( event );
@@ -4198,8 +4374,9 @@
function Playback( container, progressCheck ) {
// Cosmetics
- this.diameter = 50;
- this.thickness = 3;
+ this.diameter = 100;
+ this.diameter2 = this.diameter/2;
+ this.thickness = 6;
// Flags if we are currently playing
this.playing = false;
@@ -4217,6 +4394,8 @@
this.canvas.className = 'playback';
this.canvas.width = this.diameter;
this.canvas.height = this.diameter;
+ this.canvas.style.width = this.diameter2 + 'px';
+ this.canvas.style.height = this.diameter2 + 'px';
this.context = this.canvas.getContext( '2d' );
this.container.appendChild( this.canvas );
@@ -4267,10 +4446,10 @@
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;
+ radius = ( this.diameter2 ) - this.thickness,
+ x = this.diameter2,
+ y = this.diameter2,
+ iconSize = 28;
// Ease towards 1
this.progressOffset += ( 1 - this.progressOffset ) * 0.1;
@@ -4283,7 +4462,7 @@
// Solid background color
this.context.beginPath();
- this.context.arc( x, y, radius + 2, 0, Math.PI * 2, false );
+ this.context.arc( x, y, radius + 4, 0, Math.PI * 2, false );
this.context.fillStyle = 'rgba( 0, 0, 0, 0.4 )';
this.context.fill();
@@ -4308,14 +4487,14 @@
// 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 );
+ this.context.fillRect( 0, 0, iconSize / 2 - 4, iconSize );
+ this.context.fillRect( iconSize / 2 + 4, 0, iconSize / 2 - 4, iconSize );
}
else {
this.context.beginPath();
- this.context.translate( 2, 0 );
+ this.context.translate( 4, 0 );
this.context.moveTo( 0, 0 );
- this.context.lineTo( iconSize - 2, iconSize / 2 );
+ this.context.lineTo( iconSize - 4, iconSize / 2 );
this.context.lineTo( 0, iconSize );
this.context.fillStyle = '#fff';
this.context.fill();
@@ -4350,6 +4529,8 @@
Reveal = {
+ VERSION: VERSION,
+
initialize: initialize,
configure: configure,
sync: sync,
@@ -4422,6 +4603,9 @@
// Returns the slide background element at the specified index
getSlideBackground: getSlideBackground,
+ // Returns the speaker notes string for a slide, or null
+ getSlideNotes: getSlideNotes,
+
// Returns the previous slide element, may be null
getPreviousSlide: function() {
return previousSlide;
@@ -4500,6 +4684,11 @@
// Programatically triggers a keyboard event
triggerKey: function( keyCode ) {
onDocumentKeyDown( { keyCode: keyCode } );
+ },
+
+ // Registers a new shortcut to include in the help overlay
+ registerKeyboardShortcut: function( key, value ) {
+ keyboardShortcuts[key] = value;
}
};