ids theme: adaptive vertical centering of slide content
Slides with little content look lost when everything is glued below the
title bar, while packed slides must start at the top. reveal.js' own
center option cannot express this -- it also vertically centers the
headings.
Instead the template groups each slide's body content (everything after
the heading block except speaker notes) into a div.ids-slide-content,
and the theme turns such slides into flex columns of full slide height:
the heading block stays pinned to the top and the wrapper's auto margins
center the content in the remaining space. Auto margins collapse to zero
when there is no free space, so packed slides still start directly below
the heading and overflow downwards exactly as before -- no measuring and
no resize handling involved. The wrapper also reserves the footer band
(--ids-footer-reserve, published from the actual footer height in slide
coordinates), so centered content never covers the footer; slides too
packed to fit above it may, as they always could.
Heading-only section-divider slides keep their dedicated centered-title
layout, the transcript view treats the wrapper transparently, and the
new rules are screen-scoped so the pdf export keeps laying out slides
itself.
Change-Id: Iccfbebebbf18ec860c1ccd950043a84f638c33da
diff --git a/NEWS.md b/NEWS.md
index 8ea6408..a5e648e 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,3 +1,7 @@
+# revealjs.ids (development version)
+
+- Adaptive vertical centering of slide content: slides with little content now center their body content in the space between the heading and the footer instead of sitting glued below the title bar; packed slides still start directly below the heading and overflow downwards exactly as before, and centered content never covers the footer. Reveal.js' own `center` option could not be used (it also vertically centers the headings); instead each slide's body content is grouped into a wrapper whose auto margins do the centering, so no measuring and no resize handling is involved. Heading-only section-divider slides keep their dedicated centered-title layout.
+
# revealjs.ids 0.9.1.9010
- Automatic chapter numbering (ported from the old Google Docs slide workflow): `#` chapter headings get a sequential "1. ", "2. " ... prefix when the presentation is opened. References, appendix and thank-you chapters keep their bare titles and are not counted; manually typed leading numbers (`# 2. Methods`) are stripped before renumbering, so decks migrated from the old pipeline and repeated numbering runs stay stable. The menu plugin and the transcript view show the numbers, too, and the Overview slide's table of contents uses them as its ordered list's enumeration (entry titles are stripped back to their bare form). Decks with `slide_level: 1` (one `#` heading per slide) are left unnumbered.
diff --git a/R/revealjs_presentation.R b/R/revealjs_presentation.R
index 047bda3..d93e617 100644
--- a/R/revealjs_presentation.R
+++ b/R/revealjs_presentation.R
@@ -94,6 +94,21 @@
#' The function [slide_timing()] computes these totals directly from an
#' `.Rmd` source file without rendering it.
#'
+#' ## Vertical content centering
+#'
+#' Slides with little content look lost when everything is glued below the
+#' title bar, while packed slides must start at the top. reveal.js' own
+#' `center` option cannot express this -- it also vertically centers the
+#' headings. Instead, the body content of each slide (everything after the
+#' heading block except speaker notes) is grouped and, whenever there is
+#' free space, centered in the area between the heading and the footer:
+#' slides that are too packed simply start directly below the heading and
+#' overflow downwards, exactly as before. Centered content never reaches
+#' into the footer; content too tall to fit above it may, as it always
+#' could. Heading-only section-divider slides keep their own centered-title
+#' layout. No configuration is needed and nothing changes about how slides
+#' are authored.
+#'
#' ## Chapter numbering
#'
#' Like in the old Google Docs slide workflow, chapter headings (`#`) are
diff --git a/README.Rmd b/README.Rmd
index a4b6e88..9418dbd 100644
--- a/README.Rmd
+++ b/README.Rmd
@@ -182,6 +182,10 @@
Decks with `slide_level: 1` (one `#` heading per slide) are left unnumbered -- there, headings denote slides, not chapters.
+## Vertical Content Centering
+
+Slides with little content no longer sit glued below the title bar: whenever there is free space, the slide's body content is centered in the area between the heading and the footer. Slides that are too packed start directly below the heading and overflow downwards exactly as before, and centered content never covers the footer. Reveal.js' own `center` option could not be used for this, because it also vertically centers the headings. Heading-only section-divider slides keep their dedicated centered-title layout.
+
## Incremental Bullets
You can render bullets incrementally by adding the `incremental` option:
diff --git a/README.md b/README.md
index 5a1bd91..e842f65 100644
--- a/README.md
+++ b/README.md
@@ -263,6 +263,17 @@
Decks with `slide_level: 1` (one `#` heading per slide) are left
unnumbered -- there, headings denote slides, not chapters.
+## Vertical Content Centering
+
+Slides with little content no longer sit glued below the title bar:
+whenever there is free space, the slide's body content is centered in the
+area between the heading and the footer. Slides that are too packed start
+directly below the heading and overflow downwards exactly as before, and
+centered content never covers the footer. Reveal.js' own `center` option
+could not be used for this, because it also vertically centers the
+headings. Heading-only section-divider slides keep their dedicated
+centered-title layout.
+
## Incremental Bullets
You can render bullets incrementally by adding the `incremental` option:
diff --git a/inst/reveal.js-5.2.1/css/theme/source/ids.scss b/inst/reveal.js-5.2.1/css/theme/source/ids.scss
index e833b8f..f431662 100644
--- a/inst/reveal.js-5.2.1/css/theme/source/ids.scss
+++ b/inst/reveal.js-5.2.1/css/theme/source/ids.scss
@@ -366,6 +366,44 @@
background: #{$idsOrange} !important;
}
+// Adaptive vertical centering of slide content: the template groups each
+// slide's body content (everything after the heading block except speaker
+// notes) into a div.ids-slide-content. Slides carrying such a wrapper become
+// flex columns of full slide height: the heading block stays pinned to the
+// top and the wrapper's auto margins center the content in the remaining
+// space. Auto margins collapse to zero when there is no free space, so
+// packed slides still start directly below the heading and overflow
+// downwards as before -- the drawback that makes reveal.js' own `center`
+// option unusable here (it would also center the headings). The wrapper
+// keeps the footer band free (--ids-footer-reserve, published by the
+// template from the actual footer height), so centered content never
+// covers the footer; slides too packed to fit above it may, as they always
+// could. Display only ever changes for slides reveal.js is actually
+// showing, mirroring the section-divider rule above, which works the same
+// way; screen-only, so the pdf export keeps laying out slides itself.
+@media screen {
+ html .reveal .slides section:not(.stack):not(.title-frame) {
+ height: 100% !important;
+ display: flex !important;
+ flex-direction: column !important;
+ }
+
+ .reveal .slides section:not(.stack):not(.title-frame) > .ids-slide-content {
+ flex: none;
+ margin-top: auto !important;
+ margin-bottom: auto !important;
+ padding-bottom: var(--ids-footer-reserve, 48px);
+
+ // under block layout the first content element's margin-top collapsed
+ // with the heading's margin-bottom; as flex items the two would add up
+ // instead, so drop it and let the heading spacing rule alone define
+ // the gap, as before
+ > :first-child {
+ margin-top: 0 !important;
+ }
+ }
+}
+
.reveal .slides section.level1:not(.title-frame) > h1:first-child {
text-align: center !important;
margin: 0 !important;
diff --git a/inst/reveal.js-5.2.1/dist/theme/ids.css b/inst/reveal.js-5.2.1/dist/theme/ids.css
index dd54e86..8d56808 100644
--- a/inst/reveal.js-5.2.1/dist/theme/ids.css
+++ b/inst/reveal.js-5.2.1/dist/theme/ids.css
@@ -523,6 +523,40 @@
content: none !important;
}
+/* Adaptive vertical centering of slide content: the template groups each
+ slide's body content (everything after the heading block except speaker
+ notes) into a div.ids-slide-content. Slides carrying such a wrapper become
+ flex columns of full slide height: the heading block stays pinned to the
+ top and the wrapper's auto margins center the content in the remaining
+ space. Auto margins collapse to zero when there is no free space, so
+ packed slides still start directly below the heading and overflow
+ downwards as before -- the drawback that makes reveal.js' own `center`
+ option unusable here (it would also center the headings). The wrapper
+ keeps the footer band free (--ids-footer-reserve, published by the
+ template from the actual footer height), so centered content never
+ covers the footer; slides too packed to fit above it may, as they always
+ could. Display only ever changes for slides reveal.js is actually
+ showing, mirroring the section-divider rule above, which works the same
+ way; screen-only, so the pdf export keeps laying out slides itself. */
+@media screen {
+ html .reveal .slides section:not(.stack):not(.title-frame) {
+ height: 100% !important;
+ display: flex !important;
+ flex-direction: column !important;
+ }
+
+ .reveal .slides section:not(.stack):not(.title-frame) > .ids-slide-content {
+ flex: none;
+ margin-top: auto !important;
+ margin-bottom: auto !important;
+ padding-bottom: var(--ids-footer-reserve, 48px);
+ }
+
+ .reveal .slides section:not(.stack):not(.title-frame) > .ids-slide-content > :first-child {
+ margin-top: 0 !important;
+ }
+}
+
:root {
--r-bold-color: #f6a800;
--r-list-bullet-color: #f6a800;
diff --git a/inst/rmarkdown/templates/revealjs_presentation/resources/default.html b/inst/rmarkdown/templates/revealjs_presentation/resources/default.html
index 45b55e4..f5eb25b 100644
--- a/inst/rmarkdown/templates/revealjs_presentation/resources/default.html
+++ b/inst/rmarkdown/templates/revealjs_presentation/resources/default.html
@@ -1177,6 +1177,72 @@
})();
</script>
<script>
+ // Adaptive vertical centering of slide content: slides with little content
+ // look lost when everything is glued below the title bar, while packed
+ // slides must start at the top. Reveal.js' own `center` option cannot do
+ // this -- it also vertically centers the headings. Instead, each slide's
+ // body content (everything after the heading block, except speaker notes)
+ // is grouped into a .ids-slide-content wrapper here, and the ids theme
+ // turns these slides into flex columns: the heading stays at the top, and
+ // the wrapper's auto margins center it in the remaining space. Auto
+ // margins collapse to zero when there is no free space, so packed slides
+ // still start directly below the heading and overflow downwards exactly
+ // as before. The wrapper also reserves the footer band (--ids-footer-
+ // reserve, published below from the actual footer height), so centered
+ // content never reaches into the footer; slides too packed to fit above
+ // it may, as they always could.
+ (function () {
+ // The footer is viewport-fixed and display:none on the title page;
+ // express its height (plus the dotted rule, the progress bar and a
+ // little air) in slide coordinates, where the theme consumes it.
+ function publishFooterReserve() {
+ var footer = document.getElementById('ids-footer');
+ if (!footer || typeof Reveal === 'undefined') return;
+ var prevDisplay = footer.style.display;
+ footer.style.display = 'block';
+ var px = (footer.offsetHeight + 10) / (Reveal.getScale() || 1);
+ footer.style.display = prevDisplay;
+ document.documentElement.style.setProperty('--ids-footer-reserve', px + 'px');
+ }
+
+ // number of leading elements forming the heading block: the slide title
+ // (h1/h2) and, on level-2 slides, an h3 framesubtitle following it
+ function headingCount(slide) {
+ var kids = slide.children;
+ if (!kids[0] || (kids[0].tagName !== 'H1' && kids[0].tagName !== 'H2')) return 0;
+ if (kids[0].tagName === 'H2' && kids[1] && kids[1].tagName === 'H3') return 2;
+ return 1;
+ }
+
+ function wrapSlideContent(slide) {
+ if (slide.dataset.contentWrapped) return;
+ slide.dataset.contentWrapped = '1';
+ var content = Array.prototype.filter.call(
+ Array.prototype.slice.call(slide.children, headingCount(slide)),
+ function (el) { return el.tagName !== 'ASIDE'; }
+ );
+ // heading-only (divider) and empty slides keep their dedicated layout
+ if (!content.length) return;
+ var wrapper = document.createElement('div');
+ wrapper.className = 'ids-slide-content';
+ slide.insertBefore(wrapper, slide.children[headingCount(slide)] || null);
+ content.forEach(function (el) { wrapper.appendChild(el); });
+ }
+
+ function build() {
+ if (typeof Reveal === 'undefined' || !Reveal.isReady()) return;
+ publishFooterReserve();
+ Reveal.getSlides().forEach(wrapSlideContent);
+ }
+
+ if (typeof Reveal !== 'undefined') {
+ if (Reveal.isReady()) build();
+ else Reveal.addEventListener('ready', build);
+ Reveal.addEventListener('resize', publishFooterReserve);
+ }
+ })();
+</script>
+<script>
// Practice/read-aloud view: pressing 'r' toggles a single scrollable page
// containing every slide's content and speaker notes. Text-to-speech
// browser extensions cannot walk a reveal.js deck (hidden slides are
@@ -1195,13 +1261,19 @@
section.className = 'ids-transcript-slide';
var heading = slide.querySelector('h1,h2');
if (heading) section.appendChild(heading.cloneNode(true));
- Array.prototype.forEach.call(slide.childNodes, function (node) {
+ function appendContent(node) {
if (node.nodeType !== Node.ELEMENT_NODE) return;
if (node.tagName === 'ASIDE' || node.tagName === 'H1' ||
node.tagName === 'H2' || node.tagName === 'SECTION' ||
(node.classList && node.classList.contains('ids-overview-toc'))) return;
+ if (node.classList && node.classList.contains('ids-slide-content')) {
+ // the adaptive-centering wrapper is transparent for the transcript
+ Array.prototype.forEach.call(node.childNodes, appendContent);
+ return;
+ }
section.appendChild(node.cloneNode(true));
- });
+ }
+ Array.prototype.forEach.call(slide.childNodes, appendContent);
var notes = Array.prototype.map.call(
slide.querySelectorAll('aside.notes'),
function (n) { return n.innerHTML; }
diff --git a/man/revealjs_presentation.Rd b/man/revealjs_presentation.Rd
index ce1e0c6..2741765 100644
--- a/man/revealjs_presentation.Rd
+++ b/man/revealjs_presentation.Rd
@@ -249,6 +249,22 @@
\code{.Rmd} source file without rendering it.
}
+\subsection{Vertical content centering}{
+
+Slides with little content look lost when everything is glued below the
+title bar, while packed slides must start at the top. reveal.js' own
+\code{center} option cannot express this -- it also vertically centers the
+headings. Instead, the body content of each slide (everything after the
+heading block except speaker notes) is grouped and, whenever there is
+free space, centered in the area between the heading and the footer:
+slides that are too packed simply start directly below the heading and
+overflow downwards, exactly as before. Centered content never reaches
+into the footer; content too tall to fit above it may, as it always
+could. Heading-only section-divider slides keep their own centered-title
+layout. No configuration is needed and nothing changes about how slides
+are authored.
+}
+
\subsection{Chapter numbering}{
Like in the old Google Docs slide workflow, chapter headings (\verb{#}) are