用 Web 技术做一套数据结构微课
用 Web 技术做一套数据结构微课:从知识点到可发布视频
本文记录《数据结构微课》项目的技术设计与制作流程。项目成片已发布到:
- B 站:https://www.bilibili.com/video/BV1PNRxB7EWq/
- 抖音:https://www.douyin.com/video/7636641569991573426
项目代码计划开源在 GitHub:https://github.com/haodehaode378/video-pipeline。目前项目仍处于开发和整理阶段,后续会逐步补充完整的源码、制作脚本、示例工程和使用说明。
1. 为什么用前端技术做微课视频
数据结构的难点不在于定义本身,而在于学生很难在静态文字里看清“状态如何变化”。例如顺序表插入时元素为什么要移动,链表插入时指针为什么只改两条边,二叉树遍历为什么会得到不同序列,DFS 和 BFS 为什么访问顺序不同。
因此,这套微课没有采用传统 PPT 录屏,而是把每一节课拆成可编程的动画场景。核心思路是:
知识点脚本 -> HTML 场景 -> CSS/JS 动画 -> 浏览器预览 -> 视频渲染 -> 分段配音 -> 音视频合成 |
前端技术的优势在于可控。每一个节点、数组格、指针、栈顶标记、队头队尾标记都可以被精确定位和高亮;每一个时间点都能通过 URL 参数复现,便于截图检查和局部修正。
2. 内容结构:6 集覆盖核心脉络
这套微课按“先建立整体地图,再讲典型结构和操作”的顺序组织,共 6 集:
| 集数 | 主题 | 核心目标 |
|---|---|---|
| 01 | 数据结构到底学什么 | 建立逻辑结构、存储结构、数据运算的总览 |
| 02 | 顺序表 vs 链表 | 对比连续存储和指针存储的访问、插入、删除差异 |
| 03 | 栈和队列 | 理解 LIFO、FIFO、top、front、rear 和循环队列 |
| 04 | 树与二叉树遍历 | 理解根、左右子树、先序、中序、后序、层次遍历 |
| 05 | 图:多对多关系与遍历 | 理解顶点、边、邻接矩阵、邻接表、DFS、BFS |
| 06 | 查找与排序 | 理解顺序查找、二分查找、散列查找和常见排序思想 |
每一集都遵循固定节奏:
标题引入 -> 概念解释 -> 动画演示 -> 关键代码/公式 -> 一句话总结 |
这样做的好处是学生每次打开视频都能预期学习路径:先知道这一集解决什么问题,再看到结构如何变化,最后用一小段代码或公式把图形过程落到实现层。
3. 工程结构:每集都是一个可运行网页
项目将每一集放在独立目录中:
videos/ |
每集基本包含:
index.html # 控制台预览入口 |
这个目录设计有一个重要原则:课程内容、动画逻辑、配音素材和导出结果放在同一个 episode 边界内。这样第 3 集调整栈和队列时,不会影响第 4 集二叉树;第 5 集重新配音时,也不需要改全局工程配置。
index.html 不是普通网页,而是一个固定比例的视频舞台。根节点会声明视频尺寸、总时长和 composition id:
<main |
这里的 data-width、data-height 和 data-duration 不是装饰字段,而是后续预览、截图和渲染的统一协议:
- 浏览器预览时,脚本按 1920x1080 等比缩放舞台,避免不同屏幕尺寸导致布局变形。
- 截图时,自动化工具读取同一个入口,通过
?t=120定位到第 120 秒。 - 渲染时,导出脚本可以按 composition id 和 duration 推断视频范围。
每个场景用 section.scene 表示,并通过 data-start 和 data-duration 标注时间范围:
<section class="scene scene-stack-push bg-yellow" data-start="78.90" data-duration="22.86"> |
这种写法相当于把一条视频拆成多个“时间片”。每个时间片内部只关心自己的教学对象,例如栈场景只处理 top 指针和栈元素,队列场景只处理 front、rear 和数组格。相比把所有动画写在一个长函数里,这种结构更容易定位问题:某个画面错了,先看它属于哪个 section.scene,再看对应的局部时间计算。
submit.html 则承担“干净渲染入口”的角色。开发阶段需要控制台、滑块、时间码,方便检查动画;正式提交或录制时,这些调试组件会干扰画面。因此项目保留两个入口:
| 入口 | 用途 | 特点 |
|---|---|---|
index.html |
开发预览 | 显示控制台、时间码、调试滑块 |
submit.html |
截图/渲染/提交 | 隐藏调试 UI,只保留视频画面 |
这样可以避免一个常见问题:为了临时录制把调试控件删掉,录完又要恢复。入口分离后,开发态和成片态各自稳定。
4. 时间轴设计:把视频变成可 seek 的状态机
微课动画最关键的不是“自动播放”,而是“任意时刻都能稳定复现画面”。项目里的 script.js 会暴露类似 window.__hfSeek(time) 的能力,用于把画面跳转到指定秒数。
这种设计解决了三个问题:
- 预览时可以拖动时间轴,快速检查某一秒的布局。
- 截图时可以打开
index.html?t=120,直接定位关键帧。 - 渲染时可以逐帧推进,保证导出视频和浏览器预览一致。
以第 3 集“栈和队列”为例,脚本中维护了旁白时间段、场景切换、元素高亮和控制台状态。栈的入栈、出栈,队列的 front/rear 移动,循环队列的取模回绕,都由当前时间计算出来,而不是依赖一次性播放动画。
核心实现可以概括成三步:
function updateScene(time) { |
第一步是根据全局时间 time 找到当前场景。第二步是把全局时间转换成场景内部的局部时间,例如第 3 集入栈场景从 78.90 秒开始,那么全局 84.90 秒对应局部 6 秒。第三步是用局部时间切换 CSS class,让元素进入、离开、高亮或移动。
这里没有使用 setTimeout 串联动画,原因很直接:setTimeout 适合从头播放,但不适合跳到任意一帧。如果学生反馈“第 2 分 10 秒的 top 指针不对”,工程上应该能直接打开 index.html?t=130 复现,而不是从头播放等到那一刻。
项目实际采用的是“状态派生”思路:
当前秒数 |
这和很多前端 UI 的状态管理类似:不要记录“动画已经播放到哪一步”,而是每次根据当前时间重新计算“此刻应该是什么状态”。这样即使拖动进度条、刷新页面、自动截图或逐帧渲染,画面状态也能保持一致。
第 3 集里几个典型状态映射如下:
| 知识点 | 时间状态 | 画面状态 |
|---|---|---|
| 顺序栈入栈 | pushLocal >= 2/6/10 |
A、B、C 依次添加 is-in |
| 栈顶变化 | pushLocal 落在不同区间 |
top 指针添加 at-a、at-b、at-c |
| 出栈 | popLocal >= 4 |
C 添加 is-out,B 变成 is-top |
| 队列入队 | queueLocal >= 2/7/12/17 |
rear 指针向后移动 |
| 循环队列 | circleLocal >= 9 |
rear 从末尾回到 0 号位置 |
技术上看,这套动画不是视频剪辑,而是一个由时间驱动的确定性 UI 状态机。
5. 视觉风格:Geometric Bold
这套微课采用 Geometric Bold 风格:高饱和纯色、粗黑边框、大字号标题、强几何图形。
选择这个风格不是为了装饰,而是因为数据结构本身很适合被转译成几何对象:
| 数据结构对象 | 画面表达 |
|---|---|
| 数组 | 等宽矩形格 |
| 链表节点 | 节点块 + 粗箭头 |
| 栈 | 竖向容器 + top 指针 |
| 队列 | 横向格子 + front/rear 指针 |
| 树 | 圆形节点 + 连接边 |
| 图 | 顶点网络 + 访问高亮 |
| 排序 | 柱状条 + 比较/交换颜色 |
视觉规则保持克制:
- 不使用渐变背景。
- 不使用模糊阴影。
- 主体元素使用纯色块和粗黑边。
- 代码只展示 2 到 4 行关键逻辑。
- 每一屏都必须有图形、流程图或结构图,避免变成文字卡片。
例如讲顺序栈时,代码只保留最核心的两行:
S.data[++S.top] = x; |
画面同步展示 top 指针上移、元素入格、元素出格、top 回退。学生看到的不是孤立语法,而是“代码变量”和“图形状态”的一一映射。
6. 配音流程:分段生成,统一对齐
配音没有采用整篇一次性生成,而是按场景切成多个小段:
assets/narration/ |
segments.csv 记录每段旁白的起止时间和文本文件。每个 txt 只对应一个明确的画面段落,例如“栈的概念”“push 动作”“pop 动作”“循环队列取模”。
这里的关键不是“把文本拆小”,而是让旁白也进入同一条时间轴。每一段旁白都有明确的开始秒数、结束秒数和对应文本:
start,end,file |
这样第 004 段就只服务于入栈动画,第 005 段只服务于出栈动画,第 006 段只服务于栈代码解释。画面时间和音频时间不再靠人工感觉对齐,而是由同一份分段表约束。
分段的好处很明显:
- 某一句念得不自然,只需要重生成这一段。
- 某个动画加长后,只需要调整对应段落。
- 音频可以用静音补齐或轻微变速对齐到目标时间。
- 最终再用 ffmpeg 将视频轨和旁白音轨合成。
项目中使用 PowerShell 脚本封装 MiniMax TTS、ffprobe 时长检测、音频对齐和 ffmpeg 合成。大致流程是:
读取 segments.csv |
音频对齐时有两个常见情况:
| 情况 | 处理方式 |
|---|---|
| 生成音频短于目标时长 | 在片段末尾补静音 |
| 生成音频略长于目标时长 | 轻微调整语速或回到文本压缩 |
这一步的技术价值在于“可局部返工”。如果第 3 集最后一句公式讲得太快,只需要改 011.txt 并重生成最后一段,不需要重新处理整集旁白。最终输出也会保留无声视频和配音视频两个版本,例如:
output/ |
保留无声版本可以让后续换声音、换语速、换平台字幕时更方便,避免每次都从浏览器重新渲染画面。
7. 质量检查:截图比肉眼拖进度条可靠
数据结构微课的画面问题通常出现在关键帧:元素刚移动完、指针刚切换、代码刚高亮、字幕刚进入。单纯播放一遍很容易漏掉文字越界或状态错位。
因此项目保留了大量 snapshots/ 截图,用于检查:
- 画布是否完整填满 16:9。
- 中文是否正常显示。
- 标题、字幕、代码是否越界。
- 节点、箭头、数组格是否对齐。
- 当前讲解的变量是否和图形状态一致。
submit.html是否隐藏控制台和调试时间码。
截图检查的具体做法是先列出每集的关键时刻,再用浏览器自动打开对应时间点:
index.html?t=3 |
这些时间点不是随便选的,而是来自场景起止时间和动作变化时间。例如栈入栈要截 A 入栈、B 入栈、C 入栈三个状态;循环队列要截 rear 到 3、到 4、回绕到 0 的状态;图遍历要截 DFS 正在深入、BFS 队列扩展、访问完成这几个状态。
对数据结构课程来说,截图验收不只是看排版,还要看“教学状态是否正确”:
| 检查对象 | 技术检查 | 教学检查 |
|---|---|---|
| 顺序栈 | top 指针位置是否和 CSS class 一致 |
是否体现后进先出 |
| 循环队列 | front/rear 是否正确回绕 |
是否解释清楚假溢出 |
| 二叉树遍历 | 节点高亮顺序是否正确 | 序列是否匹配先序/中序/后序定义 |
| 图遍历 | 顶点访问状态是否稳定 | DFS 和 BFS 的差异是否可见 |
| 排序动画 | 比较、交换、已排序颜色是否区分 | 学生能否看出算法过程 |
这种检查方式更接近前端 UI 自动化测试:不是“看起来差不多”,而是把每个关键状态固定下来,逐帧验收。尤其是 submit.html,必须单独检查,因为它是最终导出入口。开发入口没问题,不代表提交入口一定没问题;如果提交入口忘记隐藏控制台,最终视频就会带上时间码或滑块。
所以项目的验收标准可以写成一句话:同一秒钟,开发预览、截图结果、最终视频三者必须一致。
8. 技术取舍
这套方案的核心取舍是:用工程复杂度换取教学画面的可控性。
传统 PPT 更快,但状态变化不容易精确复现;专业动画软件表现力更强,但批量修改和版本管理成本高。HTML/CSS/JS 处在中间位置:开发成本可接受,又能把每个教学对象变成可维护的 DOM 状态。
更具体地说,项目做了几组取舍:
| 方案 | 优点 | 问题 | 本项目选择 |
|---|---|---|---|
| PPT 录屏 | 制作快、门槛低 | 精确动画和批量修改弱 | 不作为主方案 |
| AE/PR 动画 | 视觉表现强 | 版本管理和程序化复用成本高 | 只适合后期包装 |
| Canvas | 绘制能力强 | 文本排版和 DOM 调试不如 HTML 直观 | 局部可用,不作为主结构 |
| HTML/CSS/JS | 可维护、可截图、可复现 | 初期工程搭建更复杂 | 作为主方案 |
另一个取舍是“CSS class 驱动”而不是“直接改 style”。例如元素进入、指针移动、代码高亮都尽量通过添加 is-in、is-top、is-lit 等类名完成。这样 JS 负责状态,CSS 负责表现,两者边界比较清楚:
JS:现在 C 应该是栈顶 |
这会让调试更直观。打开浏览器开发者工具,看一个节点有没有 is-top,就能判断是逻辑没算对,还是样式没写对。
当前方案适合以下场景:
- 需要大量结构化图解。
- 需要反复修改局部动画。
- 需要批量生产同一视觉系统下的视频。
- 需要把知识点、代码、旁白、截图、成片统一纳入版本管理。
不适合的场景也很明确:
- 只做口播课,不需要复杂动画。
- 追求影视级特效。
- 内容变化极少,不需要工程化复用。
9. 后续自动化方向
项目已经形成了清晰流水线,后续可以继续自动化:
输入主题和知识点 |
这里最值得继续投入的是“自动检查”。因为 AI 可以生成脚本和代码,但最终教学质量仍然取决于画面是否清楚、节奏是否合理、代码是否真的对应动画。自动截图、关键帧验收、样式规则检查,比单纯追求一键生成更重要。
后续可以把自动化拆成三个层次:
第一层是文件级检查。脚本可以检查每集是否都有 index.html、submit.html、style.css、script.js、segments.csv 和 output/,以及根节点是否声明 data-composition-id、data-width、data-height、data-duration。
第二层是画面级检查。用 Playwright 打开每个关键帧,自动截图并保存到 snapshots/qa-current/。如果画布不是 16:9、出现横向滚动条、标题越界、字幕为空、调试控件出现在提交入口,就直接报错。
第三层是教学一致性检查。每个 episode 可以维护一份关键帧清单:
[ |
自动化脚本根据清单检查 DOM class 是否符合预期。这样不仅能发现“页面有没有渲染”,还能发现“该亮的元素有没有亮”。对于数据结构微课,这比单纯截图更重要,因为教学错误往往不是页面空白,而是某个指针、某个访问顺序、某个公式和旁白不一致。
如果继续扩展,还可以把 AI 生成内容纳入这个流程:AI 先生成脚本和场景代码,再由自动检查给出失败点,例如“第 145 秒 DFS 高亮节点和旁白不一致”“第 210 秒公式卡片超出舞台”。这样 AI 不只是生成代码,还能进入一个可验证、可迭代的制作闭环。
10. 总结
这套数据结构微课的技术路线可以概括为一句话:
用前端工程的方法,把抽象的数据结构状态变化变成可定位、可复现、可检查、可合成的视频动画。
它不是把网页录成视频这么简单,而是把视频当成一条可编程时间轴:每个知识点对应一个场景,每个场景对应一组几何对象,每个对象的状态都能被脚本精确控制。对数据结构这种强调“过程理解”的课程来说,这种方式能把抽象概念转成学生真正看得见的变化。
