optionally display pacing advice based on slide timings
Add an option to display advice on whether the current pace of the presentation is on track for the right timing (shown as green), and if not, whether the presenter should speed up (shown as red) or has the luxury of slowing down (blue). The pacing timer can be enabled by configuring by the `defaultTiming` parameter in the `Reveal` configuration block, which specifies the number of seconds per slide. 120 can be a reasonable rule of thumb. Timings can also be given per slide `<section>` by setting the `data-timing` attribute. Both values are in numbers of seconds. When the option is enabled, clicking on the timers will reset the timer to the beginning of the current slide, i.e. as if pacing was perfectly on track, not to zero as if the presentation had just begun.
This commit is contained in:
parent
921a605567
commit
715cf0ba11
@ -187,6 +187,9 @@ Reveal.initialize({
|
|||||||
// Display a presentation progress bar
|
// Display a presentation progress bar
|
||||||
progress: true,
|
progress: true,
|
||||||
|
|
||||||
|
// Set default timing of 2 minutes per slide
|
||||||
|
defaultTiming: 120,
|
||||||
|
|
||||||
// Display the page number of the current slide
|
// Display the page number of the current slide
|
||||||
slideNumber: false,
|
slideNumber: false,
|
||||||
|
|
||||||
@ -967,12 +970,15 @@ Notes are only visible to the speaker inside of the speaker view. If you wish to
|
|||||||
|
|
||||||
When `showNotes` is enabled notes are also included when you [export to PDF](https://github.com/hakimel/reveal.js#pdf-export). By default, notes are printed in a semi-transparent box on top of the slide. If you'd rather print them on a separate page after the slide, set `showNotes: "separate-page"`.
|
When `showNotes` is enabled notes are also included when you [export to PDF](https://github.com/hakimel/reveal.js#pdf-export). By default, notes are printed in a semi-transparent box on top of the slide. If you'd rather print them on a separate page after the slide, set `showNotes: "separate-page"`.
|
||||||
|
|
||||||
#### Speaker notes clock and timer
|
#### Speaker notes clock and timers
|
||||||
|
|
||||||
The speaker notes window will also show:
|
The speaker notes window will also show:
|
||||||
|
|
||||||
- Time elapsed since the beginning of the presentation. If you hover the mouse above this section, a timer reset button will appear.
|
- Time elapsed since the beginning of the presentation. If you hover the mouse above this section, a timer reset button will appear.
|
||||||
- Current wall-clock time
|
- Current wall-clock time
|
||||||
|
- (Optionally) a pacing timer which indicates whether the current pace of the presentation is on track for the right timing (shown in green), and if not, whether the presenter should speed up (shown in red) or has the luxury of slowing down (blue).
|
||||||
|
|
||||||
|
The pacing timer can be enabled by configuring by the `defaultTiming` parameter in the `Reveal` configuration block, which specifies the number of seconds per slide. 120 can be a reasonable rule of thumb. Timings can also be given per slide `<section>` by setting the `data-timing` attribute. Both values are in numbers of seconds.
|
||||||
|
|
||||||
|
|
||||||
## Server Side Speaker Notes
|
## Server Side Speaker Notes
|
||||||
|
@ -82,6 +82,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.speaker-controls-time .label,
|
.speaker-controls-time .label,
|
||||||
|
.speaker-controls-pace .label,
|
||||||
.speaker-controls-notes .label {
|
.speaker-controls-notes .label {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
@ -90,7 +91,7 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.speaker-controls-time {
|
.speaker-controls-time, .speaker-controls-pace {
|
||||||
border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );
|
border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
padding: 10px 16px;
|
padding: 10px 16px;
|
||||||
@ -111,6 +112,13 @@
|
|||||||
.speaker-controls-time .timer,
|
.speaker-controls-time .timer,
|
||||||
.speaker-controls-time .clock {
|
.speaker-controls-time .clock {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.speaker-controls-time .timer,
|
||||||
|
.speaker-controls-time .clock,
|
||||||
|
.speaker-controls-time .pacing .hours-value,
|
||||||
|
.speaker-controls-time .pacing .minutes-value,
|
||||||
|
.speaker-controls-time .pacing .seconds-value {
|
||||||
font-size: 1.9em;
|
font-size: 1.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,6 +135,18 @@
|
|||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.speaker-controls-time .pacing.ahead {
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.speaker-controls-time .pacing.on-track {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.speaker-controls-time .pacing.behind {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
.speaker-controls-notes {
|
.speaker-controls-notes {
|
||||||
padding: 10px 16px;
|
padding: 10px 16px;
|
||||||
}
|
}
|
||||||
@ -276,6 +296,12 @@
|
|||||||
<span class="hours-value">00</span><span class="minutes-value">:00</span><span class="seconds-value">:00</span>
|
<span class="hours-value">00</span><span class="minutes-value">:00</span><span class="seconds-value">:00</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="clear"></div>
|
<div class="clear"></div>
|
||||||
|
|
||||||
|
<h4 class="label pacing-title" style="display: none">Pacing</h4>
|
||||||
|
<div class="pacing" style="display: none">
|
||||||
|
<span class="hours-value">00</span><span class="minutes-value">:00</span><span class="seconds-value">:00</span>
|
||||||
|
to finish current slide
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="speaker-controls-notes hidden">
|
<div class="speaker-controls-notes hidden">
|
||||||
@ -450,6 +476,47 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTimings() {
|
||||||
|
|
||||||
|
var slides = Reveal.getSlides();
|
||||||
|
var defaultTiming = Reveal.getConfig().defaultTiming;
|
||||||
|
if (defaultTiming == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var timings = [];
|
||||||
|
for ( var i in slides ) {
|
||||||
|
var slide = slides[i];
|
||||||
|
var timing = defaultTiming;
|
||||||
|
if( slide.hasAttribute( 'data-timing' )) {
|
||||||
|
var t = slide.getAttribute( 'data-timing' );
|
||||||
|
timing = parseInt(t);
|
||||||
|
if( isNaN(timing) ) {
|
||||||
|
console.warn("Could not parse timing '" + t + "' of slide " + i + "; using default of " + defaultTiming);
|
||||||
|
timing = defaultTiming;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timings.push(timing);
|
||||||
|
}
|
||||||
|
return timings;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the number of seconds allocated for presenting
|
||||||
|
* all slides up to and including this one.
|
||||||
|
*/
|
||||||
|
function getTimeAllocated(timings) {
|
||||||
|
|
||||||
|
var slides = Reveal.getSlides();
|
||||||
|
var allocated = 0;
|
||||||
|
var currentSlide = Reveal.getSlidePastCount();
|
||||||
|
for (var i in slides.slice(0, currentSlide + 1)) {
|
||||||
|
allocated += timings[i];
|
||||||
|
}
|
||||||
|
return allocated;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the timer and clock and start updating them
|
* Create the timer and clock and start updating them
|
||||||
* at an interval.
|
* at an interval.
|
||||||
@ -461,14 +528,26 @@
|
|||||||
clockEl = timeEl.querySelector( '.clock-value' ),
|
clockEl = timeEl.querySelector( '.clock-value' ),
|
||||||
hoursEl = timeEl.querySelector( '.hours-value' ),
|
hoursEl = timeEl.querySelector( '.hours-value' ),
|
||||||
minutesEl = timeEl.querySelector( '.minutes-value' ),
|
minutesEl = timeEl.querySelector( '.minutes-value' ),
|
||||||
secondsEl = timeEl.querySelector( '.seconds-value' );
|
secondsEl = timeEl.querySelector( '.seconds-value' ),
|
||||||
|
pacingTitleEl = timeEl.querySelector( '.pacing-title' ),
|
||||||
|
pacingEl = timeEl.querySelector( '.pacing' ),
|
||||||
|
pacingHoursEl = pacingEl.querySelector( '.hours-value' ),
|
||||||
|
pacingMinutesEl = pacingEl.querySelector( '.minutes-value' ),
|
||||||
|
pacingSecondsEl = pacingEl.querySelector( '.seconds-value' );
|
||||||
|
|
||||||
|
var timings = getTimings();
|
||||||
|
if (timings !== null) {
|
||||||
|
pacingTitleEl.style.removeProperty('display');
|
||||||
|
pacingEl.style.removeProperty('display');
|
||||||
|
}
|
||||||
|
|
||||||
function _displayTime( hrEl, minEl, secEl, time) {
|
function _displayTime( hrEl, minEl, secEl, time) {
|
||||||
|
|
||||||
var sign = Math.sign(time) == -1 ? "-" : "";
|
var sign = Math.sign(time) == -1 ? "-" : "";
|
||||||
time = Math.abs(Math.round(time / 1000));
|
time = Math.abs(Math.round(time / 1000));
|
||||||
var seconds = time % 60;
|
var seconds = time % 60;
|
||||||
var minutes = ( time / 60 ) % 60 ;
|
var minutes = Math.floor( time / 60 ) % 60 ;
|
||||||
var hours = time / ( 60 * 60 ) ;
|
var hours = Math.floor( time / ( 60 * 60 )) ;
|
||||||
hrEl.innerHTML = sign + zeroPadInteger( hours );
|
hrEl.innerHTML = sign + zeroPadInteger( hours );
|
||||||
if (hours == 0) {
|
if (hours == 0) {
|
||||||
hrEl.classList.add( 'mute' );
|
hrEl.classList.add( 'mute' );
|
||||||
@ -495,6 +574,28 @@
|
|||||||
|
|
||||||
clockEl.innerHTML = now.toLocaleTimeString( 'en-US', { hour12: true, hour: '2-digit', minute:'2-digit' } );
|
clockEl.innerHTML = now.toLocaleTimeString( 'en-US', { hour12: true, hour: '2-digit', minute:'2-digit' } );
|
||||||
_displayTime( hoursEl, minutesEl, secondsEl, diff );
|
_displayTime( hoursEl, minutesEl, secondsEl, diff );
|
||||||
|
if (timings !== null) {
|
||||||
|
_updatePacing(diff);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function _updatePacing(diff) {
|
||||||
|
|
||||||
|
var slideEndTiming = getTimeAllocated(timings) * 1000;
|
||||||
|
var currentSlide = Reveal.getSlidePastCount();
|
||||||
|
var currentSlideTiming = timings[currentSlide] * 1000;
|
||||||
|
var timeLeftCurrentSlide = slideEndTiming - diff;
|
||||||
|
if (timeLeftCurrentSlide < 0) {
|
||||||
|
pacingEl.className = 'pacing behind';
|
||||||
|
}
|
||||||
|
else if (timeLeftCurrentSlide < currentSlideTiming) {
|
||||||
|
pacingEl.className = 'pacing on-track';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pacingEl.className = 'pacing ahead';
|
||||||
|
}
|
||||||
|
_displayTime( pacingHoursEl, pacingMinutesEl, pacingSecondsEl, timeLeftCurrentSlide );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -504,9 +605,26 @@
|
|||||||
// Then update every second
|
// Then update every second
|
||||||
setInterval( _updateTimer, 1000 );
|
setInterval( _updateTimer, 1000 );
|
||||||
|
|
||||||
timeEl.addEventListener( 'click', function() {
|
function _resetTimer() {
|
||||||
|
|
||||||
|
if (timings == null) {
|
||||||
start = new Date();
|
start = new Date();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Reset timer to beginning of current slide
|
||||||
|
var slideEndTiming = getTimeAllocated(timings) * 1000;
|
||||||
|
var currentSlide = Reveal.getSlidePastCount();
|
||||||
|
var currentSlideTiming = timings[currentSlide] * 1000;
|
||||||
|
var previousSlidesTiming = slideEndTiming - currentSlideTiming;
|
||||||
|
var now = new Date();
|
||||||
|
start = new Date(now.getTime() - previousSlidesTiming);
|
||||||
|
}
|
||||||
_updateTimer();
|
_updateTimer();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
timeEl.addEventListener( 'click', function() {
|
||||||
|
_resetTimer();
|
||||||
return false;
|
return false;
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user