diff options
Diffstat (limited to 'js')
-rw-r--r-- | js/reveal.js | 273 |
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; } }; |