Después de llevar un tiempo preparando presentaciones en markdown mediante Marp he descubierto otra alternativa llamada Quarto, que además está mantenida por Posit (anteriormente RStudio). Quarto no solo permite crear presentaciones, sino también una amplia variedad de documentos como libros, artículos, sitios web, etc. Y no solo eso, permite generar contenido dinámico (ej. gráficas, tablas, etc.) mediante lenguajes como R, Python o Julia. En este post describo cómo lo he utilizado para generar presentaciones para mis clases.
Introducción
Quarto incluye una documentación bastante completa, así que te invito a que consultes las diferentes guías disponibles, que para hacer cosas sencillas es más que suficiente. En concreto puedes consultar la introducción a cómo hacer presentaciones y, más concretamente, a cómo hacer presentaciones HTML con reveal.js. Si quieres las presentaciones en formato pdf, puedes indicar el formato beamer (LaTeX), pero la opción de HTML me parece más completa y es fácilmente exportable a pdf.
En este post me centro en cómo preparar las diapositivas de mis asignaturas haciendo los siguientes ajustes al tema por defecto:
- Opciones de reveal.js:
- Formato 16:9 con una resolución base (es escalable) de 1280x720.
- Tabla de contenido automática
- Numeración de secciones
- Estilo CSS propio:
- Tamaños y colores
- Personalización de elementos
- Plantillas HTML:
- Página de título personalizada
- Página de copyright
- Código JavaScript para generar contenido dinámico y reorganizar elementos
A continuación pongo algunas diapositivas a modo de ejemplo:
Opciones de reveal.js
A continuación indico la cabecera YAML donde se pueden ver, entre otras cosas, las diferentes opciones de reveal.js que empleo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
title: "1. Administración de Servidores"
subtitle: "Servicios Telemáticos Avanzados"
author:
- "Gorka Prieto <<gorka.prieto@ehu.eus>>"
- "Maider Huarte <<maider.huarte@ehu.eus>>"
institute:
- "Grado en Ingeniería en Tecnología de Telecomunicación / <br/> Grado en Ingeniería Informática de Gestión y Sistemas de Información"
- "Escuela de Ingeniería de Bilbao (UPV/EHU)"
date: "now"
date-format: "[Curso 2023/2024]"
format:
revealjs:
width: 1280
height: 720
theme: "theme/ehu.scss"
#logo: "figs/ehu.png"
template-partials:
- "theme/title-slide.html"
include-after-body:
- file: "theme/shift-toc.html"
- file: "theme/repeat-heading.html"
footer: "©2013--2024 Gorka Prieto Agujeta / Maider Huarte Arrayago (UPV/EHU)"
toc: true
toc-depth: 2
toc-title: "Tabla de contenidos"
number-sections: true
number-depth: 2
slide-level: 1
slide-number: true
code-line-numbers: false
Muchas de las opciones son autoexplicativas o ya están explicadas en la documentación oficial de las opciones del formato reveal.js, así que me voy a limitar a subrayar algunas:
- Como
slide-level
indico1
ya que quiero permitir cabeceras de nivel 1 y 2 en la misma diapositiva. El tema que he desarrollado funciona tanto si se indica aquí el valor1
o el valor2
. Muchos documentos de markdown emplean la cabecera de nivel 1 simplemente para indicar el título, y las de nivel 2 para indicar las secciones. Personalmente me resulta más intuitivo indicar las secicones principales con el nivel 1, así que el tema está diseñado de esta manera. - El tema CSS se indica mediante
theme
, pero no permite personalizar las plantillas HTML (ej. la de título), cosa que se hace mediantetemplate-partials
. - Algunas personalizaciones no las puedo hacer desde CSS y para ello utilizo código JavaScript que ejecuto al final del documento mediante la opción
include-after-body
.
Estilo CSS
El tema se puede personalizar mediante un fichero SCSS, pero es necesario que contenga dos secciones indicadas con comentarios especiales:
- En la primera sección redefino las variables internas que afectan a los colores, tamaños, etc.
- En la segunda sección defino estilos propios y personalizo los propios de reveal.
El fichero SCSS resultante queda de la siguiente manera:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/*-- scss:defaults --*/
//$font-family-sans-serif: "EHUSans" !default;
$presentation-font-size-root: 24pt !default;
//$presentation-font-smaller: 0.25em !default;
$presentation-h1-font-size: 1.6em !default;
$presentation-h2-font-size: 1.3em !default;
$presentation-h3-font-size: 1.1em !default;
$presentation-h4-font-size: 1em !default;
$presentation-line-height: 1.1 !default;
//$presentation-heading-color: #3465a4 !default;
$presentation-heading-color: #355269 !default;
//$presentation-heading-text-shadow: 2px 2px 2px #555 !default;
/*-- scss:rules --*/
// Title slide
$gradient-color1: #c5dbf2;
$gradient-color2: #6ebde8;
#title-slide > #title-block {
color: black;
background: linear-gradient(180deg, $gradient-color1, $gradient-color2);
border-radius: 20px;
padding: 20px;
}
#title-slide > #title-block > hr {
border-width: 4px;
border-color: $body-bg;
}
// Table of contents
#TOC ul {
//margin-left: 0;
list-style-type: none;
}
#TOC > nav > ul {
column-count: 2;
column-fill: auto;
height: 600px;
}
#TOC > nav > ul > li {
break-inside: avoid-column;
}
// Main headings
.reveal .slide > h1:not(:has(+h2)):after {
content: "";
display: block;
border: 1px solid #ccc;
margin-top: 10px;
clear: both;
}
.reveal .slide > h2:after {
content: "";
display: block;
border: 1px solid #ccc;
margin-top: 10px;
clear: both;
}
.reveal .slide > h3:after {
content: ":";
}
.header-section-number::after, .toc-section-number::after {
content: ". ";
}
// Footer
@media print {
.reveal .slide aside {
margin-bottom: 50px;
}
}
.reveal .slide aside::before {
content: "";
display: block;
border: 1px solid #ccc;
//margin-bottom: 10px;
clear: both;
width: 33%;
}
.reveal .footer > p {
//font-style: italic;
font-size: 10pt;
//text-align: right;
//margin-right: 75px;
}
// Slide content
.reveal .slide caption {
//color: $presentation-heading-color;
font-weight: bold;
font-size: $presentation-h4-font-size;
//text-decoration: underline;
}
strong {
//font-weight: bold;
color: #3465a4;
//text-shadow: 1px 1px 1px #555;
//text-decoration: underline;
}
.reveal .slide a {
text-decoration: underline;
}
.reveal .slide .external {
target-new: window;
}
.reveal .slide ul ul {
font-size: 0.98em;
}
.reveal .slide ul ul ul {
font-size: 0.95em;
}
.reveal code {
font-size: 20pt;
}
.reveal pre.console code, .reveal pre.text code {
line-height: 1.3;
}
Algunos aspectos a destacar de la segunda sección:
- En la página de título muestro algunos contenidos con un gradiente de fondo.
- La tabla de contenidos la muestro en dos columnas.
- Las cabeceras de nivel 1 (si no va seguida por 2) y 2 muestran un línea debajo, mientras que las de nivel 3 muestran “:” tras el texto. Además se añade un “. “ tras el número de sección.
- Personalizo el tamaño del pie de página y añado un margen en el aside a la hora de imprimir para que no se superponga con el pie de página.
- Personalizo algunos estilos básicos.
Plantillas HTML
Página de título
Personalizo la forma en la que se muestran los diferentes componentes del título:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<section id="$idprefix$title-slide"$for(title-slide-attributes/pairs)$ $it.key$="$it.value$"$endfor$>
<div id="title-block" class="title">
<h1>$title$</h1>
$if(subtitle)$
<hr style="border-width: 2px"/>
<h2>$subtitle$</h2>
$endif$
$for(institute)$
<p class="institute">$institute$</p>
$endfor$
</div>
<br/>
$for(author)$
<p class="author">$author$</p>
$endfor$
<br/>
$if(date)$
<p class="date">$date$</p>
$endif$
</section>
Página de copyright
Siempre me gusta incluir un página de copyright sencilla tras el título, sin numerar y sin que aparezca en la tabla de contenidos:
1
2
3
4
5
6
7
8
9
10
11
12
# Licencia de uso {.unnumbered .unlisted}
![](https://mirrors.creativecommons.org/presskit/icons/cc.svg)
![](https://mirrors.creativecommons.org/presskit/icons/by.svg)
![](https://mirrors.creativecommons.org/presskit/icons/sa.svg)
_Servicios Telemáticos Avanzados_ © 2012-2024 by
- Gorka Prieto Agujeta <<gorka.prieto@ehu.eus>>
- Maider Huarte Arrayago <<maider.huarte@ehu.eus>>
is licensed under _CC BY-SA 4.0_. To view a copy of this license, visit <https://creativecommons.org/licenses/by-sa/4.0/>.
Pero esto tiene el problema de que la diapositiva con la tabla de contenidos que se genera automáticamente se inserta tras la diapositiva de título, es decir, antes de la de copyright. Como esto no se puede personalizar, indico que se ejecute el siguiente código JavaScript que la desplaza una posición, es decir, pone la tabla de contenidos tras la diapositiva de copyright:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>
shiftToc = function() {
const toc = document.querySelector("#TOC")
if( !toc ) {
return;
}
const slides = document.querySelector(".slides");
if( slides.children.length >= 4 ) {
slides.insertBefore(toc.cloneNode(true), slides.children[3]);
} else {
slides.appendChild(toc.cloneNode(true));
}
toc.remove();
}
shiftToc();
</script>
Mostrar sección actual
Un aspecto que me parece importante es que los estudiantes puedan ver directamente a qué sección pertenece la diapositiva actual. Por defecto la sección solo se muestra en la dispositiva en la que se define, así que utilizo el siguiente código JavaScript para insertarla de nuevo al comienzo de cada diapositiva hasta que se cambie de sección:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<script>
insertHeadings = function() {
const tagRegex = /^H[1-5]$/
const headings = new Array(5);
for( const section of document.querySelectorAll(".reveal section.slide") ) {
if( section.innerHTML.trim() === "" ) {
section.remove();
continue;
}
let firstLevel = null;
for( const child of section.childNodes ) {
if( tagRegex.test(child.tagName) ) {
const level = parseInt(child.tagName.charAt(1), 10);
if( !firstLevel ) {
firstLevel = level;
}
for(let i = 5; i > level; i-- ) {
headings[i-1] = null;
}
headings[level-1] = child;
}
}
if( !firstLevel ) {
firstLevel = headings.length+1;
}
for( let i = firstLevel-1; i > 1; i-- ) {
if( headings[i-1] ) {
section.insertBefore(headings[i-1].cloneNode(true), section.firstChild);
}
}
}
}
insertHeadings();
</script>
Comments powered by Disqus.