From 75480b5bf43cb3fd542a535aec7f34d295179b71 Mon Sep 17 00:00:00 2001 From: Jeroen Hermans Date: Sun, 15 Nov 2020 21:46:33 +0100 Subject: [PATCH 1/8] Remove unused argument --- js/reveal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/reveal.js b/js/reveal.js index 67ad0b8b96b..727cc8e9f92 100644 --- a/js/reveal.js +++ b/js/reveal.js @@ -2290,7 +2290,7 @@ export default function( revealElement, options ) { // When looping is enabled `routes.down` is always available // so we need a separate check for when we've reached the // end of a stack and should move horizontally - if( routes.down && routes.right && config.loop && isLastVerticalSlide( currentSlide ) ) { + if( routes.down && routes.right && config.loop && isLastVerticalSlide() ) { routes.down = false; } From aaa7c02b5c7cc6a1697d90c786268cb72d0649be Mon Sep 17 00:00:00 2001 From: Jeroen Hermans Date: Sun, 15 Nov 2020 21:54:05 +0100 Subject: [PATCH 2/8] Prevent layout thrashing by status text --- js/reveal.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/js/reveal.js b/js/reveal.js index 727cc8e9f92..b3f81a165e7 100644 --- a/js/reveal.js +++ b/js/reveal.js @@ -1335,7 +1335,11 @@ export default function( revealElement, options ) { } // Announce the current slide contents to screen readers - announceStatus( getStatusText( currentSlide ) ); + // Use animation frame to prevent getComputedStyle in getStatusText + // from triggering layout mid-frame + requestAnimationFrame( function() { + announceStatus( getStatusText( currentSlide ) ); + }); progress.update(); controls.update(); From 3d701edc71145e42e361b6b7898406c4d300f762 Mon Sep 17 00:00:00 2001 From: Jeroen Hermans Date: Sun, 15 Nov 2020 22:01:50 +0100 Subject: [PATCH 3/8] Limit slide number DOM mutations --- js/controllers/print.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/js/controllers/print.js b/js/controllers/print.js index ed7da73e812..84651a4f62c 100644 --- a/js/controllers/print.js +++ b/js/controllers/print.js @@ -43,14 +43,13 @@ export default class Print { // Make sure stretch elements fit on slide this.Reveal.layoutSlideContents( slideWidth, slideHeight ); + const slides = queryAll( this.Reveal.getRevealElement(), SLIDES_SELECTOR ) + // Compute slide numbers now, before we start duplicating slides let doingSlideNumbers = config.slideNumber && /all|print/i.test( config.showSlideNumber ); - queryAll( this.Reveal.getRevealElement(), SLIDES_SELECTOR ).forEach( function( slide ) { - slide.setAttribute( 'data-slide-number', this.Reveal.slideNumber.getSlideNumber( slide ) ); - }, this ); // Slide and slide background layout - queryAll( this.Reveal.getRevealElement(), SLIDES_SELECTOR ).forEach( function( slide ) { + slides.forEach( function( slide, index ) { // Vertical stacks are not centred since their section // children will be @@ -118,10 +117,11 @@ export default class Print { // Inject slide numbers if `slideNumbers` are enabled if( doingSlideNumbers ) { - let numberElement = document.createElement( 'div' ); + const slideNumber = index + 1; + const numberElement = document.createElement( 'div' ); numberElement.classList.add( 'slide-number' ); numberElement.classList.add( 'slide-number-pdf' ); - numberElement.innerHTML = slide.getAttribute( 'data-slide-number' ); + numberElement.innerHTML = slideNumber; page.appendChild( numberElement ); } From 957f928c707b230a35afe8c9e27de1b129cfaedc Mon Sep 17 00:00:00 2001 From: Jeroen Hermans Date: Sun, 15 Nov 2020 22:03:39 +0100 Subject: [PATCH 4/8] Prevent layout thrashing by scroll height --- js/controllers/print.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/js/controllers/print.js b/js/controllers/print.js index 84651a4f62c..7d89f626f7b 100644 --- a/js/controllers/print.js +++ b/js/controllers/print.js @@ -16,7 +16,7 @@ export default class Print { * Configures the presentation for printing to a static * PDF. */ - setupPDF() { + async setupPDF() { let config = this.Reveal.getConfig(); @@ -48,6 +48,13 @@ export default class Print { // Compute slide numbers now, before we start duplicating slides let doingSlideNumbers = config.slideNumber && /all|print/i.test( config.showSlideNumber ); + // Batch scrollHeight access to prevent layout thrashing + await new Promise(requestAnimationFrame); + const slideScrollHeights = [] + slides.forEach( function( slide ) { + slideScrollHeights.push( slide.scrollHeight ); + }); + // Slide and slide background layout slides.forEach( function( slide, index ) { @@ -58,7 +65,7 @@ export default class Print { let left = ( pageWidth - slideWidth ) / 2, top = ( pageHeight - slideHeight ) / 2; - let contentHeight = slide.scrollHeight; + const contentHeight = slideScrollHeights[ index ]; let numberOfPages = Math.max( Math.ceil( contentHeight / pageHeight ), 1 ); // Adhere to configured pages per slide limit From 49f78535d11d92c669630f3a7045ce6fb780d807 Mon Sep 17 00:00:00 2001 From: Jeroen Hermans Date: Sun, 15 Nov 2020 22:06:48 +0100 Subject: [PATCH 5/8] Use const in print controller --- js/controllers/print.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/js/controllers/print.js b/js/controllers/print.js index 7d89f626f7b..c895654f96d 100644 --- a/js/controllers/print.js +++ b/js/controllers/print.js @@ -62,8 +62,8 @@ export default class Print { // children will be if( slide.classList.contains( 'stack' ) === false ) { // Center the slide inside of the page, giving the slide some margin - let left = ( pageWidth - slideWidth ) / 2, - top = ( pageHeight - slideHeight ) / 2; + const left = ( pageWidth - slideWidth ) / 2; + let top = ( pageHeight - slideHeight ) / 2; const contentHeight = slideScrollHeights[ index ]; let numberOfPages = Math.max( Math.ceil( contentHeight / pageHeight ), 1 ); @@ -78,7 +78,7 @@ export default class Print { // Wrap the slide in a page element and hide its overflow // so that no page ever flows onto another - let page = document.createElement( 'div' ); + const page = document.createElement( 'div' ); page.className = 'pdf-page'; page.style.height = ( ( pageHeight + config.pdfPageHeightOffset ) * numberOfPages ) + 'px'; slide.parentNode.insertBefore( page, slide ); @@ -97,12 +97,12 @@ export default class Print { if( config.showNotes ) { // Are there notes for this slide? - let notes = this.Reveal.getSlideNotes( slide ); + const notes = this.Reveal.getSlideNotes( slide ); if( notes ) { - let notesSpacing = 8; - let notesLayout = typeof config.showNotes === 'string' ? config.showNotes : 'inline'; - let notesElement = document.createElement( 'div' ); + const notesSpacing = 8; + const notesLayout = typeof config.showNotes === 'string' ? config.showNotes : 'inline'; + const notesElement = document.createElement( 'div' ); notesElement.classList.add( 'speaker-notes' ); notesElement.classList.add( 'speaker-notes-pdf' ); notesElement.setAttribute( 'data-layout', notesLayout ); @@ -138,7 +138,7 @@ export default class Print { // 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. - let fragmentGroups = this.Reveal.fragments.sort( page.querySelectorAll( '.fragment' ), true ); + const fragmentGroups = this.Reveal.fragments.sort( page.querySelectorAll( '.fragment' ), true ); let previousFragmentStep; let previousPage; From 33c4c1c5d2bd19abda159c4b507b93ee1745f15a Mon Sep 17 00:00:00 2001 From: Jeroen Hermans Date: Sun, 15 Nov 2020 22:08:13 +0100 Subject: [PATCH 6/8] Batch print DOM updates --- js/controllers/print.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/js/controllers/print.js b/js/controllers/print.js index c895654f96d..2ceda1ceb76 100644 --- a/js/controllers/print.js +++ b/js/controllers/print.js @@ -55,6 +55,9 @@ export default class Print { slideScrollHeights.push( slide.scrollHeight ); }); + const pages = []; + const pageContainer = slides[0].parentNode; + // Slide and slide background layout slides.forEach( function( slide, index ) { @@ -79,9 +82,10 @@ export default class Print { // Wrap the slide in a page element and hide its overflow // so that no page ever flows onto another const page = document.createElement( 'div' ); + pages.push( page ); + page.className = 'pdf-page'; page.style.height = ( ( pageHeight + config.pdfPageHeightOffset ) * numberOfPages ) + 'px'; - slide.parentNode.insertBefore( page, slide ); page.appendChild( slide ); // Position the slide inside of the page @@ -109,7 +113,7 @@ export default class Print { notesElement.innerHTML = notes; if( notesLayout === 'separate-page' ) { - page.parentNode.insertBefore( notesElement, page.nextSibling ); + pages.push( notesElement ); } else { notesElement.style.left = notesSpacing + 'px'; @@ -141,7 +145,6 @@ export default class Print { const fragmentGroups = this.Reveal.fragments.sort( page.querySelectorAll( '.fragment' ), true ); let previousFragmentStep; - let previousPage; fragmentGroups.forEach( function( fragments ) { @@ -159,10 +162,9 @@ export default class Print { // Create a separate page for the current fragment state let clonedPage = page.cloneNode( true ); - page.parentNode.insertBefore( clonedPage, ( previousPage || page ).nextSibling ); + pages.push( clonedPage ); previousFragmentStep = fragments; - previousPage = clonedPage; }, this ); @@ -185,6 +187,11 @@ export default class Print { }, this ); + await new Promise(requestAnimationFrame); + pages.forEach( function( page ) { + pageContainer.appendChild( page ); + }) + // Notify subscribers that the PDF layout is good to go this.Reveal.dispatchEvent({ type: 'pdf-ready' }); @@ -199,4 +206,4 @@ export default class Print { } -} \ No newline at end of file +} From 10f02ece9942d8bb067091615591b1c1d2d2f515 Mon Sep 17 00:00:00 2001 From: Jeroen Hermans Date: Mon, 16 Nov 2020 09:47:19 +0100 Subject: [PATCH 7/8] Group pdf setup reads and writes --- js/controllers/print.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/js/controllers/print.js b/js/controllers/print.js index 2ceda1ceb76..5a6bfa88cd1 100644 --- a/js/controllers/print.js +++ b/js/controllers/print.js @@ -18,18 +18,24 @@ export default class Print { */ async setupPDF() { - let config = this.Reveal.getConfig(); + const config = this.Reveal.getConfig(); + const slides = queryAll( this.Reveal.getRevealElement(), SLIDES_SELECTOR ) + + // Compute slide numbers now, before we start duplicating slides + const doingSlideNumbers = config.slideNumber && /all|print/i.test( config.showSlideNumber ); - let slideSize = this.Reveal.getComputedSlideSize( window.innerWidth, window.innerHeight ); + const slideSize = this.Reveal.getComputedSlideSize( window.innerWidth, window.innerHeight ); // Dimensions of the PDF pages - let pageWidth = Math.floor( slideSize.width * ( 1 + config.margin ) ), + const pageWidth = Math.floor( slideSize.width * ( 1 + config.margin ) ), pageHeight = Math.floor( slideSize.height * ( 1 + config.margin ) ); // Dimensions of slides within the pages - let slideWidth = slideSize.width, + const slideWidth = slideSize.width, slideHeight = slideSize.height; + await new Promise(requestAnimationFrame); + // Let the browser know what page size we want to print createStyleSheet( '@page{size:'+ pageWidth +'px '+ pageHeight +'px; margin: 0px;}' ); @@ -41,13 +47,9 @@ export default class Print { document.body.style.height = pageHeight + 'px'; // Make sure stretch elements fit on slide + await new Promise(requestAnimationFrame); this.Reveal.layoutSlideContents( slideWidth, slideHeight ); - const slides = queryAll( this.Reveal.getRevealElement(), SLIDES_SELECTOR ) - - // Compute slide numbers now, before we start duplicating slides - let doingSlideNumbers = config.slideNumber && /all|print/i.test( config.showSlideNumber ); - // Batch scrollHeight access to prevent layout thrashing await new Promise(requestAnimationFrame); const slideScrollHeights = [] @@ -161,7 +163,7 @@ export default class Print { }, this ); // Create a separate page for the current fragment state - let clonedPage = page.cloneNode( true ); + const clonedPage = page.cloneNode( true ); pages.push( clonedPage ); previousFragmentStep = fragments; From e57ff233a4830de7729a7235b2ec120179feb581 Mon Sep 17 00:00:00 2001 From: Jeroen Hermans Date: Mon, 16 Nov 2020 09:47:58 +0100 Subject: [PATCH 8/8] Group background reads and writes --- js/controllers/backgrounds.js | 40 +++++++++++++++++------------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/js/controllers/backgrounds.js b/js/controllers/backgrounds.js index e8cc9960e69..1327b9f80c5 100644 --- a/js/controllers/backgrounds.js +++ b/js/controllers/backgrounds.js @@ -27,8 +27,6 @@ export default class Backgrounds { */ create() { - let printMode = this.Reveal.isPrintingPDF(); - // Clear prior backgrounds this.element.innerHTML = ''; this.element.classList.add( 'no-transition' ); @@ -114,9 +112,24 @@ export default class Backgrounds { */ sync( slide ) { - let element = slide.slideBackgroundElement, + const element = slide.slideBackgroundElement, contentElement = slide.slideBackgroundContentElement; + const data = { + background: slide.getAttribute( 'data-background' ), + backgroundSize: slide.getAttribute( 'data-background-size' ), + backgroundImage: slide.getAttribute( 'data-background-image' ), + backgroundVideo: slide.getAttribute( 'data-background-video' ), + backgroundIframe: slide.getAttribute( 'data-background-iframe' ), + backgroundColor: slide.getAttribute( 'data-background-color' ), + backgroundRepeat: slide.getAttribute( 'data-background-repeat' ), + backgroundPosition: slide.getAttribute( 'data-background-position' ), + backgroundTransition: slide.getAttribute( 'data-background-transition' ), + backgroundOpacity: slide.getAttribute( 'data-background-opacity' ), + }; + + const dataPreload = slide.hasAttribute( 'data-preload' ); + // Reset the prior background state in case this is not the // initial sync slide.classList.remove( 'has-dark-background' ); @@ -135,19 +148,6 @@ export default class Backgrounds { contentElement.style.opacity = ''; contentElement.innerHTML = ''; - let data = { - background: slide.getAttribute( 'data-background' ), - backgroundSize: slide.getAttribute( 'data-background-size' ), - backgroundImage: slide.getAttribute( 'data-background-image' ), - backgroundVideo: slide.getAttribute( 'data-background-video' ), - backgroundIframe: slide.getAttribute( 'data-background-iframe' ), - backgroundColor: slide.getAttribute( 'data-background-color' ), - backgroundRepeat: slide.getAttribute( 'data-background-repeat' ), - backgroundPosition: slide.getAttribute( 'data-background-position' ), - backgroundTransition: slide.getAttribute( 'data-background-transition' ), - backgroundOpacity: slide.getAttribute( 'data-background-opacity' ) - }; - if( data.background ) { // Auto-wrap image urls in url(...) if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)([?#\s]|$)/gi.test( data.background ) ) { @@ -179,7 +179,7 @@ export default class Backgrounds { if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor; if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition ); - if( slide.hasAttribute( 'data-preload' ) ) element.setAttribute( 'data-preload', '' ); + if( dataPreload ) element.setAttribute( 'data-preload', '' ); // Background image options are set on the content wrapper if( data.backgroundSize ) contentElement.style.backgroundSize = data.backgroundSize; @@ -194,14 +194,14 @@ export default class Backgrounds { // If no bg color was found, check the computed background if( !contrastColor ) { - let computedBackgroundStyle = window.getComputedStyle( element ); + const computedBackgroundStyle = window.getComputedStyle( element ); if( computedBackgroundStyle && computedBackgroundStyle.backgroundColor ) { contrastColor = computedBackgroundStyle.backgroundColor; } } if( contrastColor ) { - let rgb = colorToRgb( contrastColor ); + const rgb = colorToRgb( contrastColor ); // Ignore fully transparent backgrounds. Some browsers return // rgba(0,0,0,0) when reading the computed background color of @@ -394,4 +394,4 @@ export default class Backgrounds { } -} \ No newline at end of file +}