Compare commits
10 Commits
b03a94c6fc
...
363bb3e6e6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
363bb3e6e6 | ||
|
|
c9d6785df2 | ||
|
|
8bb6674303 | ||
|
|
1427059378 | ||
|
|
4cf184924d | ||
|
|
e8cd04da83 | ||
|
|
5412639a54 | ||
|
|
8003efe030 | ||
|
|
eb95b14531 | ||
|
|
dfc5690c6d |
@@ -9,9 +9,10 @@
|
||||
@import "../template/settings";
|
||||
// ---------------------------------------------
|
||||
|
||||
|
||||
|
||||
// Include theme-specific fonts
|
||||
@import url(./fonts/league-gothic/league-gothic.css);
|
||||
@import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic);
|
||||
|
||||
$systemFontsSansSerif: -apple-system,
|
||||
BlinkMacSystemFont,
|
||||
avenir next,
|
||||
|
||||
4
dist/reveal.esm.js
vendored
4
dist/reveal.esm.js
vendored
File diff suppressed because one or more lines are too long
2
dist/reveal.esm.js.map
vendored
2
dist/reveal.esm.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/reveal.js
vendored
4
dist/reveal.js
vendored
File diff suppressed because one or more lines are too long
2
dist/reveal.js.map
vendored
2
dist/reveal.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/theme/dracula.css
vendored
2
dist/theme/dracula.css
vendored
@@ -2,6 +2,8 @@
|
||||
* Dracula Dark theme for reveal.js.
|
||||
* Based on https://draculatheme.com
|
||||
*/
|
||||
@import url(./fonts/league-gothic/league-gothic.css);
|
||||
@import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic);
|
||||
/**
|
||||
* Dracula colors by Zeno Rocha
|
||||
* https://draculatheme.com/contribute
|
||||
|
||||
@@ -33,10 +33,10 @@
|
||||
|
||||
<section>
|
||||
<h2>Video</h2>
|
||||
<video src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4" data-autoplay></video>
|
||||
<video src="https://static.slid.es/site/homepage/v1/homepage-video-editor.mp4" data-autoplay></video>
|
||||
</section>
|
||||
|
||||
<section data-background-video="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4">
|
||||
<section data-background-video="https://static.slid.es/site/homepage/v1/homepage-video-editor.mp4">
|
||||
<h2>Background Video</h2>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -168,6 +168,9 @@ export default {
|
||||
// - false: All iframes with data-src will be loaded only when visible
|
||||
preloadIframes: null,
|
||||
|
||||
// Prevent embedded iframes from automatically focusing on themselves
|
||||
preventIframeAutoFocus: true,
|
||||
|
||||
// Can be used to globally disable auto-animation
|
||||
autoAnimate: true,
|
||||
|
||||
|
||||
7
js/controllers/controls.js
vendored
7
js/controllers/controls.js
vendored
@@ -83,9 +83,10 @@ export default class Controls {
|
||||
let pointerEvents = [ 'touchstart', 'click' ];
|
||||
|
||||
// Only support touch for Android, fixes double navigations in
|
||||
// stock browser
|
||||
// stock browser. Use touchend for it to be considered a valid
|
||||
// user interaction (so we're allowed to autoplay media).
|
||||
if( isAndroid ) {
|
||||
pointerEvents = [ 'touchstart' ];
|
||||
pointerEvents = [ 'touchend' ];
|
||||
}
|
||||
|
||||
pointerEvents.forEach( eventName => {
|
||||
@@ -102,7 +103,7 @@ export default class Controls {
|
||||
|
||||
unbind() {
|
||||
|
||||
[ 'touchstart', 'click' ].forEach( eventName => {
|
||||
[ 'touchstart', 'touchend', 'click' ].forEach( eventName => {
|
||||
this.controlsLeft.forEach( el => el.removeEventListener( eventName, this.onNavigateLeftClicked, false ) );
|
||||
this.controlsRight.forEach( el => el.removeEventListener( eventName, this.onNavigateRightClicked, false ) );
|
||||
this.controlsUp.forEach( el => el.removeEventListener( eventName, this.onNavigateUpClicked, false ) );
|
||||
|
||||
@@ -9,11 +9,15 @@ import fitty from 'fitty';
|
||||
*/
|
||||
export default class SlideContent {
|
||||
|
||||
allowedToPlay = true;
|
||||
|
||||
constructor( Reveal ) {
|
||||
|
||||
this.Reveal = Reveal;
|
||||
|
||||
this.startEmbeddedIframe = this.startEmbeddedIframe.bind( this );
|
||||
this.preventIframeAutoFocus = this.preventIframeAutoFocus.bind( this );
|
||||
this.ensureMobileMediaPlaying = this.ensureMobileMediaPlaying.bind( this );
|
||||
|
||||
}
|
||||
|
||||
@@ -51,14 +55,25 @@ export default class SlideContent {
|
||||
load( slide, options = {} ) {
|
||||
|
||||
// Show the slide element
|
||||
slide.style.display = this.Reveal.getConfig().display;
|
||||
const displayValue = this.Reveal.getConfig().display;
|
||||
if( displayValue.includes('!important') ) {
|
||||
const value = displayValue.replace(/\s*!important\s*$/, '').trim();
|
||||
slide.style.setProperty('display', value, 'important');
|
||||
} else {
|
||||
slide.style.display = displayValue;
|
||||
}
|
||||
|
||||
// Media elements with data-src attributes
|
||||
// Media and iframe elements with data-src attributes
|
||||
queryAll( slide, 'img[data-src], video[data-src], audio[data-src], iframe[data-src]' ).forEach( element => {
|
||||
if( element.tagName !== 'IFRAME' || this.shouldPreload( element ) ) {
|
||||
const isIframe = element.tagName === 'IFRAME';
|
||||
if( !isIframe || this.shouldPreload( element ) ) {
|
||||
element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
|
||||
element.setAttribute( 'data-lazy-loaded', '' );
|
||||
element.removeAttribute( 'data-src' );
|
||||
|
||||
if( isIframe ) {
|
||||
element.addEventListener( 'load', this.preventIframeAutoFocus );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
@@ -320,10 +335,16 @@ export default class SlideContent {
|
||||
else if( isMobile ) {
|
||||
let promise = el.play();
|
||||
|
||||
el.addEventListener( 'canplay', this.ensureMobileMediaPlaying );
|
||||
|
||||
// If autoplay does not work, ensure that the controls are visible so
|
||||
// that the viewer can start the media on their own
|
||||
if( promise && typeof promise.catch === 'function' && el.controls === false ) {
|
||||
promise.catch( () => {
|
||||
promise
|
||||
.then( () => {
|
||||
this.allowedToPlay = true;
|
||||
})
|
||||
.catch( () => {
|
||||
el.controls = true;
|
||||
|
||||
// Once the video does start playing, hide the controls again
|
||||
@@ -374,6 +395,40 @@ export default class SlideContent {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that an HTMLMediaElement is playing on mobile devices.
|
||||
*
|
||||
* This is a workaround for a bug in mobile Safari where
|
||||
* the media fails to display if many videos are started
|
||||
* at the same moment. When this happens, Mobile Safari
|
||||
* reports the video is playing, and the current time
|
||||
* advances, but nothing is visible.
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
ensureMobileMediaPlaying( event ) {
|
||||
|
||||
const el = event.target;
|
||||
|
||||
// Ignore this check incompatible browsers
|
||||
if( typeof el.getVideoPlaybackQuality !== 'function' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout( () => {
|
||||
|
||||
const playing = el.paused === false;
|
||||
const totalFrames = el.getVideoPlaybackQuality().totalVideoFrames;
|
||||
|
||||
if( playing && totalFrames === 0 ) {
|
||||
el.load();
|
||||
el.play();
|
||||
}
|
||||
|
||||
}, 1000 );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts playing an embedded video/audio element after
|
||||
* it has finished loading.
|
||||
@@ -389,7 +444,19 @@ export default class SlideContent {
|
||||
// Don't restart if media is already playing
|
||||
if( event.target.paused || event.target.ended ) {
|
||||
event.target.currentTime = 0;
|
||||
event.target.play();
|
||||
const promise = event.target.play();
|
||||
|
||||
if( promise && typeof promise.catch === 'function' ) {
|
||||
promise
|
||||
.then( () => {
|
||||
this.allowedToPlay = true;
|
||||
} )
|
||||
.catch( ( error ) => {
|
||||
if( error.name === 'NotAllowedError' ) {
|
||||
this.allowedToPlay = false;
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,6 +474,8 @@ export default class SlideContent {
|
||||
|
||||
let iframe = event.target;
|
||||
|
||||
this.preventIframeAutoFocus( event );
|
||||
|
||||
if( iframe && iframe.contentWindow ) {
|
||||
|
||||
let isAttachedToDOM = !!closest( event.target, 'html' ),
|
||||
@@ -461,12 +530,17 @@ export default class SlideContent {
|
||||
if( !el.hasAttribute( 'data-ignore' ) && typeof el.pause === 'function' ) {
|
||||
el.setAttribute('data-paused-by-reveal', '');
|
||||
el.pause();
|
||||
|
||||
if( isMobile ) {
|
||||
el.removeEventListener( 'canplay', this.ensureMobileMediaPlaying );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
// Generic postMessage API for non-lazy loaded iframes
|
||||
queryAll( element, 'iframe' ).forEach( el => {
|
||||
if( el.contentWindow ) el.contentWindow.postMessage( 'slide:stop', '*' );
|
||||
el.removeEventListener( 'load', this.preventIframeAutoFocus );
|
||||
el.removeEventListener( 'load', this.startEmbeddedIframe );
|
||||
});
|
||||
|
||||
@@ -497,4 +571,46 @@ export default class SlideContent {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether media playback is blocked by the browser. This
|
||||
* typically happens when media playback is initiated without a
|
||||
* direct user interaction.
|
||||
*/
|
||||
isNotAllowedToPlay() {
|
||||
|
||||
return !this.allowedToPlay;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents iframes from automatically focusing themselves.
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
preventIframeAutoFocus( event ) {
|
||||
|
||||
const iframe = event.target;
|
||||
|
||||
console.log(111)
|
||||
|
||||
if( iframe && this.Reveal.getConfig().preventIframeAutoFocus ) {
|
||||
|
||||
let elapsed = 0;
|
||||
const interval = 100;
|
||||
const maxTime = 1000;
|
||||
const checkFocus = () => {
|
||||
if( document.activeElement === iframe ) {
|
||||
document.activeElement.blur();
|
||||
} else if( elapsed < maxTime ) {
|
||||
elapsed += interval;
|
||||
setTimeout( checkFocus, interval );
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout( checkFocus, interval );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -216,6 +216,14 @@ export default class Touch {
|
||||
*/
|
||||
onTouchEnd( event ) {
|
||||
|
||||
// Media playback is only allowed as a direct result of a
|
||||
// user interaction. Some mobile devices do not consider a
|
||||
// 'touchmove' to be a direct user action. If this is the
|
||||
// case, we fall back to starting playback here instead.
|
||||
if( this.touchCaptured && this.Reveal.slideContent.isNotAllowedToPlay() ) {
|
||||
this.Reveal.startEmbeddedContent( this.Reveal.getCurrentSlide() );
|
||||
}
|
||||
|
||||
this.touchCaptured = false;
|
||||
|
||||
}
|
||||
|
||||
41
js/reveal.js
41
js/reveal.js
@@ -394,7 +394,7 @@ export default function( revealElement, options ) {
|
||||
|
||||
// Text node
|
||||
if( node.nodeType === 3 ) {
|
||||
text += node.textContent;
|
||||
text += node.textContent.trim();
|
||||
}
|
||||
// Element node
|
||||
else if( node.nodeType === 1 ) {
|
||||
@@ -403,10 +403,25 @@ export default function( revealElement, options ) {
|
||||
let isDisplayHidden = window.getComputedStyle( node )['display'] === 'none';
|
||||
if( isAriaHidden !== 'true' && !isDisplayHidden ) {
|
||||
|
||||
// Capture alt text from img and video elements
|
||||
if( node.tagName === 'IMG' || node.tagName === 'VIDEO' ) {
|
||||
let altText = node.getAttribute( 'alt' );
|
||||
if( altText ) {
|
||||
text += ensurePunctuation( altText );
|
||||
}
|
||||
}
|
||||
|
||||
Array.from( node.childNodes ).forEach( child => {
|
||||
text += getStatusText( child );
|
||||
} );
|
||||
|
||||
// Add period after block-level text elements to improve
|
||||
// screen reader experience
|
||||
const textElements = ['P', 'DIV', 'UL', 'OL', 'LI', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE'];
|
||||
if( textElements.includes( node.tagName ) && text.trim() !== '' ) {
|
||||
text = ensurePunctuation( text );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -417,6 +432,22 @@ export default function( revealElement, options ) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures text ends with proper punctuation by adding a period
|
||||
* if it doesn't already end with punctuation.
|
||||
*/
|
||||
function ensurePunctuation( text ) {
|
||||
|
||||
const trimmedText = text.trim();
|
||||
|
||||
if( trimmedText === '' ) {
|
||||
return text;
|
||||
}
|
||||
|
||||
return !/[.!?]$/.test(trimmedText) ? trimmedText + '.' : trimmedText;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an unfortunate necessity. Some actions – such as
|
||||
* an input field being focused in an iframe or using the
|
||||
@@ -1782,14 +1813,16 @@ export default function( revealElement, options ) {
|
||||
|
||||
if( horizontalSlidesLength && typeof indexh !== 'undefined' ) {
|
||||
|
||||
const isOverview = overview.isActive();
|
||||
|
||||
// The number of steps away from the present slide that will
|
||||
// be visible
|
||||
let viewDistance = overview.isActive() ? 10 : config.viewDistance;
|
||||
let viewDistance = isOverview ? 10 : config.viewDistance;
|
||||
|
||||
// Shorten the view distance on devices that typically have
|
||||
// less resources
|
||||
if( Device.isMobile ) {
|
||||
viewDistance = overview.isActive() ? 6 : config.mobileViewDistance;
|
||||
viewDistance = isOverview ? 6 : config.mobileViewDistance;
|
||||
}
|
||||
|
||||
// All slides need to be visible when exporting to PDF
|
||||
@@ -1822,7 +1855,7 @@ export default function( revealElement, options ) {
|
||||
|
||||
if( verticalSlidesLength ) {
|
||||
|
||||
let oy = getPreviousVerticalIndex( horizontalSlide );
|
||||
let oy = isOverview ? 0 : getPreviousVerticalIndex( horizontalSlide );
|
||||
|
||||
for( let y = 0; y < verticalSlidesLength; y++ ) {
|
||||
let verticalSlide = verticalSlides[y];
|
||||
|
||||
Reference in New Issue
Block a user