summaryrefslogtreecommitdiffhomepage
path: root/js/reveal.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/reveal.js')
-rw-r--r--js/reveal.js446
1 files changed, 394 insertions, 52 deletions
diff --git a/js/reveal.js b/js/reveal.js
index 132237a..e7788c2 100644
--- a/js/reveal.js
+++ b/js/reveal.js
@@ -70,6 +70,9 @@ var Reveal = (function(){
// Apply a 3D roll to links on hover
rollingLinks: true,
+ // Opens links in an iframe preview overlay
+ previewLinks: false,
+
// Theme (see /css/theme)
theme: null,
@@ -79,6 +82,9 @@ var Reveal = (function(){
// Transition speed
transitionSpeed: 'default', // default/fast/slow
+ // Transition style for full page slide backgrounds
+ backgroundTransition: 'default', // default/linear
+
// Script dependencies to load
dependencies: []
},
@@ -186,6 +192,13 @@ var Reveal = (function(){
dom.wrapper = document.querySelector( '.reveal' );
dom.slides = document.querySelector( '.reveal .slides' );
+ // Background element
+ if( !document.querySelector( '.reveal .backgrounds' ) ) {
+ dom.background = document.createElement( 'div' );
+ dom.background.classList.add( 'backgrounds' );
+ dom.wrapper.appendChild( dom.background );
+ }
+
// Progress bar
if( !dom.wrapper.querySelector( '.progress' ) ) {
var progressElement = document.createElement( 'div' );
@@ -205,11 +218,11 @@ var Reveal = (function(){
dom.wrapper.appendChild( controlsElement );
}
- // Presentation background element
+ // State background element [DEPRECATED]
if( !dom.wrapper.querySelector( '.state-background' ) ) {
- var backgroundElement = document.createElement( 'div' );
- backgroundElement.classList.add( 'state-background' );
- dom.wrapper.appendChild( backgroundElement );
+ var stateBackgroundElement = document.createElement( 'div' );
+ stateBackgroundElement.classList.add( 'state-background' );
+ dom.wrapper.appendChild( stateBackgroundElement );
}
// Overlay graphic which is displayed during the paused mode
@@ -238,6 +251,86 @@ var Reveal = (function(){
}
/**
+ * Creates the slide background elements and appends them
+ * to the background container. One element is created per
+ * slide no matter if the given slide has visible background.
+ */
+ function createBackgrounds() {
+
+ if( isPrintingPDF() ) {
+ document.body.classList.add( 'print-pdf' );
+ }
+
+ // Clear prior backgrounds
+ dom.background.innerHTML = '';
+ dom.background.classList.add( 'no-transition' );
+
+ // Helper method for creating a background element for the
+ // given slide
+ function _createBackground( slide, container ) {
+
+ var data = {
+ background: slide.getAttribute( 'data-background' ),
+ backgroundSize: slide.getAttribute( 'data-background-size' ),
+ backgroundColor: slide.getAttribute( 'data-background-color' ),
+ backgroundRepeat: slide.getAttribute( 'data-background-repeat' ),
+ backgroundPosition: slide.getAttribute( 'data-background-position' )
+ };
+
+ var element = document.createElement( 'div' );
+ element.className = 'slide-background';
+
+ if( data.background ) {
+ // Auto-wrap image urls in url(...)
+ if( /\.(png|jpg|jpeg|gif|bmp)$/gi.test( data.background ) ) {
+ element.style.backgroundImage = 'url('+ data.background +')';
+ }
+ else {
+ element.style.background = data.background;
+ }
+ }
+
+ // Additional and optional background properties
+ if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize;
+ if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;
+ if( data.backgroundRepeat ) element.style.backgroundRepeat = data.backgroundRepeat;
+ if( data.backgroundPosition ) element.style.backgroundPosition = data.backgroundPosition;
+
+ container.appendChild( element );
+
+ return element;
+
+ }
+
+ // Iterate over all horizontal slides
+ toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) {
+
+ var backgroundStack;
+
+ if( isPrintingPDF() ) {
+ backgroundStack = _createBackground( slideh, slideh );
+ }
+ else {
+ backgroundStack = _createBackground( slideh, dom.background );
+ }
+
+ // Iterate over all vertical slides
+ toArray( slideh.querySelectorAll( 'section' ) ).forEach( function( slidev ) {
+
+ if( isPrintingPDF() ) {
+ _createBackground( slidev, slidev );
+ }
+ else {
+ _createBackground( slidev, backgroundStack );
+ }
+
+ } );
+
+ } );
+
+ }
+
+ /**
* Hides the address bar if we're on a mobile device.
*/
function hideAddressBar() {
@@ -348,6 +441,7 @@ var Reveal = (function(){
dom.wrapper.classList.add( config.transition );
dom.wrapper.setAttribute( 'data-transition-speed', config.transitionSpeed );
+ dom.wrapper.setAttribute( 'data-background-transition', config.backgroundTransition );
if( dom.controls ) {
dom.controls.style.display = ( config.controls && dom.controls ) ? 'block' : 'none';
@@ -380,12 +474,21 @@ var Reveal = (function(){
document.removeEventListener( 'mousewheel', onDocumentMouseScroll, false );
}
- // 3D links
+ // Rolling 3D links
if( config.rollingLinks ) {
- enable3DLinks();
+ enableRollingLinks();
+ }
+ else {
+ disableRollingLinks();
+ }
+
+ // Iframe link previews
+ if( config.previewLinks ) {
+ enablePreviewLinks();
}
else {
- disable3DLinks();
+ disablePreviewLinks();
+ enablePreviewLinks( '[data-preview-link]' );
}
// Load the theme in the config, if it's not already loaded
@@ -524,6 +627,50 @@ var Reveal = (function(){
}
/**
+ * Retrieves the height of the given element by looking
+ * at the position and height of its immediate children.
+ */
+ function getAbsoluteHeight( element ) {
+
+ var height = 0;
+
+ if( element ) {
+ var absoluteChildren = 0;
+
+ toArray( element.childNodes ).forEach( function( child ) {
+
+ if( typeof child.offsetTop === 'number' && child.style ) {
+ // Count # of abs children
+ if( child.style.position === 'absolute' ) {
+ absoluteChildren += 1;
+ }
+
+ height = Math.max( height, child.offsetTop + child.offsetHeight );
+ }
+
+ } );
+
+ // If there are no absolute children, use offsetHeight
+ if( absoluteChildren === 0 ) {
+ height = element.offsetHeight;
+ }
+
+ }
+
+ return height;
+
+ }
+
+ /**
+ * Checks if this instance is being used to print a PDF.
+ */
+ function isPrintingPDF() {
+
+ return ( /print-pdf/gi ).test( window.location.search );
+
+ }
+
+ /**
* Causes the address bar to hide on mobile devices,
* more vertical space ftw.
*/
@@ -560,7 +707,7 @@ var Reveal = (function(){
/**
* Wrap all links in 3D goodness.
*/
- function enable3DLinks() {
+ function enableRollingLinks() {
if( supports3DTransforms && !( 'msPerspective' in document.body.style ) ) {
var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a:not(.image)' );
@@ -585,7 +732,7 @@ var Reveal = (function(){
/**
* Unwrap all 3D links.
*/
- function disable3DLinks() {
+ function disableRollingLinks() {
var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a.roll' );
@@ -602,6 +749,90 @@ var Reveal = (function(){
}
/**
+ * Bind preview frame links.
+ */
+ function enablePreviewLinks( selector ) {
+
+ var anchors = toArray( document.querySelectorAll( selector ? selector : 'a' ) );
+
+ anchors.forEach( function( element ) {
+ if( /^(http|www)/gi.test( element.getAttribute( 'href' ) ) ) {
+ element.addEventListener( 'click', onPreviewLinkClicked, false );
+ }
+ } );
+
+ }
+
+ /**
+ * Unbind preview frame links.
+ */
+ function disablePreviewLinks() {
+
+ var anchors = toArray( document.querySelectorAll( 'a' ) );
+
+ anchors.forEach( function( element ) {
+ if( /^(http|www)/gi.test( element.getAttribute( 'href' ) ) ) {
+ element.removeEventListener( 'click', onPreviewLinkClicked, false );
+ }
+ } );
+
+ }
+
+ /**
+ * Opens a preview window for the target URL.
+ */
+ function openPreview( url ) {
+
+ closePreview();
+
+ dom.preview = document.createElement( 'div' );
+ dom.preview.classList.add( 'preview-link-overlay' );
+ dom.wrapper.appendChild( dom.preview );
+
+ dom.preview.innerHTML = [
+ '<header>',
+ '<a class="close" href="#"><span class="icon"></span></a>',
+ '<a class="external" href="'+ url +'" target="_blank"><span class="icon"></span></a>',
+ '</header>',
+ '<div class="spinner"></div>',
+ '<div class="viewport">',
+ '<iframe src="'+ url +'"></iframe>',
+ '</div>'
+ ].join('');
+
+ dom.preview.querySelector( 'iframe' ).addEventListener( 'load', function( event ) {
+ dom.preview.classList.add( 'loaded' );
+ }, false );
+
+ dom.preview.querySelector( '.close' ).addEventListener( 'click', function( event ) {
+ closePreview();
+ event.preventDefault();
+ }, false );
+
+ dom.preview.querySelector( '.external' ).addEventListener( 'click', function( event ) {
+ closePreview();
+ }, false );
+
+ setTimeout( function() {
+ dom.preview.classList.add( 'visible' );
+ }, 1 );
+
+ }
+
+ /**
+ * Closes the iframe preview window.
+ */
+ function closePreview() {
+
+ if( dom.preview ) {
+ dom.preview.setAttribute( 'src', '' );
+ dom.preview.parentNode.removeChild( dom.preview );
+ dom.preview = null;
+ }
+
+ }
+
+ /**
* Return a sorted fragments list, ordered by an increasing
* "data-fragment-index" attribute.
*
@@ -639,7 +870,7 @@ var Reveal = (function(){
*/
function layout() {
- if( dom.wrapper ) {
+ if( dom.wrapper && !isPrintingPDF() ) {
// Available space to scale within
var availableWidth = dom.wrapper.offsetWidth,
@@ -707,7 +938,7 @@ var Reveal = (function(){
slide.style.top = 0;
}
else {
- slide.style.top = Math.max( - ( slide.offsetHeight / 2 ) - 20, -slideHeight / 2 ) + 'px';
+ slide.style.top = Math.max( - ( getAbsoluteHeight( slide ) / 2 ) - 20, -slideHeight / 2 ) + 'px';
}
}
else {
@@ -748,7 +979,10 @@ var Reveal = (function(){
function getPreviousVerticalIndex( stack ) {
if( typeof stack === 'object' && typeof stack.setAttribute === 'function' && stack.classList.contains( 'stack' ) ) {
- return parseInt( stack.getAttribute( 'data-previous-indexv' ) || 0, 10 );
+ // Prefer manually defined start-indexv
+ var attributeName = stack.hasAttribute( 'data-start-indexv' ) ? 'data-start-indexv' : 'data-previous-indexv';
+
+ return parseInt( stack.getAttribute( attributeName ) || 0, 10 );
}
return 0;
@@ -1128,7 +1362,8 @@ var Reveal = (function(){
}
// Dispatch an event if the slide changed
- if( indexh !== indexhBefore || indexv !== indexvBefore ) {
+ var slideChanged = ( indexh !== indexhBefore || indexv !== indexvBefore );
+ if( slideChanged ) {
dispatchEvent( 'slidechanged', {
'indexh': indexh,
'indexv': indexv,
@@ -1165,11 +1400,14 @@ var Reveal = (function(){
}
// Handle embedded content
- stopEmbeddedContent( previousSlide );
- startEmbeddedContent( currentSlide );
+ if( slideChanged ) {
+ stopEmbeddedContent( previousSlide );
+ startEmbeddedContent( currentSlide );
+ }
updateControls();
updateProgress();
+ updateBackground();
}
@@ -1193,8 +1431,12 @@ var Reveal = (function(){
// Start auto-sliding if it's enabled
cueAutoSlide();
+ // Re-create the slide backgrounds
+ createBackgrounds();
+
updateControls();
updateProgress();
+ updateBackground();
}
@@ -1251,6 +1493,9 @@ var Reveal = (function(){
element.classList.remove( 'present' );
element.classList.remove( 'future' );
+ // http://www.w3.org/html/wg/drafts/html/master/editing.html#the-hidden-attribute
+ element.setAttribute( 'hidden', '' );
+
if( i < index ) {
// Any element previous to index is given the 'past' class
element.classList.add( reverse ? 'future' : 'past' );
@@ -1268,6 +1513,7 @@ var Reveal = (function(){
// Mark the current slide as present
slides[index].classList.add( 'present' );
+ slides[index].removeAttribute( 'hidden' );
// If this slide has a state associated with it, add it
// onto the current state of the deck
@@ -1400,6 +1646,37 @@ var Reveal = (function(){
}
/**
+ * Updates the background elements to reflect the current
+ * slide.
+ */
+ function updateBackground() {
+
+ // Update the classes of all backgrounds to match the
+ // states of their slides (past/present/future)
+ toArray( dom.background.childNodes ).forEach( function( backgroundh, h ) {
+
+ // Reverse past/future classes when in RTL mode
+ var horizontalPast = config.rtl ? 'future' : 'past',
+ horizontalFuture = config.rtl ? 'past' : 'future';
+
+ backgroundh.className = 'slide-background ' + ( h < indexh ? horizontalPast : h > indexh ? horizontalFuture : 'present' );
+
+ toArray( backgroundh.childNodes ).forEach( function( backgroundv, v ) {
+
+ backgroundv.className = 'slide-background ' + ( v < indexv ? 'past' : v > indexv ? 'future' : 'present' );
+
+ } );
+
+ } );
+
+ // Allow the first background to apply without transition
+ setTimeout( function() {
+ dom.background.classList.remove( 'no-transition' );
+ }, 1 );
+
+ }
+
+ /**
* Determine what available routes there are for navigation.
*
* @return {Object} containing four booleans: left/right/up/down
@@ -1629,10 +1906,18 @@ var Reveal = (function(){
var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment:not(.visible)' ) );
if( fragments.length ) {
- fragments[0].classList.add( 'visible' );
+ // Find the index of the next fragment
+ var index = fragments[0].getAttribute( 'data-fragment-index' );
+
+ // Find all fragments with the same index
+ fragments = currentSlide.querySelectorAll( '.fragment[data-fragment-index="'+ index +'"]' );
- // Notify subscribers of the change
- dispatchEvent( 'fragmentshown', { fragment: fragments[0] } );
+ toArray( fragments ).forEach( function( element ) {
+ element.classList.add( 'visible' );
+
+ // Notify subscribers of the change
+ dispatchEvent( 'fragmentshown', { fragment: element } );
+ } );
updateControls();
return true;
@@ -1655,10 +1940,18 @@ var Reveal = (function(){
var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment.visible' ) );
if( fragments.length ) {
- fragments[ fragments.length - 1 ].classList.remove( 'visible' );
+ // Find the index of the previous fragment
+ var index = fragments[ fragments.length - 1 ].getAttribute( 'data-fragment-index' );
- // Notify subscribers of the change
- dispatchEvent( 'fragmenthidden', { fragment: fragments[ fragments.length - 1 ] } );
+ // Find all fragments with the same index
+ fragments = currentSlide.querySelectorAll( '.fragment[data-fragment-index="'+ index +'"]' );
+
+ toArray( fragments ).forEach( function( f ) {
+ f.classList.remove( 'visible' );
+
+ // Notify subscribers of the change
+ dispatchEvent( 'fragmenthidden', { fragment: f } );
+ } );
updateControls();
return true;
@@ -1805,40 +2098,75 @@ var Reveal = (function(){
// keyboard modifier key is present
if( hasFocus || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
- var triggered = true;
-
- // while paused only allow "unpausing" keyboard events (b and .)
+ // While paused only allow "unpausing" keyboard events (b and .)
if( isPaused() && [66,190,191].indexOf( event.keyCode ) === -1 ) {
return false;
}
- 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;
- // b, period, Logitech presenter tools "black screen" button
- case 66: case 190: case 191: togglePause(); break;
- // f
- case 70: enterFullscreen(); break;
- default:
- triggered = 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 ];
+
+ // Calback function
+ 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;
+ // b, period, Logitech presenter tools "black screen" button
+ case 66: case 190: case 191: togglePause(); break;
+ // f
+ case 70: enterFullscreen(); break;
+ default:
+ triggered = false;
+ }
+
}
// If the input resulted in a triggered action we should prevent
@@ -2098,6 +2426,20 @@ var Reveal = (function(){
}
+ /**
+ * 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();
+ }
+
+ }
+
// --------------------------------------------------------------------//
// ------------------------------- API --------------------------------//
@@ -2226,4 +2568,4 @@ var Reveal = (function(){
}
};
-})(); \ No newline at end of file
+})();