1
0
mirror of https://github.com/hakimel/reveal.js.git synced 2025-08-04 21:57:59 +02:00

reader mode supports scroll snapping, sticky pages with scroll triggers are always full height

This commit is contained in:
Hakim El Hattab
2023-09-21 13:35:49 +02:00
parent f0950ba9ae
commit e49e89a557
9 changed files with 72 additions and 48 deletions

View File

@@ -262,10 +262,19 @@ export default {
// triggered animations
view: null,
// When the presentation is in reader mode, this controls whether each
// page should be as tall as the presentation viewport. Set this to false
// for a more compact layout with multiple slides visible at a time.
readerFullPageHeight: true,
// Adjusts the height of each slide in reader mode
// - full: Each slide is as tall as the viewport
// - compact: Slides are as small as possible, allowing multiple slides
// to be visible in parallel on tall devices
readerLayout: 'full',
// Control how scroll snapping works in reader mode.
// - false: No snapping, scrolling is continuous
// - proximity: Snap when close to a slide
// - mandatory: Always snap to the closest slide
//
// Only applies to presentations in reader mode.
readerScrollSnap: 'proximity',
// The maximum number of pages a single slide can expand onto when printing
// to PDF, unlimited by default

View File

@@ -62,9 +62,6 @@ export default class Reader {
page.className = 'reader-page';
pageElements.push( page );
slide.style.width = slideWidth + 'px';
// slide.style.height = slideHeight + 'px';
// Copy the presentation-wide background to each individual
// page when printing
if( presentationBackground ) {
@@ -143,21 +140,29 @@ export default class Reader {
}
generatePageMap() {
/**
* Updates our reader pages to match the latest configuration and
* presentation size.
*/
sync() {
const viewportElement = this.Reveal.getViewportElement();
const viewportHeight = viewportElement.offsetHeight;
const config = this.Reveal.getConfig();
const slideSize = this.Reveal.getComputedSlideSize( window.innerWidth, window.innerHeight );
const scale = this.Reveal.getScale();
const fullPageHeight = this.Reveal.getConfig().readerFullPageHeight;
const readerLayout = config.readerLayout;
const pageHeight = fullPageHeight === true ? viewportHeight : slideSize.height * scale;
const viewportElement = this.Reveal.getViewportElement();
const viewportHeight = viewportElement.offsetHeight;
const compactHeight = slideSize.height * scale;
const pageHeight = readerLayout === 'full' ? viewportHeight : compactHeight;
// The height that needs to be scrolled between scroll triggers
const scrollTriggerHeight = viewportHeight / 2;
viewportElement.style.setProperty( '--page-height', pageHeight + 'px' );
viewportElement.style.scrollSnapType = typeof config.readerScrollSnap === 'string' ?
`y ${config.readerScrollSnap}` : '';
const pageElements = Array.from( this.Reveal.getRevealElement().querySelectorAll( '.reader-page' ) );
@@ -168,36 +173,18 @@ export default class Reader {
slideElement: pageElement.querySelector( 'section' ),
backgroundElement: pageElement.querySelector( '.slide-background' ),
top: pageElement.offsetTop,
pageHeight: pageHeight,
scrollTriggers: []
};
page.slideElement.style.width = slideSize.width + 'px';
page.slideElement.style.height = config.center === true ? '' : slideSize.height + 'px';
// Each fragment 'group' is an array containing one or more
// fragments. Multiple fragments that appear at the same time
// are part of the same group.
page.fragments = this.Reveal.fragments.sort( pageElement.querySelectorAll( '.fragment:not(.disabled)' ) );
page.fragmentGroups = this.Reveal.fragments.sort( pageElement.querySelectorAll( '.fragment' ), true );
// The amount of empty scrollable space that has been append
page.scrollPadding = scrollTriggerHeight * Math.max( page.fragmentGroups.length - 1, 0 );
// This variable is used to pad the height of our page in CSS
page.pageElement.style.setProperty( '--page-scroll-padding', page.scrollPadding + 'px' );
// The total height including scrollable space
page.totalHeight = page.pageHeight + page.scrollPadding;
page.bottom = page.top + page.totalHeight;
// If this is a sticky page, stick it to the vertical center
if( page.scrollPadding > 0 ) {
page.stickyElement.style.position = 'sticky';
page.stickyElement.style.top = Math.max( ( viewportHeight - page.pageHeight ) / 2, 0 ) + 'px';
}
else {
page.stickyElement.style.position = 'relative';
}
// Create scroll triggers that show/hide fragments
if( page.fragmentGroups.length ) {
const segmentSize = 1 / ( page.fragmentGroups.length + 1 );
@@ -211,10 +198,45 @@ export default class Reader {
fragmentIndex: i
}))
);
}
// Add scroll padding based on how many scroll triggers we have
page.scrollPadding = scrollTriggerHeight * page.scrollTriggers.length;
// In the compact layout, only slides with scroll triggers cover the
// full viewport height. This helps avoid empty gaps before or after
// a sticky slide.
if( readerLayout === 'compact' && page.scrollTriggers.length > 0 ) {
page.pageHeight = viewportHeight;
page.pageElement.style.setProperty( '--page-height', viewportHeight + 'px' );
}
else {
page.pageHeight = pageHeight;
page.pageElement.style.removeProperty( '--page-height' );
}
page.pageElement.style.scrollSnapAlign = page.pageHeight < viewportHeight ? 'center' : 'start';
// This variable is used to pad the height of our page in CSS
page.pageElement.style.setProperty( '--page-scroll-padding', page.scrollPadding + 'px' );
// The total height including scrollable space
page.totalHeight = page.pageHeight + page.scrollPadding;
page.bottom = page.top + page.totalHeight;
// If this is a sticky page, stick it to the vertical center
if( page.scrollTriggers.length > 0 ) {
page.stickyElement.style.position = 'sticky';
page.stickyElement.style.top = Math.max( ( viewportHeight - page.pageHeight ) / 2, 0 ) + 'px';
// Make this page freeze at the vertical center of the viewport
page.top -= ( viewportHeight - page.pageHeight ) / 2;
}
else {
page.stickyElement.style.position = 'relative';
}
return page;
} );
@@ -223,15 +245,7 @@ export default class Reader {
layout() {
this.generatePageMap();
const scale = this.Reveal.getScale();
this.pages.forEach( ( page ) => {
page.slideElement.style.transform = `scale(${scale}) translate(-50%, -50%)`;
} );
this.sync();
this.onScroll();
}

View File

@@ -2744,7 +2744,7 @@ export default function( revealElement, options ) {
isFocused: focus.isFocused.bind( focus ),
isReaderMode: reader.isActive.bind( reader ),
isPrintMode: print.isActive.bind( print ),
isPrinting: print.isActive.bind( print ),
// Checks if reveal.js has been loaded and is ready for use
isReady: () => ready,