Jupyter Notebook导出幻灯片(基于Reveal.js)
前言
前一段时间在整理Python数据可视化分享的时候,为了简化幻灯片的制作,同时能够有助于提高对于代码等的展示效果,所以学习基于Jupyter Notebook和Reveal.js制作HTML幻灯片,其中涉及到一些定制化的设置,特意记录下来,也供后续提高参考。
Reveal.js介绍
基于HTML的幻灯片有多种,其中较为流行的一种就是Reveal.js 。Reveal.js是一个展示内容的框架,可以简单的理解为网页版的PPT,能够将Markdown等格式的文件转化为类似PPT的幻灯片。开发者甚至还提供了一个网站——Slides.com,帮助使用者在线制作幻灯片。
Reveal.js的功能非常强大,具体的使用方法我也只是接触了皮毛,更多可以参考其技术文档。
Jupyter Notebook转HTML幻灯片
根据我的调研,基于Reveal.js,将Jupyter Notebook转为HTML幻灯片大概可以分为三条技术路线:
- Jupyter Notebook安装RISE插件,直接以幻灯片的形式放映Notebook;
- 将Notebook导出为Markdown文件,然后使用Pandoc将md文件转为幻灯片;
- 直接将Notebook转换为html幻灯片。
这三种技术路线各有优劣:
- 方法1展示起来很方便,且效果也不错;但是,RISE插件目前还不能再Jupyter Lab下使用,而且不方便移植播放;
- 方法2可以对Markdown进行比较精细化的设置,效果美观;但是,Notebook导出md,交互式图表会变成静态图片;
- 方法3导出也比较方便,但是效果一般,需要进一步调整样式。
根据我的需求,一番权衡之后,决定采用方法3。
基本方法
基本的转换方法,其实非常简单,因为Jupyter Lab/Notebook中的nbconvert有输出Reveal.js slides的功能:
-
采用GUI导出
-
采用命令行,例如:
1
jupyter nbconvert --output-dir='../slides' 03_plotnine.ipynb --to slides --template=my_slides.tpl --SlidesExport er.reveal_theme=solarized
具体的参数,可以查看
nbconvert
的源代码和技术文档,其中对于我们定制化比较重要的参数是--template
,与GUI相比,命令行显然能够提供更加精细的控制。
在使用默认参数的情况下,我们导出的幻灯片大概是这样子的:
与我们平时做的PPT相比,显然谈不上美观,可能存在的不足包括:
- 首页、内容页等不同样式没有区分;
- 没有页眉、页脚、页码;
- 丑,丑,还是丑……
样式调整
基本思路
前面导出的换灯片之所以存在不足就是因为,原生的导出只使用了Reveal.js基本的功能,所以我们要自己添加一些额外的功能。例如,我想要做的调整主要包括以下内容:
- 区分幻灯片的首页和内容页;
- 为幻灯片添加页眉、页脚;
- 优化幻灯片段切换的效果
一番探索之后,发现Jupyter Notebook转HTML幻灯片主要涉及到以下对象:
nbconvert
模块.ipynb
文件- 转换模板
- 幻灯片的css文件
第一个显然不是我们想动的,所以我们只能对2~4下手,具体如下。
修改.ipynb
文件
修改.ipynb文件有两种方式:
-
直接写原生的html内容,例如我们想添加javascript脚本、div元素或者css样式,可以直接将cell选择为Raw,然后直接填写相应内容;
-
修改Cell Metadata,在Jupyter Lab中,我们可以使用Property Inspector。
修改转换模板
-
首先找到
nbconvert
自带的模板,从名字可以看出来应该是slides_reveal.tpl
,复制重命名为my_slides.tpl
; -
查看模板的具体内容,可以知道模板采用的是
jinja2
语法,那么就可以根据需要在其中进行增删修改。
修改css文件
我建议修改的css包括两个来源:
-
Reveal.js预设的一些模板样式,在
nbconvert
中可以指定参数,例如1
--SlidesExport er.reveal_theme=solarized
这样就可以采用Reveal.js的Solarized样式;
-
nbconvert
的默认模板添加了custom.css
,所以我们可以创建custom.css
,然后对默认样式进行补充或者覆盖。
大概的修改顺序也应该按照2->3->4的顺序进行:
- 如果对转换的内容进行调整,首先修改
Cell Metadata
,如果是针对全局的,可以直接在模板中修改; - 根据上一步的调整,在转换模板中进行相应调整;
- 根据HTML元素的调整或者样式修改的需要,在
custom.css
中修改。
幻灯片添加首页
在Jupyter Notebook中的Slide只有五种类型:
- Slide
- Sub-Slide
- Fragment
- Skip
- Notes
因此,对是不是首页并没有区分,那么我们可以自己添加这个信息,首先将Cell MetaData
修改为:
1 | { |
然后,在my_slides.tpl
中增加对homepage
的类属性:
1 | {%- if cell.metadata.get('homepage', False) -%} |
在使用Flask、Hexo的过程多少用过一些jinja2,对照着模板原来的内容,可以大概猜到它的作用就是:
- 默认cell不是homepage;
- 如果homepage为真,则添加homepage的class属性。
这样,生成的HTML文件里就会有class="homepage"
的幻灯片,即首页。我们在custom.css
中修改首页的样式,例如将文字居中:
1 | .homepage { |
这样就可以完成了添加首页的全过程。
添加页眉、页脚
与设置首页不同,页眉、页脚是对于所有的slides
都要添加,如果我们的页眉、页脚与幻灯片内容无关,我们只需要在模板文件里添加footer
和headbar
,而不需要对.ipynb
文件进行任何修改。
这部分的修改是在my_slides.tpl
的pre_slides
块,顾名思义,也就是在幻灯片之前设置的内容:
1 | {% block pre_slides %} |
与前面类似,我们还要设置headbar
和footer
的css样式,此处不再赘述。
需要注意的是,第4行的 nb.metadata.get('headbar', '')
其实是想在.ipynb
文件中添加页眉的内容,与Cell Metadata
不同,我们设置的是Notebook Metada
,具体如下图所示:
我们可以看到最终的页眉页脚以及首页效果:
幻灯片内容的出现消失
在演示的时候,我们经常有这样的动画需求:一项内容出现、然后消失,随之出现下一项内容。
查阅Reveal.js的文档可知,这一功能可以通过在Fragment
中添加current-visible
(新的文档似乎是fade-in-then-out
)的class属性。那么,类似对首页的修改,实现这一需求可以分为三步:
-
在
Cell Metadata
添加current_visible
项(注意,不是HTML中的current-visible
),其为真,表明该Fragement
需要先出现再消失; -
在模板中对
class=fragment
的元素,增加current_visible
的判断:1
2
3{%- if cell.metadata.get('fragment_start', False) -%}
<div class="fragment {{'current-visible' if cell.metadata.get('current_visible', False)}}">
{%- endif -%} -
此时,Reveal.js已经可以实现上述动画效果,但是缺点在于消失的元素仍会占据位置,原因是Reveal.js是通过
opacity
来控制其可见性的,我们可以在custom.css
中改为通过display
来控制:1
2
3
4
5
6.reveal .slides section .fragment {
opacity: 0;
display: none; }
.reveal .slides section .fragment.current-fragment {
opacity: 1;
display: inline; }有关Issue可参见GitHub项目。
总结
以上是对如何个性化设置Jupyter Notebook导出基于Reveal.js的HTML幻灯片的简单介绍,其核心包括:
- 修改
.ipynb
文件,包括Raw Cell
、Cell Metadata
或者Notebook Metadata
; - 修改
nbconvert
的转换模板; - 添加
custom.css
补充和覆盖样式; - 以上三项的协调配合。
当然,以上只是介绍了实现的技术手段,要想做得漂亮还是离不开审美的提高和精力的投入。实际上,Jupyter Notebook限制了Reveal.js的发挥,我个人觉得直接从Markdown文件出发,可以做出更加精美的幻灯片。