跳转至

Markdown 生成幻灯片

幻灯片 - markdown

使用 Markdown 一键生成精美幻灯片,先看演示。

选中幻灯片,试一试:

全屏模式 概览模式 演讲视图 暂停放映
F 进入,ESC 退出 OESC 进出 S 进入 B. 进出

一、渲染框架

普通编辑器一般不支持制作幻灯片演示文稿,但可以借助开源项目轻松将 markdown 文件转化为精美幻灯片。

1. Reveal.js

reveal.js,一个流行的 HTML 幻灯片展示框架,能让任何人免费制作功能齐全且精美的演示文稿。

  • 官方案例 和 第三方使用手册 - 使用案例
  • 丰富的插件生态:Plugins, Tools and Hardware
  • 推荐扩展:
    • reveal-md,打了类固醇的 reveal.js,从任何 Markdown 文件获取精美的 reveal.js 演示文稿;
    • Slidesdown,一款简洁快速的幻灯片演示框架,案例展示
    • MkSlides,一款专门用于制作幻灯片的静态网站生成器,案例展示
  • 特点区分:
    • reveal.js:直接在 HTML 中写代码驱动幻灯片;
    • reveal-md:是 reveal.js 的套装强化版,内置了很多插件包和工具包,可以自由扩展,使用JSON和JS文件配置;
    • Slidesdown:参考 reveal.js 修改的现成框架,集成了几乎所有幻灯片常用插件,开箱即用,但扩展性不好;
    • MkSlides:基于 reveal.js 和参考 reveal-md 和 MkDocs 的 Python封装优化版,仅使用一个 YAML文件配置所有属性,可自由扩展插件;✓

2. Slidev

Slidev (slide + dev),为开发者打造的极具灵活性和交互性的幻灯片制作工具。案例展示

3. Marp

Marp,使用 Markdown 编写演示文稿的生态系统。

  • Framework:Marpit,独立于 Marp 的轻量级框架;
  • CLI 界面:Marp CLI,将 Markdown 文件转换为静态 HTML / CSS、PDF、PowerPoint 文档和图像;
  • 集成编辑:Marp for VS Code,VS Code 扩展;
  • 推荐扩展:MarkSlides,基于 Marp 创建的幻灯片工具,支持使用 ChatGPT 等方式生成幻灯片,也可使用 在线编辑器

4. 其他

二、幻灯片输出

reveal.js 足够了。如果是开发者,不考虑复杂性和时间成本还可选择 Slidev。

  • 使用编辑器,方便简单,但功能有限:
    • Typora 支持导出幻灯片演示文稿。参考 使用 Pandoc 和 RevealJs 转换漂亮的演示文稿
    • MiaoYan 集成了 reveal.js,不需要导出直接在编辑器编写和预览;
    • YankNote 内置插件库,可扩展 reveal.js 后直接使用;
    • VS Code 也可以通过插件库扩展 reveal.js;
  • 使用原生 reveal.js,通过 CLI 操作,功能最丰富,更好用。

三、使用 Reveal.js

推荐 MkSlides,功能介绍还可参考 reveal.js markdown

1. 安装 MkSlides

pip3 install mkslides

mkslides serve demo.md
mkslides serve -o docs/

mkslides build demo.md -d _slide
mkslides build docs/ -d _slide

2. 操作提示

  • 前后翻页:Space / Shift+Space,还可以使用方向键;
  • 全屏模式:F 进入,ESC 退出;
  • 概览模式:OESC 进出;
  • 跳转页面:G 进入,输入页码,最后 Enter 确认;还可以用Shift+←/→ 跳转首/尾页;
  • 暂停放映:B. 进出;
  • 演讲视图:S 进入,可在 md 源文中使用 Note: 标记演讲备注;
  • 绘图白板:D 进入,ESC 退出,需要先安装插件 tldreveal
  • 全文搜索:CTRL+Shift+F 进出,Enter 匹配下一项;
  • 等比缩放:按住 Option (Alt) 同时鼠标单击需要缩放的位置;
  • 帮助提示:Shift+? 进出;
  • 菜单目录:M 或点击按钮进出,需要先安装插件 reveal.js-menu
  • 滚动视图:可通过URL激活,直接在URL后添加参数 ?view=scroll 后回车;
  • PDF 输出:可通过URL激活,直接在URL后添加参数 ?print-pdf 后回车,然后CTRL/CMD+P,利用系统打印功能另存为PDF;

3. 基础语法

  • 分页:默认用分割线 ---,可自定义,还可配置垂直分页符;
  • 标记演讲备注:使用 Note: ,默认只在演讲视图下能看到演讲备注;
  • 设置幻灯片及元素属性:通过 HTML注解的方式。最大程度避免对 Markdown 语法的污染;
<!-- 举例:markdown语法 -->

文本靠右 <!-- .element: style="float: right; width: 40%" -->
文本靠左 <!-- .element: style="width: 40%" -->
用法参考 <!-- .element: class="fragment" -->
<!-- .slide: data-background="#5EAF9E" -->

4. 背景设置

用法参考:

<!-- .slide: data-background="#5EAF9E" -->
<!-- .slide: data-background="#F8CB9E" data-background-transition="zoom" -->
<!-- .slide: data-background-gradient="radial-gradient(#283b95, #17b2c3)" -->

<!-- .slide: data-background-image="assets/schoonmeersen.jpg" -->

<!-- .slide: data-background-video="xx.mp4" data-background-video-loop data-background-video-muted -->

<!-- .slide: data-background-iframe="https://revealjs.com/demo" data-background-interactive -->

通过 data-background 配置,支持 4 种不同类型:

类型 复合属性
data-background-color data-background-gradient
data-background-image data-background-size
data-background-repeat
data-background-opacity
data-background-video data-background-video-loop
data-background-video-muted
data-background-iframe data-background-interactive

5. 片段控制

用法参考:

1. 有序列表项 一 <!-- .element: class="fragment" -->
2. 有序列表项 二 <!-- .element: class="fragment" -->
3. 有序列表项 三 <!-- .element: class="fragment" -->

- 我第一最后出来 <!-- .element: class="fragment fade-down" data-fragment-index="3" -->
- 我第二先出来 <!-- .element: class="fragment highlight-current-yellow" data-fragment-index="1" -->
- 我第三再出来 <!-- .element: class="fragment fade-up" data-fragment-index="2" -->

内联元素

举例:Markdown 是一种轻量级的纯文本标记语言,用简单少量的符号对文字进行标注,从而实现 以最小的输入代价生成印刷级排版格式的文档。 它在 流畅的书写和印刷级阅读体验 之间找到了平衡, 让人只需专注内容而不是纠结排版。

常用的标记符号不超过十个,好记好用好效果。

用法参考:

<p>
<span class="fragment" data-fragment-index="0">Markdown 是一种轻量级的纯文本标记语言,用简单少量的符号对文字进行标注,从而实现</span>
<span class="fragment" data-fragment-index="1" style="color:red">以最小的输入代价生成印刷级排版格式的文档。</span>
<span class="fragment" data-fragment-index="2">它在</span>
<span class="fragment" data-fragment-index="3" style="color:red">流畅的书写和印刷级阅读体验</span>
<span class="fragment" data-fragment-index="3">之间找到了平衡,</span>
<span class="fragment" data-fragment-index="4">让人只需专注内容而不是纠结排版。</span>
</p>
<p class="fragment" data-fragment-index="5">常用的标记符号不超过十个,好记好用好效果。</p>

6. 动画补充

上一节 片段控制 就是动画设置,但还可以安装插件 reveal.js-appearance,补充更多动画效果,使用参考 demo-markdown

<!-- .element: class="animate__bounceInLeft" -->
<!-- .element: class="animate__fadeInDown" data-split="words" -->
<!-- .element: class="animate__fadeInDown" data-split="letters" -->
<!-- .element: class="animate__fadeInDown" data-delay="200" -->
<!-- .element: class="animate__fadeInDown animate__slow" -->
<!-- .element: class="animate__fadeInDown animate__faster" data-split="words" data-delay="200" -->
<!-- .element: class="animate__fadeInUp animate__faster baseline" data-split="words" data-delay="80" data-container-delay="600" -->
<!-- .element: class="animate__flipInX demoimg" -->
<!-- .slide: data-autoappear="true" -->

7. 排版布局

详细教程,参考另一篇文档:CSS排版布局

8. 动态代码

在代码块中用行号控制,直接在起始标记符 ``` 后面用中括号配行号方式设置。

  • 行号高亮:[3,8-10],第3行和第8-10行高亮;
  • 分步高亮:[3-5|8-10|13-15],开始高亮 3-5 行,下一步 8-10,最后 13-15 行;
  • 行号偏移:[30:],行号从 30 开始;
  • 组合使用:[30: 1|2-4|5]
import { withTable, useTable } from 'table-render';
const Page = () => {
    const { refresh } = useTable();
}
export default withTable(Page)

四、配置与扩展

通过配置与扩展可实现幻灯片的各种自定义功能。有 2 种配置方式:

  • 全局配置,使用配置文件;
  • 独立配置,在 md 源文中使用 YAML 前言;

如果同时使用,则 YAML 前言会覆盖配置文件的选项。一般用配置文件配好通用选项,个别特例再用 YAML 前言调整。选项参考 reveal.js config

1. 使用 MkSlides

独立配置-YAML前言

可在 md源文中使用 YAML Front Matter 来设定单个文档属性,参考顶部:

title: Markdown 幻灯片
slides:
    separator: '^\s*<!--h-->\s*$'
    separator_vertical: '^\s*<!--v-->\s*$'
    theme: solarized  # league sky
revealjs:
    transition: slide
    navigationMode: linear
    separator_notes: '^Notes?:'

全局配置-配置文件

MkSlides 使用一个 mkslides.yml 配置所有属性、插件、预处理

  1. 在文档根目录创建 docs 和 plugin 文件夹,分别用来存放 markdown 文档和外部插件;
  2. 在文档根目录创建 mkslides.yml(会被自动拾取),内容参考:
index:
  title: Aaron - Slides
slides:
  theme: sky
  highlight_theme: monokai-sublime
  separator: '^\s*<!--h-->\s*$'
  separator_vertical: '^\s*<!--v-->\s*$'
  separator_notes: '^Notes?:'
  preprocess_script: preproc.py    # 预处理脚本
revealjs:
  width: 1280
  height: 800
  transition: slide
  navigationMode: linear
  menu:
    titleSelector: 'h1, h2, h3'
    hideMissingTitles: true
    openButton: true
    openSlideNumber: true
plugins:
  - name: RevealMermaid
    extra_javascript:
      - ../plugin/reveal.js-mermaid-plugin/plugin/mermaid/mermaid.js
  - name: RevealMenu
    extra_javascript:
      - ../plugin/reveal.js-menu/menu.js
  - name: RevealPlantUML
    extra_javascript:
      - ../plugin/revealjs-plantuml/dist/plantuml.js

预处理-Python

可为 MkSlides 提供一个 Python 脚本,用来批处理文档格式的自定义调整。

  • 例如,让标题自动创建新幻灯片,可使用 preproc.py,将其路径配置在 mkslides.yml 中:
def preprocess(markdown_text: str) -> str:
    lines = markdown_text.split('\n')
    result = []
    first_heading_found = False
    for index, line in enumerate(lines):
        stripped = line.lstrip()
        if stripped.startswith('#'):
            if first_heading_found:
                result.append('\n<!--h-->\n' if len(stripped.split()[0]) <= 2 else '\n<!--v-->\n')
            else:
                first_heading_found = True
        result.append(line)
    return '\n'.join(result)

2. 使用 reveal-md

独立配置-YAML前言

可在 md源文中使用 YAML Front Matter 来设定单个文档属性,内容参考:

---
title: Markdown 幻灯片
separator: '^\s*<!--h-->\s*$'
verticalSeparator: '^\s*<!--v-->\s*$'
theme: sky
revealOptions:
  transition: slide
  navigationMode: linear
  separator_notes: '^Notes?:'
---

全局配置-配置文件

reveal-md 使用 JSON 和 JS 文件配置全局文档属性,以及插件

  1. 在文档根目录创建 docs 和 plugin 文件夹,分别用来存放 markdown 文档和外部插件;
  2. 在文档根目录创建 reveal-md.jsonreveal.json,文件会被自动拾取;
  3. 在文档根目录创建 plugin.js 文件,用来配置插件名称,内容参考:
    options.plugins.push(
        RevealMermaid,
        RevealPlantUML,
        RevealMenu,
        RevealChart
    );
    
  4. reveal-md.json 中配置 reveal-md 扩展的选项、插件路径和 plugin.js 路径,内容参考:
    {
        "separator": "^\s*---\s*$",
        "verticalSeparator": "^\s*- - -\s*$",
        "theme": "league",
    
        "scripts": [
            "plugin/reveal.js-mermaid-plugin/plugin/mermaid/mermaid.js",
            "plugin/revealjs-plantuml/dist/plantuml.js",
            "plugin/reveal.js-menu/menu.js",
            "plugin/chart/chart.min.js",
            "plugin/chart/plugin.js",
            "plugin.js"
        ]
    }
    
  5. reveal.json 中配置 reveal 选项和插件属性,内容参考:
    {
        "transition": "slide",
        "navigationMode": "linear",
        "slideNumber": "c/t",
        "showSlideNumber": "speaker",
        "highlight_theme": "monokai-sublime",
        "separator_notes": "^Notes?:",
        "controls": false,
    
        "menu": {
            "titleSelector": "h1, h2, h3",
            "hideMissingTitles": true,
            "openButton": true,
            "openSlideNumber": true
        },
        "mermaid": {
            "theme": "base",
            "themeVariables": {
                "primaryColor": "#43464A",
                "primaryTextColor": "#E3DB82",
                "lineColor": "#F2AA3B"
            }
        },
    }
    

预处理-JS

可为 reveal-md 提供一个 JS 脚本,用来批处理文档格式的自定义调整。

  • 例如:让标题自动创建新幻灯片,可使用 preproc.js
module.exports = (markdown, options) => {
  return new Promise((resolve, reject) => {
    var firstHeadingFound = false;
    return resolve(
      markdown
      .split('\n')
      .map((line, index) => {
        const stripped = line.trimStart();
        if (stripped.startsWith('#')) {
            if (firstHeadingFound) {
                return (stripped.split(' ')[0].length <= 2 ? '\n<!--h-->\n\n' + line : '\n<!--v-->\n\n' + line);
            } else {
                firstHeadingFound = true;
            }
        }
        return line;
      })
      .join('\n')
    );
  });
};

终端运行时,把脚本路径配置在参数里:

reveal-md --preprocessor preproc.js slide.md  --static _site

3. 目录树

.
├── docs
   ├── slide.md
   └── xxx.md
├── plugin
   ├── reveal.js-menu
      └──menu.js
   └── reveal.js-mermaid
       └──mermaid.js
├── plugin.js
├── preproc.js
├── reveal-md.json
├── reveal.json
├── preproc.py
└── mkslides.yml

五、AI 加持

可以用 AI 自动生成代码,比如上面的 预处理 脚本。

可以把需求提前翻译成英文(避免翻译损耗),输出结果更精准。

1. Prompt

写一段解析 markdown 文本的代码,要求如下:
1. 在一级标题(#)和二级标题(##)前一行添加分隔符 -h-,对于其他级别的标题,添加 -v-;
2. 忽略第一次的标题的添加动作;
3. 要考虑标题前有空格的情况;
4. 用 JavaScript 或 Python 实现;

Write a piece of code that parses markdown text with the following requirements:
1. Add the separator -h- to the line before the first-level heading (#) and the second-level heading (##), and -v- for the headings at other levels;
2. Ignore the first action of adding titles;
3. Consider the case where there is a space before the title;
4. Implemented in JavaScript or Python;

2. Claude 解析

Claude,我只能说:🐮👍。需求理解、 代码质量、代码注释、结果预演等样样到位,甚至连示范输出都考虑了各种情况。

需求理解

1. Takes a markdown text as input and processes it line by line
2. Identifies heading lines by checking for '#' symbols at the start (after removing leading spaces)
3. Adds the appropriate separator:
4. '-h-' for level 1 and 2 headings (# and ##)
5. '-v-' for all other heading levels
6. Ignores the first heading encountered (no separator added)
7. Handles cases where there are spaces before the heading markers
8. Preserves the original formatting and spacing

结果预演

Python实现

Claude 3.5 Sonnet

def preprocess(markdown_text: str) -> str:
    lines = markdown_text.split('\n')
    result = []
    first_heading_found = False

    for index, line in enumerate(lines):
        stripped = line.lstrip()
        if stripped.startswith('#'):
            if first_heading_found:
                result.append('\n<!--h-->\n' if len(stripped.split()[0]) <= 2 else '\n<!--v-->\n')
            else:
                first_heading_found = True
        result.append(line)

    return '\n'.join(result)

3. ChatGPT 解析

中规中矩,代码都可以使用。

Python 实现

ChatGPT 4o

def parse_markdown(md_text: str) -> str:
    lines = md_text.split("\n")
    result = []
    first_title_found = False  # Flag to ignore the first heading

    for line in lines:
        stripped_line = line.lstrip()  # Remove leading spaces for title detection
        if stripped_line.startswith("#"):
            if not first_title_found:
                first_title_found = True  # Skip adding separator for the first title
            else:
                if stripped_line.startswith("##"):
                    result.append("\n<!--h-->\n")  # Separator for ## headings
                elif stripped_line.startswith("#"):
                    result.append("\n<!--h-->\n")  # Separator for # headings
                else:
                    result.append("\n<!--v-->\n")  # Separator for other headings
        result.append(line)  # Add the original line

    return "\n".join(result)

六、示范 Demo

完整示范见 md-slideshow

评论