aboutsummaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
Diffstat (limited to 'js')
-rw-r--r--js/reveal.js164
1 files changed, 127 insertions, 37 deletions
diff --git a/js/reveal.js b/js/reveal.js
index b591735..41000d6 100644
--- a/js/reveal.js
+++ b/js/reveal.js
@@ -454,6 +454,8 @@
loaded = true;
+ dom.wrapper.classList.add( 'ready' );
+
dispatchEvent( 'ready', {
'indexh': indexh,
'indexv': indexv,
@@ -556,6 +558,38 @@
}
/**
+ * Converts the given HTML element into a string of text
+ * that can be announced to a screen reader. Hidden
+ * elements are excluded.
+ */
+ function getStatusText( node ) {
+
+ var text = '';
+
+ // Text node
+ if( node.nodeType === 3 ) {
+ text += node.textContent;
+ }
+ // Element node
+ else if( node.nodeType === 1 ) {
+
+ var isAriaHidden = node.getAttribute( 'aria-hidden' );
+ var isDisplayHidden = window.getComputedStyle( node )['display'] === 'none';
+ if( isAriaHidden !== 'true' && !isDisplayHidden ) {
+
+ toArray( node.childNodes ).forEach( function( child ) {
+ text += getStatusText( child );
+ } );
+
+ }
+
+ }
+
+ return text;
+
+ }
+
+ /**
* Configures the presentation for printing to a static
* PDF.
*/
@@ -864,15 +898,15 @@
// If this slide has a background color, add a class that
// signals if it is light or dark. If the slide has no background
// color, no class will be set
- var computedBackgroundColor = window.getComputedStyle( element ).backgroundColor;
- if( computedBackgroundColor ) {
- var rgb = colorToRgb( computedBackgroundColor );
+ var computedBackgroundStyle = window.getComputedStyle( element );
+ if( computedBackgroundStyle && computedBackgroundStyle.backgroundColor ) {
+ var rgb = colorToRgb( computedBackgroundStyle.backgroundColor );
// Ignore fully transparent backgrounds. Some browsers return
// rgba(0,0,0,0) when reading the computed background color of
// an element with no background
if( rgb && rgb.a !== 0 ) {
- if( colorBrightness( computedBackgroundColor ) < 128 ) {
+ if( colorBrightness( computedBackgroundStyle.backgroundColor ) < 128 ) {
slide.classList.add( 'has-dark-background' );
}
else {
@@ -1272,6 +1306,42 @@
}
/**
+ * Find the closest parent that matches the given
+ * selector.
+ *
+ * @param {HTMLElement} target The child element
+ * @param {String} selector The CSS selector to match
+ * the parents against
+ *
+ * @return {HTMLElement} The matched parent or null
+ * if no matching parent was found
+ */
+ function closestParent( target, selector ) {
+
+ var parent = target.parentNode;
+
+ while( parent ) {
+
+ // There's some overhead doing this each time, we don't
+ // want to rewrite the element prototype but should still
+ // be enough to feature detect once at startup...
+ var matchesMethod = parent.matches || parent.matchesSelector || parent.msMatchesSelector;
+
+ // If we find a match, we're all set
+ if( matchesMethod && matchesMethod.call( parent, selector ) ) {
+ return parent;
+ }
+
+ // Keep searching
+ parent = parent.parentNode;
+
+ }
+
+ return null;
+
+ }
+
+ /**
* Converts various color input formats to an {r:0,g:0,b:0} object.
*
* @param {string} color The string representation of a color
@@ -2155,6 +2225,9 @@
// Query all horizontal slides in the deck
var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
+ // Abort if there are no slides
+ if( horizontalSlides.length === 0 ) return;
+
// If no vertical index is specified and the upcoming slide is a
// stack, resume at its previous vertical index
if( v === undefined && !isOverview() ) {
@@ -2271,7 +2344,7 @@
}
// Announce the current slide contents, for screen readers
- dom.statusDiv.textContent = currentSlide.textContent;
+ dom.statusDiv.textContent = getStatusText( currentSlide );
updateControls();
updateProgress();
@@ -2620,10 +2693,10 @@
* Updates the slide number div to reflect the current slide.
*
* 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
+ * "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() {
@@ -3109,34 +3182,46 @@
/**
* Start playback of any embedded content inside of
- * the targeted slide.
+ * the given element.
*
* @param {HTMLElement} slide
*/
- function startEmbeddedContent( slide ) {
+ function startEmbeddedContent( element ) {
- if( slide && !isSpeakerNotes() ) {
+ if( element && !isSpeakerNotes() ) {
// Restart GIFs
- toArray( slide.querySelectorAll( 'img[src$=".gif"]' ) ).forEach( function( el ) {
+ toArray( element.querySelectorAll( 'img[src$=".gif"]' ) ).forEach( function( el ) {
// Setting the same unchanged source like this was confirmed
// to work in Chrome, FF & Safari
el.setAttribute( 'src', el.getAttribute( 'src' ) );
} );
// HTML5 media elements
- toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
+ toArray( element.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
+ if( closestParent( el, '.fragment' ) && !closestParent( el, '.fragment.visible' ) ) {
+ return;
+ }
+
if( el.hasAttribute( 'data-autoplay' ) && typeof el.play === 'function' ) {
el.play();
}
} );
// Normal iframes
- toArray( slide.querySelectorAll( 'iframe[src]' ) ).forEach( function( el ) {
+ toArray( element.querySelectorAll( 'iframe[src]' ) ).forEach( function( el ) {
+ if( closestParent( el, '.fragment' ) && !closestParent( el, '.fragment.visible' ) ) {
+ return;
+ }
+
startEmbeddedIframe( { target: el } );
} );
// Lazy loading iframes
- toArray( slide.querySelectorAll( 'iframe[data-src]' ) ).forEach( function( el ) {
+ toArray( element.querySelectorAll( 'iframe[data-src]' ) ).forEach( function( el ) {
+ if( closestParent( el, '.fragment' ) && !closestParent( el, '.fragment.visible' ) ) {
+ return;
+ }
+
if( el.getAttribute( 'src' ) !== el.getAttribute( 'data-src' ) ) {
el.removeEventListener( 'load', startEmbeddedIframe ); // remove first to avoid dupes
el.addEventListener( 'load', startEmbeddedIframe );
@@ -3157,17 +3242,21 @@
var iframe = event.target;
- // YouTube postMessage API
- if( /youtube\.com\/embed\//.test( iframe.getAttribute( 'src' ) ) && iframe.hasAttribute( 'data-autoplay' ) ) {
- iframe.contentWindow.postMessage( '{"event":"command","func":"playVideo","args":""}', '*' );
- }
- // Vimeo postMessage API
- else if( /player\.vimeo\.com\//.test( iframe.getAttribute( 'src' ) ) && iframe.hasAttribute( 'data-autoplay' ) ) {
- iframe.contentWindow.postMessage( '{"method":"play"}', '*' );
- }
- // Generic postMessage API
- else {
- iframe.contentWindow.postMessage( 'slide:start', '*' );
+ if( iframe && iframe.contentWindow ) {
+
+ // YouTube postMessage API
+ if( /youtube\.com\/embed\//.test( iframe.getAttribute( 'src' ) ) && iframe.hasAttribute( 'data-autoplay' ) ) {
+ iframe.contentWindow.postMessage( '{"event":"command","func":"playVideo","args":""}', '*' );
+ }
+ // Vimeo postMessage API
+ else if( /player\.vimeo\.com\//.test( iframe.getAttribute( 'src' ) ) && iframe.hasAttribute( 'data-autoplay' ) ) {
+ iframe.contentWindow.postMessage( '{"method":"play"}', '*' );
+ }
+ // Generic postMessage API
+ else {
+ iframe.contentWindow.postMessage( 'slide:start', '*' );
+ }
+
}
}
@@ -3705,10 +3794,11 @@
element.classList.remove( 'current-fragment' );
// Announce the fragments one by one to the Screen Reader
- dom.statusDiv.textContent = element.textContent;
+ dom.statusDiv.textContent = getStatusText( element );
if( i === index ) {
element.classList.add( 'current-fragment' );
+ startEmbeddedContent( element );
}
}
// Hidden fragments
@@ -3718,7 +3808,6 @@
element.classList.remove( 'current-fragment' );
}
-
} );
if( fragmentsHidden.length ) {
@@ -3806,12 +3895,13 @@
// If there are media elements with data-autoplay,
// automatically set the autoSlide duration to the
// length of that media. Not applicable if the slide
- // is divided up into fragments.
+ // is divided up into fragments.
+ // playbackRate is accounted for in the duration.
if( currentSlide.querySelectorAll( '.fragment' ).length === 0 ) {
toArray( currentSlide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
if( el.hasAttribute( 'data-autoplay' ) ) {
- if( autoSlide && el.duration * 1000 > autoSlide ) {
- autoSlide = ( el.duration * 1000 ) + 1000;
+ if( autoSlide && (el.duration * 1000 / el.playbackRate ) > autoSlide ) {
+ autoSlide = ( el.duration * 1000 / el.playbackRate ) + 1000;
}
}
} );
@@ -4056,8 +4146,8 @@
// keyboard modifier key is present
if( activeElementIsCE || activeElementIsInput || activeElementIsNotes || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
- // While paused only allow resume keyboard events; 'b', '.''
- var resumeKeyCodes = [66,190,191];
+ // While paused only allow resume keyboard events; 'b', 'v', '.'
+ var resumeKeyCodes = [66,86,190,191];
var key;
// Custom key bindings for togglePause should be able to resume
@@ -4129,8 +4219,8 @@
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;
+ // two-spot, semicolon, b, v, period, Logitech presenter tools "black screen" button
+ case 58: case 59: case 66: case 86: case 190: case 191: togglePause(); break;
// f
case 70: enterFullscreen(); break;
// a
@@ -4352,7 +4442,7 @@
if( delta > 0 ) {
navigateNext();
}
- else {
+ else if( delta < 0 ) {
navigatePrev();
}