flowchart LR qmd["QMD:结构与教学内容"] metadata["_reveal.yml:公共元数据"] deckCss["同名目录:专用 CSS"] behavior["HTML/JS:交互行为"] rCode["R:数据与 Plotly 图形"] quarto["Quarto + knitr + Pandoc"] reveal["Reveal.js HTML"] browser["浏览器课堂运行"] metadata --> quarto qmd --> quarto deckCss --> quarto rCode --> quarto behavior --> quarto quarto --> reveal reveal --> browser
本文记录 slide/slide-reactive/ 交互课件经过多轮开发后形成的设计原则和工程约定。它不是某一个互动功能的源码讲解,而是整个课件体系的入口文档:先回答“文件放在哪里、配置写在哪里、各层负责什么”,再说明“怎样新增、验证和维护一份课件”。
投票图与引用跳转的逐行 JavaScript 说明,参见《用 JavaScript 做 Reveal.js 课堂互动》。本文只保留其架构结论和开发接口,不重复解释每一行脚本。
1 设计目标
交互课件同时服务课堂展示、现场操作和后续人工审核,因此需要满足五个目标:
- 一致:title slide、章节页、普通标题和结束页保持统一视觉语言。
- 专用:一份讲义独有的 CSS、脚本、数据和图片能够快速定位。
- 共享:真正跨课件复用的配置和样式只维护一份。
- 可复现:R 模拟、图形和默认交互状态能够由固定代码重新生成。
- 可验证:渲染成功不等于功能正确,必须在浏览器中检查动态行为。
这里最重要的判断不是“CSS 都放一起、JavaScript 都放一起”,而是按责任和作用域组织资源。文件类型相同,不代表生命周期相同。
2 当前课件体系
活动入口 slide/slide-reactive/list-activities.qmd 当前连接四份互动讲义:
chpt01-em-language.qmd:计量经济学语言;包含课堂选择题和章节页。chpt01-act-em-schools.qmd:客观世界与思维模型中文稿;包含投票图和文内引用导航。chpt01-act-em-schools-en.qmd:上一份讲义的英文版本;共用同一套专用资源。chpt10-multicollinearity.qmd:多重共线性;由 R 生成数据和 Plotly 图形,JavaScript 负责播放与尺寸协调。
chpt00-revealjs-basic.qmd 是 Reveal.js 基础示例,没有接入 _reveal.yml,不应把它误当作当前互动课件规范的实现样板。
2.1 每套讲义的专用资源
slide/slide-reactive/
├── _metadata.yml
├── _reveal.yml
├── list-activities.qmd
├── scss/
│ ├── reveal-common.css
│ ├── title-slide.html
│ └── title-slide.scss
├── chpt01-em-language.qmd
├── chpt01-em-language/
│ ├── slides.css
│ ├── interaction.html
│ └── chpt11-mroz2-eq-2sls-adj.png
├── chpt01-act-em-schools.qmd
├── chpt01-act-em-schools-en.qmd
├── chpt01-act-em-schools/
│ ├── slides.css
│ ├── poll-chart.html
│ └── cite-nav.html
├── chpt10-multicollinearity.qmd
└── chpt10-multicollinearity/
├── slides.css
├── R/
│ ├── mc-data.R
│ └── mc-plotly.R
└── js/
└── plotly-interaction.html
目录树表达了两个层级:
scss/和_reveal.yml是公共层。- 与主 QMD 同名的文件夹是讲义专用层。
中英文版本不是两套独立产品。chpt01-act-em-schools.qmd 与 chpt01-act-em-schools-en.qmd 属于同一课件家族,因此共同使用 chpt01-act-em-schools/。
3 从源文件到课堂页面
各层只承担一种主要职责:
- QMD:标题层级、正文、原生 HTML 标记、课堂说明和 speaker notes。
- YAML:格式、资源入口、作者信息和渲染参数。
- CSS/SCSS:布局和视觉,不计算业务状态。
- JavaScript:事件、状态变化、Reveal API 和 Plotly 浏览器行为。
- R:可复现数据、统计模型和渲染前图形构造。
把计算、外观和行为混进同一个文件,短期看似方便,长期会使审核者无法判断某次修改影响哪一层。
4 配置继承与视觉系统
4.1 四层配置链
互动课件不是只受 QMD 顶部 YAML 控制。实际配置从外到内依次为:
- 项目根
_quarto.yml:网站类型、_site输出目录、全局语言、bibliography、freeze: auto和 post-render; slide/slide-reactive/_metadata.yml:该目录默认隐藏代码回显;- 讲义通过
metadata-files引入的_reveal.yml:互动课件公共 Reveal 配置; - 单份 QMD:标题、footer、专用 CSS/脚本和个别导航选项。
越靠近文档的设置越具体。list-activities.qmd 显式使用 format: html,所以它是网站列表页而不是 Reveal deck。不要把 include-after-body 放入目录 _metadata.yml 或 _reveal.yml,否则列表页和无关讲义可能加载不属于自己的脚本。
4.2 公共 YAML
每份规范化互动课件通过下面的元数据入口继承公共配置:
metadata-files:
- _reveal.yml_reveal.yml 负责:
1600 × 900画布、页边距与auto-stretch;- 公共日期、中文作者和单位;
- title slide 背景;
- Logo、页码、控制器和 chalkboard;
title-slide.html与title-slide.scss;reveal-common.css。
讲义 QMD 只保留自己的 title、subtitle、footer 和专用资源。英文稿在本地覆盖 lang、英文作者和英文单位,但继续继承背景、画布和公共主题。
这一做法遵循“默认值集中,差异值就近”原则。不要把每份讲义都相同的作者块和背景参数复制四遍。
4.3 CSS 分层
公共 CSS 位于 scss/reveal-common.css,只放多套不同课件都需要的规则:
- H2 标题颜色和浅蓝下边线;
- H1 章节页样式库;
.center-h结束页基础行为;- Logo、footer 和 slide number。
专用 CSS 位于同名目录的 slides.css:
.eml-*、.mcq属于计量经济学语言课件;.quiz-*、.poll-*、.stage-*、.cite-*属于学派互动课件;.lift-split、.plane-tall、.tf-slide属于多重共线性课件。
判定一条规则是否公共时,按“课件家族”而不是按 QMD 数量计算。中英文稿同时使用 .poll-layout,仍然只算一个课件家族,因此它是专用样式。
CSS 选择器应以 .reveal 开头,减少对普通网站页面的污染。专用类还应带稳定前缀,例如 eml-、poll- 或 mc-,不要创建含义模糊的 .box、.left、.blue。
4.4 title slide
title slide 由三部分共同完成:
_reveal.yml提供元数据和背景参数;scss/title-slide.html决定标题、作者、单位和日期的 HTML 结构;scss/title-slide.scss调整作者区布局。
修改 title slide 前先判断需求属于“数据、结构还是外观”,分别改 YAML、HTML partial 或 SCSS。不要为了移动作者姓名而修改每份 QMD。
4.5 H1 章节页
H1 用于纯章节分隔页。公共样式提供一个基础类和四个变体:
# 第一部分 {.section-title .section-title--academic}可用变体:
.section-title--academic:学术蓝渐变和细网格,当前默认。.section-title--image:共用封面图叠加深色遮罩。.section-title--minimal:浅色留白和左侧强调条。.section-title--spotlight:深蓝背景和柔和光晕。
每张 H1 页使用一个变体即可。变体负责视觉差异,.section-title 负责共同布局。不要复制整段 CSS 创建“几乎一样”的第五种风格。
4.6 H2 普通页面
H2 是日常内容页标题,由公共 CSS 自动添加浅蓝线条。QMD 不需要逐页加 class:
## 四句话,四个身份只有明确的特殊页面才能覆盖这一规则。覆盖应放在该讲义的 slides.css,并以页面 class 限定作用域。
4.7 end slide
结束页只保留一个 H2,不再叠加讲义专用正文:
## 互动练习结束{.center-h background-color="aliceblue" background-image="../pic/thank-you-gif-funny-fan.gif" background-size="600px" background-position="center" background-opacity="0.8" style="font-size:65px !important"}英文稿可以本地化标题文字,结构与视觉参数保持一致。公共 CSS 会在 .center-h 页面取消 H2 下边线。
5 专用资源管理
5.1 文件夹命名
主文件 name.qmd 对应 name/。语言后缀不参与资源文件夹命名:
chpt01-act-em-schools.qmd
chpt01-act-em-schools-en.qmd
chpt01-act-em-schools/
文件少时可直接放在目录根部;资源类型变多后再使用 R/、js/ 等子目录。不要预先创建一批空目录。
5.2 哪些资源必须专用
以下内容只要服务单一课件家族,就进入同名目录:
include-after-body引入的 HTML/JavaScript;- 讲义专用
slides.css; - R helper;
- 数据文件;
- 只在该课件出现的图片;
- 与该课件一起维护的说明或测试素材。
不要建立目录级公共 js/ 来收纳互不相关的脚本。脚本是否公共由业务行为决定,而不是由扩展名决定。
5.3 哪些资源可以公共
只有出现明确跨课件需求时,资源才进入公共层:
- 统一 title partial 和主题;
- 全部互动讲义的画布与导航配置;
- 多套课件共同使用的标题、Logo、footer 和页码样式;
- 多套讲义真正共用的
slide/pic/图片。
“未来也许会复用”不是公共化理由。先专用,出现第二个独立课件家族后再抽取。
6 交互实现技术栈
6.1 纯 JavaScript 与 HTML 标记
轻量交互使用浏览器原生 API,不引入额外前端框架。主要技术包括:
querySelector/querySelectorAll;dataset和data-*协议;addEventListener;classList;- SVG DOM;
Reveal.on、Reveal.getIndices、Reveal.slide;- Plotly 浏览器 API。
HTML class 服务 CSS,data-* 服务 JavaScript。不要让脚本通过中文显示文本猜测业务含义。
脚本通常用 IIFE 隔离变量:
<script>
(function () {
// deck-specific behavior
})();
</script>若脚本依赖页面 DOM,应等待 DOMContentLoaded;若依赖 Reveal 当前页尺寸,还应监听 ready 和 slidechanged。
6.2 投票柱状图
chpt01-act-em-schools/poll-chart.html 使用原生 SVG:
- 从
.poll-input[data-poll]读取人数; - 校验并合计数值;
- 清空旧 SVG;
- 重画网格、柱子、数字和标签;
- 在输入变化、Reveal ready 和 slidechanged 时重新绘制。
脚本按 .poll-layout 局部查询节点,因此一页存在多个互动区域时不会串值。英文稿通过 data-poll-lang="en" 切换标签,而不是复制一份英文脚本。
6.3 引用跳转
cite-nav.html 通过 data-cite-nav 建立正文编号和参考资料之间的双向连接。普通 href="#id" 不足以驱动 Reveal 翻页,因此脚本先找到目标所属的 <section>,再调用 Reveal API。
事件监听放在 document 上,属于事件委托。它能处理后续生成的节点,也减少重复 listener。
6.4 选择题即时反馈
chpt01-em-language/interaction.html 查找每个 .mcq:
data-answer保存正确选项;data-explain保存正确解释;- 按钮的
data-choice表示作答; is-correct/is-wrongclass 控制视觉状态;aria-pressed和aria-live提供基础无障碍反馈。
题干和答案属于教学内容,应留在 QMD;脚本只实现通用判定过程。
6.5 R、knitr 与 Plotly
多重共线性课件把统计计算放在 R:
mc-data.R使用固定种子、MASS::mvrnorm、lm和stats生成可复现样本与模型结果;mc-plotly.R使用plotly构建二维、三维和动画图形;htmlwidgets::onRender把必要配置挂到浏览器图形节点;plotly-interaction.html负责播放、暂停、翻页停止和尺寸重算。
统计机制与早期可视化探索参见《多重共线性问题》;本指南关注现行课件的工程实现。
R 负责“数据和图是什么”,JavaScript 负责“浏览器里怎样操作”。不要在 JavaScript 中重新实现统计模拟,也不要为一次点击重新运行 R。
三维图页使用 data-prevent-swipe="true",避免拖动图形时被 Reveal 解释成翻页手势。
6.6 R 环境与 freeze
仓库使用 renv.lock 固定 R 依赖;首次在新环境渲染前,应先恢复项目环境:
renv::restore()项目设置 execute.freeze: auto。目前四份互动讲义中,只有多重共线性课件执行 R,因此修改 mc-data.R、mc-plotly.R 或相关 chunk 后,需要确认 _freeze/slide/slide-reactive/chpt10-multicollinearity/ 是否应随源代码更新。
普通 CSS、JavaScript 或文字修改不应无差别提交其他文章的 freeze 变化。若缓存结果明显未刷新,可显式刷新该讲义:
quarto render "slide/slide-reactive/chpt10-multicollinearity.qmd" --cache-refresh7 标准开发流程
7.1 定义课堂行为
先写清楚:
- 学生要做什么;
- 教师何时操作;
- 页面状态怎样变化;
- 是否需要中英文;
- 是否需要可复现数据。
不要从“我要写一个脚本”开始。交互代码是课堂流程的实现,不是设计目标。
7.2 创建主文件和同名目录
新课件建议从最小头部开始:
---
title: "课件标题"
subtitle: "课件副标题"
metadata-files:
- _reveal.yml
format:
revealjs:
footer: "章节|主题"
css: chptXX-topic/slides.css
include-after-body: chptXX-topic/interaction.html
---没有专用 CSS 或 JavaScript 时不要写空配置。
7.3 先完成静态结构
先让 QMD 在没有 JavaScript 时仍能表达问题和结论。为交互区域选择稳定 class,并用 data-* 写机器可读的键。
7.4 添加专用视觉
只在 name/slides.css 中写本讲义规则。确认相同需求已经存在于其他课件后,才考虑移动到 reveal-common.css。
7.5 添加行为
脚本必须:
- 限定查询根节点;
- 对缺失节点安全退出;
- 不污染全局变量,除非确实要暴露给 Plotly;
- 不依赖页面显示文字;
- 处理 Reveal ready / slidechanged;
- 更新可访问状态。
7.6 添加可复现计算
R 模拟应固定 seed,并将数据生成、模型拟合和绘图函数分开。QMD 中只保留调用顺序和教学内容,复杂函数进入 R/。
7.7 更新活动入口
新讲义确认可用后,再加入 list-activities.qmd。入口链接到 QMD,让 Quarto 建立项目依赖和最终 HTML 链接。
7.8 单独渲染
在项目根目录执行:
quarto render "slide/slide-reactive/chptXX-topic.qmd"一次修改多份课件时逐份渲染。这样可以明确是哪一份失败,也避免把目录中实验稿一并构建。
全站发布使用:
quarto render全站渲染结束后,项目会执行 R/copy-post-sync.R,把 _site 镜像到同步目录。单份讲义开发阶段不应把同步结果当作源文件修改。
8 验证与回归测试
8.1 静态检查
- YAML 缩进正确;
- 所有资源路径存在;
- 生成 HTML 同时加载
scss/reveal-common.css和专用slides.css; - 页面没有引用已移动的旧路径;
- CSS 选择器没有泄漏到无关讲义;
- 浏览器控制台没有 JavaScript 错误。
8.2 视觉检查
- title slide 的背景、作者、单位和日期正确;
- H1 章节页铺满课件画布,文字对比度足够;
- H2 有统一浅蓝线;
- end slide 只有标题,没有残留正文;
- Logo、footer 和页码不遮挡内容;
- 1600 × 900 下不溢出;
- 缩小窗口后仍能阅读。
8.3 功能检查
学派互动课件:
- 改一个人数后,合计和柱高同时变化;
- 中英文标签正确;
- 点击引用编号进入参考资料;
- 点击“返回 / Back”回到正文并短暂高亮。
计量经济学语言课件:
- 点击答案后只保留一个按钮状态;
- 正确与错误反馈文字、颜色和
aria-pressed正确。
多重共线性课件:
- Plotly 能旋转、缩放和选择样本;
- Play / Pause 正常;
- 翻页后计时器停止;
- 重新进入页面后图形尺寸正确;
- 拖动三维图不会触发 Reveal 翻页。
8.4 本地浏览器检查
渲染后可临时启动静态服务器:
python -m http.server 8765 --directory _site然后打开:
http://127.0.0.1:8765/slide/slide-reactive/chptXX-topic.html
不要只用 file:// 检查;资源路径、模块策略和浏览器安全限制可能与 HTTP 环境不同。
8.5 PDF 与离线边界
公共配置当前使用 embed-resources: false,因此互动课件依赖 _site 中的 CSS、图片和脚本,不是可随意单独复制的自包含 HTML。课堂使用应发布完整站点目录。公式离线排版见《全站数学公式的本地 MathJax》。
chpt00-revealjs-basic.qmd 及 topics-reveal.qmd 保留了 Chrome 与 decktape 的 PDF 对照材料,可用于导出方法参考;它们是基础实验,不是新互动课件的配置模板。PDF 无法完整保留输入框、按钮、动画和引用跳转,发布前应把“可打印讲义”和“课堂交互 HTML”视为两种不同产物。
9 版本控制与迭代规范
9.1 小步提交
一次提交只解决一个明确问题,例如:
- 归位一套课件资源;
- 抽取公共 CSS;
- 新增一个互动;
- 统一 title / heading / end slide;
- 修复 Plotly 翻页尺寸。
资源移动与功能重写最好分开提交,便于审核路径变化和行为变化。
9.2 精确暂存
Quarto 渲染可能修改 _freeze/ 或生成主题文件。提交前使用 git status 和 staged diff,只暂存本次源文件。除非明确要刷新冻结结果,否则不要顺手提交无关 _freeze 变化。
数据文件、图片和旧稿也不能因为“在同一目录”就自动加入提交。先确认它们被当前 QMD 引用。
9.3 修改后的最小回归集合
- 改
_reveal.yml、title partial 或reveal-common.css:渲染四份规范化互动课件。 - 改某个
name/slides.css:渲染该课件家族的全部语言版本。 - 改共享脚本:检查所有使用该脚本的语言版本。
- 改 R helper:重新渲染多重共线性课件并操作关键 Plotly 页。
- 只改教学文字:至少渲染该 QMD,检查分页和溢出。
10 常见反模式
10.1 把专用规则放进公共 CSS
后果是所有课件都下载无关样式,并且同名 class 容易发生覆盖。解决办法是搜索 class 实际使用位置,按课件家族判定归属。
10.2 建立目录级公共 JavaScript 杂物箱
js/ 看起来整齐,却失去“这个脚本属于哪份课件”的信息。专用脚本应跟随同名目录。
10.3 中英文复制两份逻辑
复制会让修复只落在一个版本。应共用脚本,用 lang 或 data-* 提供语言差异。
10.4 通过可见文字驱动逻辑
文本翻译、标点或排版变化都会让脚本失效。应用稳定的 data-choice、data-poll、data-cite-nav。
10.5 只看渲染退出码
Quarto 成功只能证明生成了 HTML,不能证明柱状图画出、引用能翻页、MCQ 能反馈或 Plotly 能 resize。
10.6 把所有内容塞进 QMD
长 CSS、复杂 R 函数和完整 JavaScript 混在正文中会妨碍教学审核。QMD 应尽量呈现“课堂讲什么”,专用资源文件呈现“页面怎样工作”。
10.7 未验证就抽象
只有一个课件使用的规则先保持专用。过早抽象会产生难懂的全局配置和兼容负担。
11 Cursor 项目规范
项目当前的相关规则是:
.cursor/rules/reactive-slide-resources.mdc
规则会在编辑 slide/slide-reactive 下的 QMD、HTML、CSS、SCSS、YAML 和 R 文件时提供上下文,核心要求包括:
- 公共层与专用层分离;
- 语言版本共用同名目录;
- 公共 title、H1、H2、end slide 规范;
- 移动资源后同步更新讲义、语言版本和技术笔记;
- 修改后渲染并核对公共 CSS 与专用 CSS。
项目目前没有 .cursor/skills/ 下的项目级 skill。因此,Cursor rule 是自动生效的约束,本文是供人和 Agent 阅读的完整解释。如果以后形成稳定、重复且需要执行命令的自动化流程,例如“创建新互动课件骨架并完成浏览器验收”,再把该流程实现为项目 skill;不要用 skill 重复存放本文全部知识。
12 维护原则
今后的修改应始终回答三个问题:
- 这是内容、外观、行为还是数据问题?
- 它服务一套课件,还是至少两个独立课件家族?
- 怎样证明修改在浏览器课堂环境中有效?
只要坚持“职责分层、资源就近、差异显式、渲染加浏览器验证”,slide-reactive 就能在增加课件数量和交互复杂度后,仍然保持可审核、可维护和可复用。