如果你让我用一句话描述“45组数学动画”这个项目,我会说:它不是45条花哨的短视频,而是我把一整个学期的抽象数学概念,变成了可以反复观看、随时暂停、从任意角度审视的思维过程记录。这45组动画的起点,其实是一次讲台上的翻车。去年冬天,我在给某高校的一个班级上“导数与极限”的习题课。我花了十分钟在黑板上画了三张图,想说明“切线是割线的极限位置”。结果下课之后,有个学生很诚恳地问:“老师,我看这三张图都一样,你能不能给我看中间的过渡过程?”我当时愣住了——静态的板书,确实没法展示“割线一点点靠拢变成切线”的那个连续过程。数学里真正难讲的东西,从来不是结论,而是结论形成的过程。而过程,恰恰是静态画面表达不出来的。所以当时我就决定,做一个自己的数学动画库。目标很明确:用代码生成动画,把函数、几何、极限、概率、微积分这些高中和大学低年级的核心知识点,全部做成可视化的“过程记录”。这就是“45组数学动画”这个项目的真实来历。下文我会把整个项目的来龙去脉、工具选型、代码核心、工程化方法、踩过的坑,以及课堂实测的结果,全部摊开来讲。如果你也在做数学可视化内容,或者想给自己的数学课配一套能用的动画素材,这篇文章可以直接当参考手册用。1. 45组动画的来历:一节讲不清“极限”的课1.1 从讲台翻车到可视化立项那个学生的提问点醒了我一件事:我们的数学课堂太依赖“静态精确图”了。黑板上的函数曲线、教科书里的示意图、PPT上的插图,其实都是一种“最终状态”。但数学对象在运动中的状态——比如割线趋近切线、内接多边形趋近圆、随机试验的频率趋近概率——这些过程性的内容,几乎无法靠静态图讲清楚。我把这个困境拆成了两个需求。第一,我需要做一个覆盖主要知识点的动画库,每个知识点对应一组动画,时长控制在1到3分钟,能嵌入课件,也能在习题课上单独播放。第二,动画不能只是“花哨的视觉刺激”,它必须服务于一个目标:让观众看到变化、看到逼近、看到趋势、看到连续与离散的区别。于是“45组”不是随便定的数字,而是我按教学板块梳理了一遍课程后得出的需求清单。数学的内容非常广,但真正适合用动画表达、且教学价值高的场景,集中在五类:函数与图像、几何与空间、数列与极限、概率与统计、微积分入门。每个板块我规划了若干组动画,加起来正好45组。1.2 45组内容的分布与命名规则这45组动画的具体分布如下表。我要特别说明一下,“组”不是“个”——一组动画可能只讲一个知识点,但内部包含三到五个分镜,分别演示不同参数或不同视角。模块动画组数典型动画解决的问题函数与图像9参数变化下的正弦波、绝对值函数的“折叠”参数对图像形状的影响几何与空间10勾股定理的动态证明、圆锥截面的形成几何结论的过程性理解数列与极限8割圆术逼近圆、调和级数的发散趋势无限过程的有限表示概率与统计8大数定律的抛硬币模拟、中心极限定理的分布叠加随机性中如何涌现规律微积分入门10割线变切线、黎曼和逼近定积分初等微积分的几何直觉命名上我也吃了不少亏。早期我用“函数01.mp4”“动画05.mp4”这种名字,做到第15组时就完全乱了。后来统一改成“模块_知识点_子分镜”的三段式,比如limits_tangent_sequence.mp4、probability_coin_density.mp4。这个习惯强烈建议你从一开始就保持——45个文件看起来不多,但对同一组动画做三版修改之后,你就知道一个清晰的命名规则有多重要了。2. 工具选型:为什么最后用了Manim2.1 4种方案的真实对比在决定用什么工具之前,我认真对比过四类方案:PPT自带动画、传统视频剪辑软件加手绘、基于网页前端的Three.js、以及本节的主角Manim。这四类方案代表的是完全不同的制作哲学。PPT自带动画,适合做文字飞入飞出,但数学对象之间的平滑变换几乎做不出来——你没法让一条正弦曲线在PPT里随着参数变化而连续变形。传统视频剪辑加手绘,比如用绘图板一帧一帧画,效果好但效率太低,45组动画按这个做法至少需要一整年,而且修改成本极高。Three.js这类Web 3D框架,功能强大,能在浏览器里跑实时交互,但它的核心是三维场景和物理渲染,做二维坐标系的数学动画,反而要写大量额外的坐标变换代码,开发效率不理想。Manim能胜出,是因为它从一开始就是为“数学动画”而生的。它的核心模型是“场景——对象——运动”:你告诉它在某个场景里放一个坐标平面,在坐标平面上画一条函数曲线,然后让这条曲线在3秒内完成某个变形。它不需要你关心每一帧怎么画,你只需要描述“对象从哪到哪”,剩下的插值、渲染、细节平滑都由框架完成。它的坐标系统自带数学语义,x轴就是数学意义上的x轴,画函数就是输入函数表达式而不是画像素点。2.2 环境搭建:从Python到第一个方块Manim是基于Python的,官方推荐用社区版。安装思路分三步。第一步,准备Python环境和包管理器,建议用虚拟环境,避免污染系统Python。第二步,安装Manim本体,同时需要安装FFmpeg作为视频编码后端,以及一个TeX发行版用于渲染数学公式——这一点很关键,因为Manim里的公式文本默认是走LaTeX管线渲染的。没有TeX,公式部分会渲染成空框。第三步,写一段最小代码验证环境。我用的是虚拟环境方式。创建并激活环境之后,安装命令只有一条:pip install manim。FFmpeg和TeX需要单独装,Windows用户装MiKTeX和FFmpeg的官方安装包,Linux用户可以用系统包管理装texlive和ffmpeg。验证环境是否正常的经典版本是第一段“做一个彩色方块”:from manim import * class FirstBlock(Scene): def construct(self): block = Square(color=BLUE, side_length=2) self.play(Create(block)) self.wait(1)渲染命令也很简单:manim -pql first_block.py FirstBlock-p表示渲染完成后预览,-ql表示低清模式。低清模式调试速度快,正式渲染用-qh(高清)。如果你能弹出窗口看到一个蓝色方块被画出来,环境就通了。3. 制作一组数学动画的核心写法3.1 第一组动画:让函数图像“动”起来的代码骨架很多人在接触Manim时最大的卡点,是不理解它那套“面向对象”的动画思维。它的设计哲学可以类比成戏剧舞台:场景是舞台,每个数学对象是演员,self.play()就是发布一条导演指令——让某个演员在指定时间内完成指定的动作。以我做的第一组函数动画为例,目标是把正弦曲线做参数扫描,让观众看到当a从1变到3时,y = sin(a·x)的频率如何变化。关键代码是这样的:from manim import * import numpy as np class SineParameter(Scene): def construct(self): axes = Axes(x_range=[-PI, PI], y_range=[-2, 2]) self.play(Create(axes)) # 初始曲线:a=1 graph = axes.plot(lambda x: np.sin(x), color=BLUE) self.play(Create(graph)) # 用追值器驱动参数 a 从 1 连续变成 3 a = ValueTracker(1.0) curve = always_redraw( lambda: axes.plot( lambda x: np.sin(a.get_value() * x), color=YELLOW ) ) self.play( Transform(graph, curve), a.animate.set_value(3.0), run_time=4 ) self.wait(1)这段代码里有三个点值得反复琢磨。第一,ValueTracker是一个可以随时间变化的数值容器,它可以作为参数被外部修改,而曲线是always_redraw的——这意味着每一帧都会重新计算函数图像,从而产生连续变形的效果。第二,lambda表达式的嵌套容易让人看晕,但核心逻辑只有一层:曲线的y值是np.sin(a * x),a是外部变量。第三,run_time控制了动画总时长,它直接决定了课堂播放的节奏。这个例子的教学意义在于:学生第一次看到“参数连续变化导致图像连续变形”时,他们理解“频率”这个概念的方式会和看静态图完全不同。正弦波的疏密变化是“看”出来的,而不是靠算周期函数硬推出来的。3.2 第二组动画:用正多边形逼近圆的极限片段如果说函数变换动画展示了“参数驱动的连续过程”,那么割圆术动画展示的则是“离散过程的极限可视化”。这一组动画我用了三步设计。第一步,画一个圆,并同时画出它的内接正三角形。第二步,动态增加边数:从三角形到正方形到五边形到六边形,每增加一条边,播放一次“旧多边形淡出、新多边形淡入”的过渡。第三步,也是整组动画的灵魂,在边上加一个计数器,实时显示当前边数和当前周长与圆周长的比值。from manim import * class PolygonToCircle(Scene): def construct(self): circle = Circle(radius=3, color=WHITE, stroke_opacity=0.3) self.add(circle) sides = ValueTracker(3) poly = always_redraw( lambda: RegularPolygon( n=int(sides.get_value()), radius=3, color=BLUE ) ) self.play(Create(poly)) self.play( sides.animate.set_value(60), run_time=6 ) self.play( sides.animate.set_value(200), run_time=4 ) self.wait(1)这段动画在课堂上的效果,远超我的预期。传统板书讲“以六为基,割之又割,以至于不可割”,学生只能想象。动画播完,一个学生当场就说:“老师,这个多边形的周长一直在变大,但好像永远是小于圆周长的,这个‘永远小于但不断接近’的感觉就是极限吗?”——这句话本身就说明极限这个概念的动态本质被看见了。值得特别提一句的是int(sides.get_value())这个写法。RegularPolygon的边数必须是整数,但ValueTracker的值是浮点数,在连续变化过程中,如果不做取整,动画会出现明显的抖动。我在最初版本里没有注意这一点,结果多边形在边数变化过程中不断地闪,后来加了int()取整才解决。4. 工程化管线:如何把45组动画真正做完4.1 统一基类与场景模板做两三组动画,每个文件独立写没问题。但做45组,必须工程化。我不可能每天花大量时间在重复的事情上:设置坐标轴样式、统一背景色、统一字体大小、统一淡入淡出效果、统一片尾字幕。如果每个场景都单独写一遍,不仅慢,而且最后成品风格会五花八门,放一起像拼盘。我的做法是写一个BaseMathScene基类,继承Manim的Scene,然后把所有全局配置放进去。这个基类的主要职责有三个:统一坐标轴样式、统一主题色、统一动画的出场和退场方式。from manim import * class BaseMathScene(Scene): def setup(self): self.camera.background_color = "#101018" self.axes_style = { "x_axis_config": { "color": "#BBBBBB", "include_ticks": True }, "y_axis_config": { "color": "#BBBBBB", "include_ticks": True } }每个具体动画都继承这个基类,只写自己的核心逻辑。这样的好处是,我后来要调整全局背景色,只需要改一行代码,所有45组动画下次渲染时都会统一变动。做数学动画,最大的隐性成本不是“写动画逻辑”,而是“风格一致性维护”。基类把这部分成本彻底压掉了。除了基类,我还做了“分镜模板”。每个动画文件遵循同样的结构:开头1秒黑屏缓冲,接着是问题呈现,然后是核心动画过程,最后是结论标注和1秒回落。用模板化结构有三个好处:第一,播放节奏统一,学生看多了会形成观看预期;第二,剪辑时拼接方便;第三,如果某一段效果不理想,你只需要重渲染那一小段,不用整组返工。4.2 渲染命令与批量生产节拍做45组动画,单条命令逐个渲染肯定不行。我的做法是用一行循环命令跑完所有场景:for i in $(seq 1 45); do manim -qh scenes_$i.py MathScene_$i; done这里有一个细节:Manim每次都启动一个Python解释器,如果场景内大量使用always_redraw和Transform,CPU占用会很高。渲染45组动画不是几分钟的事。以我的机器为例,每个场景平均时长3分钟,高清渲染需要大约8到12分钟,45组全部渲染一轮要7到9个小时。所以我基本是晚上临睡前跑渲染,第二天醒来收片。我强烈建议在正式批量渲染之前,先每个场景用-ql跑一遍,确认所有关键动作都能正确播放。否则等一晚上渲染完后才发现某个片段的公式排版重叠,那就真的要崩溃了。另外,如果中途改了基类或者某个全局配置,得把之前渲染的所有相关视频全部重出,这不是小事。我会记录每个场景的“渲染版本号”,一旦版本号变更就说明需要全部重渲染。4.3 命名、版本与素材管理前面说过命名规则:模块_知识点_子分镜.py。这份命名规则同时对应到输出视频文件名。版本管理我用了最直接的办法:每个场景文件头部用注释记录修改记录。比如:# limits_tangent_sequence.py # v1 初始版本 # v2 修复切线长度比例问题,更新坐标轴刻度范围 # v3 增加割线变切线的中间形态,降低动画速度很多人觉得数学动画是视觉素材,不需要像代码那样做版本管理。但我的经验恰恰相反:数学动画的内容和代码是强绑定的,改一个参数、调一个坐标范围,输出的视频就完全不同。如果你不记录版本,过两周回来看旧素材,你根本不知道这个视频是哪个版本的代码渲染的。素材管理上,我把所有渲染好的视频按模块存目录,然后每个模块里放一个README.md,记录每个视频在课堂上的适用场景、建议播放时机、以及对应的配套习题。这个文件后来成了我备课的重要参考。5. 踩坑记录:数学动画最贵的学费5.1 TeX渲染空框这是我遇到的第一个大坑。第一组动画写完公式部分,渲染出来公式的位置是一个个空框,没有任何符号。原因很简单:系统里没有安装完整的TeX发行版。Manim默认会把MathTex渲染为LaTeX公式,公式的排版走的是TeX管线。只要TeX缺失、损坏或字库不全,就会出现空框或方块。解决方案,就是把TeX装好。Windows装MiKTeX,macOS装MacTeX,Linux装texlive-full,注意别装精简版。另外有个检查技巧:第一次渲染前,先手动编译一次带公式的测试脚本,如果公式区域依然空白,大概率就是加粗字体或数学符号字体缺失。5.2 中文字体标注乱码这个坑非常折磨人。我在做概率模块时,想给动画标注“正面次数”“频率”这些中文字。渲染出来文字全部变成方框。原因是Manim默认字体配置不支持中文字符。解决思路有两种。第一种,是标注尽量用英文或LaTeX公式,比如“frequency”替代“频率”。对于数学课,这其实更符合教材风格。第二种,是给Text对象指定支持中文的字体,例如在Windows下指定SimHei或者Microsoft YaHei:label = Text("频率", font="Microsoft YaHei")但要注意:这个方法依赖目标系统是否有该字体文件。如果你要换机器渲染,字体没装就会再次乱码。我在项目后期统一采用了“英文标注+LaTeX公式”的策略,整套动画风格也更统一清爽。5.3 大场景绘制的致命卡顿有一组动画,我想让坐标平面网格随着视角放大持续展开。结果动画在播放到第20秒时严重掉帧,画面变成幻灯片。原因是我用了always_redraw包住了一个庞大的NumberPlane对象——它的网格线和刻度标签每个都参与重绘,每一帧的绘制量是爆炸级的。这类性能问题的解法是“分层”:静态层只绘制一次,动态层才做always_redraw。坐标平面那个大网格属于静态层,我用普通self.add()加入;动态的曲线、点、标签才用always_redraw。这个优化让渲染时间从22分钟降到了4分钟。做数学动画,一定要明白“每一帧都在重算”的对象要尽量少。5.4 预览帧与最终渲染不一致有一次我用低清预览模式看动画,动画流畅,于是直接开始高清渲染。结果高清版本渲染出来后,有一段公式的间距变得非常拥挤。这是因为低清和高清模式下,某些对象的位置使用的是相对像素单位,字体缩放和边距计算存在差异。我的应对方法是:所有关键动画先做-ql预览,但涉及字幕、公式、坐标刻度这类排版敏感元素,必须额外跑一次“中间画质”渲染确认。另一个利器是-s参数,可以只渲染最后一帧画面,用来检查最终排版。这是性价比最高的检查手段。5.5 动画时长与讲课节奏的匹配技术问题之外,还有一个方法论问题。最初我的动画时长都很随性,有的6秒,有的1分钟。课堂上使用时发现:太短的动画学生还没看清就结束了,太长的动画后面部分学生开始走神。后来我总结出一个节奏经验:单个知识点动画,核心过程控制在20到40秒;参数扫描类动画,控制在1到2分钟;需要用暂停来讨论的动画,片尾必须留出至少3秒的定格画面。不要指望一个动画替代你的讲解,动画只负责展示“过程”,讲解始终要由人来完成。6. 课堂实测:45组动画改变了什么6.1 三个最直观的变化第一个学期结束后,我统计了自己课堂上的三个变化。首先是抽象概念的理解时间显著缩短。之前讲“中心极限定理”时,我得花大半节课去描述“样本均值的分布如何趋近正态”,学生依然一脸茫然。用动画展示不同样本量下均值分布的叠加,几条曲线逐渐聚拢成一个钟形,学生的反馈是“原来这就是中心极限定理”。其次是习题课效率提高了。我把动画片段嵌入习题讲解中,讲题时直接拖回某个关键帧,让学生基于动画里的动态过程讨论。之前要画大量辅助线的题目,现在直接播放对应动画就能找到思路。直观效果是:习题讲解时间从平均12分钟下降到了8分钟,学生课后的正确率有了明显改善。第三个变化,是复习效率。期末复习时,我没有发传统的知识清单,而是把45组动画按章节顺序贴到一个播放列表里。让学生自主选择播放章节,每个动画开场是问题,中场是过程,结尾是结论——看完一遍,整个学期的知识框架就串起来了。6.2 哪些动画不适合在课堂上播放45组动画不是每一组都适合在课堂上播放。我做了几组“极限不存在”的可视化,比如震荡型函数在某个点附近完全没有极限。这类动画内容本身正确,但课堂上播放时容易让学生产生困惑:明明图像在震,为什么说极限不存在?我反思后发现,这类动画更适合让学有余力的学生在课下自行观看,不适合全班集体播放。原因很简单:课堂时间有限,老师要保证主线清晰,而“反例类”动画会分散注意力,除非你有至少15分钟专门讨论反例构造。“动画能增加教学资源”,不等于“每个动画都必须在课堂上放”。另一个不适用的场景是考试前突击。考试前学生的信息接收偏好是“结论密集型”:公式、定理、注意事项。过程性动画在考前播放,反而容易制造焦虑——“这个原理我好像还没完全看懂”。考前我更倾向播放静态总结页,动画留给平时新课和复习课用。6.3 使用动画的节奏:先问、再看、再推导根据一学期的试验,我摸索出一套更稳定的使用节奏:先提问,再看动画,最后回到板书推导。以“割线变切线”为例。上课我先抛出一个问题:“什么叫切线的斜率?请用一句不超过20个字的话描述。”学生回答五花八门,然后我播放动画:一条割线从右边慢慢靠近切线位置,两条线的夹角逐渐缩小,直到完全重合。这时我再回到板书,用极限的数学定义去解释刚才动画里“重合”的瞬间。这个过程的核心价值,是让动画充当“提问和理论之间的桥梁”——动画不是替代思考,而是给思考提供素材。如果直接放动画,学生只是被动观看,效果会大打折扣;如果直接讲定义,没有动画的直观支持,学生理解速度又太慢。两者结合的课堂节奏,才是这45组动画最合理的打开方式。在我的实际使用中,另一个很有效的小技巧是:播放动画前,让学生先预测“接下来会发生什么”。比如放内接多边形逼近圆之前,问学生“当边数从3变成6、12、24,周长会变大还是变小?”学生们会和身边的人争论,然后再播动画验证。这个过程会让注意力显著提升——因为有预测,就有期待,动画的每一秒都在回应期待。经验之外的最后一件事项目做到后期,我逐渐意识到一件事:数学动画最重要的不是渲染技巧,而是你对“过程”的理解。你只有在脑子里真正想明白了“割线如何变成切线”“频率如何趋向概率”“多边形的边数如何逼近无穷”,你才能用动画语言把它表达清楚。工具只是替你完成重复的帧绘制,但“哪种连续变化最有启发性”这件事,永远需要人的判断。所以如果你也想做一套自己的数学动画,我的建议是:不要一上来就追求45组。先挑一个你最熟悉、也最讲不清楚的知识点,用Manim做出一组来,放给一个朋友看,问他“你看到了什么”,再根据反馈修改。等你把第一组动画打磨到自己满意,后面的路线就清晰了。还有一个很实用的技巧留给你:所有动画场景里,尽量保持“每个屏幕只有一个核心动态对象”。背景的坐标轴和参考线可以静态放着,但同一时刻动的东西不要超过两个。这条原理解释起来也很简单——课堂教学的关键不是信息密度,而是认知焦点。让学生的注意力在一段时间里只聚焦一个变化,效果远远好于让屏幕每个角落都在动。最后,如果你好奇这些动画模板能否直接复用:Manim的社区相当活跃,官方文档里已经有大量现成的数学场景代码,很多可以直接改写成自己的版本。我的建议是,把文档里的示例当成骨架,把你自己对数学内容的理解填进去。动画形式可以相似,但每个知识点的“演进过程”是独一无二的——那才是你的课堂最宝贵的部分。
如果你让我用一句话描述“45组数学动画”这个项目,我会说:它不是45条花哨的短视频,而是我把一整个学期的抽象数学概念,变成了可以反复观看、随时暂停、从任意角度审视的思维过程记录。这45组动画的起点,其实是一次讲台上的翻车。去年冬天,我在给某高校的一个班级上“导数与极限”的习题课。我花了十分钟在黑板上画了三张图,想说明“切线是割线的极限位置”。结果下课之后,有个学生很诚恳地问:“老师,我看这三张图都一样,你能不能给我看中间的过渡过程?”我当时愣住了——静态的板书,确实没法展示“割线一点点靠拢变成切线”的那个连续过程。数学里真正难讲的东西,从来不是结论,而是结论形成的过程。而过程,恰恰是静态画面表达不出来的。所以当时我就决定,做一个自己的数学动画库。目标很明确:用代码生成动画,把函数、几何、极限、概率、微积分这些高中和大学低年级的核心知识点,全部做成可视化的“过程记录”。这就是“45组数学动画”这个项目的真实来历。下文我会把整个项目的来龙去脉、工具选型、代码核心、工程化方法、踩过的坑,以及课堂实测的结果,全部摊开来讲。如果你也在做数学可视化内容,或者想给自己的数学课配一套能用的动画素材,这篇文章可以直接当参考手册用。1. 45组动画的来历:一节讲不清“极限”的课1.1 从讲台翻车到可视化立项那个学生的提问点醒了我一件事:我们的数学课堂太依赖“静态精确图”了。黑板上的函数曲线、教科书里的示意图、PPT上的插图,其实都是一种“最终状态”。但数学对象在运动中的状态——比如割线趋近切线、内接多边形趋近圆、随机试验的频率趋近概率——这些过程性的内容,几乎无法靠静态图讲清楚。我把这个困境拆成了两个需求。第一,我需要做一个覆盖主要知识点的动画库,每个知识点对应一组动画,时长控制在1到3分钟,能嵌入课件,也能在习题课上单独播放。第二,动画不能只是“花哨的视觉刺激”,它必须服务于一个目标:让观众看到变化、看到逼近、看到趋势、看到连续与离散的区别。于是“45组”不是随便定的数字,而是我按教学板块梳理了一遍课程后得出的需求清单。数学的内容非常广,但真正适合用动画表达、且教学价值高的场景,集中在五类:函数与图像、几何与空间、数列与极限、概率与统计、微积分入门。每个板块我规划了若干组动画,加起来正好45组。1.2 45组内容的分布与命名规则这45组动画的具体分布如下表。我要特别说明一下,“组”不是“个”——一组动画可能只讲一个知识点,但内部包含三到五个分镜,分别演示不同参数或不同视角。模块动画组数典型动画解决的问题函数与图像9参数变化下的正弦波、绝对值函数的“折叠”参数对图像形状的影响几何与空间10勾股定理的动态证明、圆锥截面的形成几何结论的过程性理解数列与极限8割圆术逼近圆、调和级数的发散趋势无限过程的有限表示概率与统计8大数定律的抛硬币模拟、中心极限定理的分布叠加随机性中如何涌现规律微积分入门10割线变切线、黎曼和逼近定积分初等微积分的几何直觉命名上我也吃了不少亏。早期我用“函数01.mp4”“动画05.mp4”这种名字,做到第15组时就完全乱了。后来统一改成“模块_知识点_子分镜”的三段式,比如limits_tangent_sequence.mp4、probability_coin_density.mp4。这个习惯强烈建议你从一开始就保持——45个文件看起来不多,但对同一组动画做三版修改之后,你就知道一个清晰的命名规则有多重要了。2. 工具选型:为什么最后用了Manim2.1 4种方案的真实对比在决定用什么工具之前,我认真对比过四类方案:PPT自带动画、传统视频剪辑软件加手绘、基于网页前端的Three.js、以及本节的主角Manim。这四类方案代表的是完全不同的制作哲学。PPT自带动画,适合做文字飞入飞出,但数学对象之间的平滑变换几乎做不出来——你没法让一条正弦曲线在PPT里随着参数变化而连续变形。传统视频剪辑加手绘,比如用绘图板一帧一帧画,效果好但效率太低,45组动画按这个做法至少需要一整年,而且修改成本极高。Three.js这类Web 3D框架,功能强大,能在浏览器里跑实时交互,但它的核心是三维场景和物理渲染,做二维坐标系的数学动画,反而要写大量额外的坐标变换代码,开发效率不理想。Manim能胜出,是因为它从一开始就是为“数学动画”而生的。它的核心模型是“场景——对象——运动”:你告诉它在某个场景里放一个坐标平面,在坐标平面上画一条函数曲线,然后让这条曲线在3秒内完成某个变形。它不需要你关心每一帧怎么画,你只需要描述“对象从哪到哪”,剩下的插值、渲染、细节平滑都由框架完成。它的坐标系统自带数学语义,x轴就是数学意义上的x轴,画函数就是输入函数表达式而不是画像素点。2.2 环境搭建:从Python到第一个方块Manim是基于Python的,官方推荐用社区版。安装思路分三步。第一步,准备Python环境和包管理器,建议用虚拟环境,避免污染系统Python。第二步,安装Manim本体,同时需要安装FFmpeg作为视频编码后端,以及一个TeX发行版用于渲染数学公式——这一点很关键,因为Manim里的公式文本默认是走LaTeX管线渲染的。没有TeX,公式部分会渲染成空框。第三步,写一段最小代码验证环境。我用的是虚拟环境方式。创建并激活环境之后,安装命令只有一条:pip install manim。FFmpeg和TeX需要单独装,Windows用户装MiKTeX和FFmpeg的官方安装包,Linux用户可以用系统包管理装texlive和ffmpeg。验证环境是否正常的经典版本是第一段“做一个彩色方块”:from manim import * class FirstBlock(Scene): def construct(self): block = Square(color=BLUE, side_length=2) self.play(Create(block)) self.wait(1)渲染命令也很简单:manim -pql first_block.py FirstBlock-p表示渲染完成后预览,-ql表示低清模式。低清模式调试速度快,正式渲染用-qh(高清)。如果你能弹出窗口看到一个蓝色方块被画出来,环境就通了。3. 制作一组数学动画的核心写法3.1 第一组动画:让函数图像“动”起来的代码骨架很多人在接触Manim时最大的卡点,是不理解它那套“面向对象”的动画思维。它的设计哲学可以类比成戏剧舞台:场景是舞台,每个数学对象是演员,self.play()就是发布一条导演指令——让某个演员在指定时间内完成指定的动作。以我做的第一组函数动画为例,目标是把正弦曲线做参数扫描,让观众看到当a从1变到3时,y = sin(a·x)的频率如何变化。关键代码是这样的:from manim import * import numpy as np class SineParameter(Scene): def construct(self): axes = Axes(x_range=[-PI, PI], y_range=[-2, 2]) self.play(Create(axes)) # 初始曲线:a=1 graph = axes.plot(lambda x: np.sin(x), color=BLUE) self.play(Create(graph)) # 用追值器驱动参数 a 从 1 连续变成 3 a = ValueTracker(1.0) curve = always_redraw( lambda: axes.plot( lambda x: np.sin(a.get_value() * x), color=YELLOW ) ) self.play( Transform(graph, curve), a.animate.set_value(3.0), run_time=4 ) self.wait(1)这段代码里有三个点值得反复琢磨。第一,ValueTracker是一个可以随时间变化的数值容器,它可以作为参数被外部修改,而曲线是always_redraw的——这意味着每一帧都会重新计算函数图像,从而产生连续变形的效果。第二,lambda表达式的嵌套容易让人看晕,但核心逻辑只有一层:曲线的y值是np.sin(a * x),a是外部变量。第三,run_time控制了动画总时长,它直接决定了课堂播放的节奏。这个例子的教学意义在于:学生第一次看到“参数连续变化导致图像连续变形”时,他们理解“频率”这个概念的方式会和看静态图完全不同。正弦波的疏密变化是“看”出来的,而不是靠算周期函数硬推出来的。3.2 第二组动画:用正多边形逼近圆的极限片段如果说函数变换动画展示了“参数驱动的连续过程”,那么割圆术动画展示的则是“离散过程的极限可视化”。这一组动画我用了三步设计。第一步,画一个圆,并同时画出它的内接正三角形。第二步,动态增加边数:从三角形到正方形到五边形到六边形,每增加一条边,播放一次“旧多边形淡出、新多边形淡入”的过渡。第三步,也是整组动画的灵魂,在边上加一个计数器,实时显示当前边数和当前周长与圆周长的比值。from manim import * class PolygonToCircle(Scene): def construct(self): circle = Circle(radius=3, color=WHITE, stroke_opacity=0.3) self.add(circle) sides = ValueTracker(3) poly = always_redraw( lambda: RegularPolygon( n=int(sides.get_value()), radius=3, color=BLUE ) ) self.play(Create(poly)) self.play( sides.animate.set_value(60), run_time=6 ) self.play( sides.animate.set_value(200), run_time=4 ) self.wait(1)这段动画在课堂上的效果,远超我的预期。传统板书讲“以六为基,割之又割,以至于不可割”,学生只能想象。动画播完,一个学生当场就说:“老师,这个多边形的周长一直在变大,但好像永远是小于圆周长的,这个‘永远小于但不断接近’的感觉就是极限吗?”——这句话本身就说明极限这个概念的动态本质被看见了。值得特别提一句的是int(sides.get_value())这个写法。RegularPolygon的边数必须是整数,但ValueTracker的值是浮点数,在连续变化过程中,如果不做取整,动画会出现明显的抖动。我在最初版本里没有注意这一点,结果多边形在边数变化过程中不断地闪,后来加了int()取整才解决。4. 工程化管线:如何把45组动画真正做完4.1 统一基类与场景模板做两三组动画,每个文件独立写没问题。但做45组,必须工程化。我不可能每天花大量时间在重复的事情上:设置坐标轴样式、统一背景色、统一字体大小、统一淡入淡出效果、统一片尾字幕。如果每个场景都单独写一遍,不仅慢,而且最后成品风格会五花八门,放一起像拼盘。我的做法是写一个BaseMathScene基类,继承Manim的Scene,然后把所有全局配置放进去。这个基类的主要职责有三个:统一坐标轴样式、统一主题色、统一动画的出场和退场方式。from manim import * class BaseMathScene(Scene): def setup(self): self.camera.background_color = "#101018" self.axes_style = { "x_axis_config": { "color": "#BBBBBB", "include_ticks": True }, "y_axis_config": { "color": "#BBBBBB", "include_ticks": True } }每个具体动画都继承这个基类,只写自己的核心逻辑。这样的好处是,我后来要调整全局背景色,只需要改一行代码,所有45组动画下次渲染时都会统一变动。做数学动画,最大的隐性成本不是“写动画逻辑”,而是“风格一致性维护”。基类把这部分成本彻底压掉了。除了基类,我还做了“分镜模板”。每个动画文件遵循同样的结构:开头1秒黑屏缓冲,接着是问题呈现,然后是核心动画过程,最后是结论标注和1秒回落。用模板化结构有三个好处:第一,播放节奏统一,学生看多了会形成观看预期;第二,剪辑时拼接方便;第三,如果某一段效果不理想,你只需要重渲染那一小段,不用整组返工。4.2 渲染命令与批量生产节拍做45组动画,单条命令逐个渲染肯定不行。我的做法是用一行循环命令跑完所有场景:for i in $(seq 1 45); do manim -qh scenes_$i.py MathScene_$i; done这里有一个细节:Manim每次都启动一个Python解释器,如果场景内大量使用always_redraw和Transform,CPU占用会很高。渲染45组动画不是几分钟的事。以我的机器为例,每个场景平均时长3分钟,高清渲染需要大约8到12分钟,45组全部渲染一轮要7到9个小时。所以我基本是晚上临睡前跑渲染,第二天醒来收片。我强烈建议在正式批量渲染之前,先每个场景用-ql跑一遍,确认所有关键动作都能正确播放。否则等一晚上渲染完后才发现某个片段的公式排版重叠,那就真的要崩溃了。另外,如果中途改了基类或者某个全局配置,得把之前渲染的所有相关视频全部重出,这不是小事。我会记录每个场景的“渲染版本号”,一旦版本号变更就说明需要全部重渲染。4.3 命名、版本与素材管理前面说过命名规则:模块_知识点_子分镜.py。这份命名规则同时对应到输出视频文件名。版本管理我用了最直接的办法:每个场景文件头部用注释记录修改记录。比如:# limits_tangent_sequence.py # v1 初始版本 # v2 修复切线长度比例问题,更新坐标轴刻度范围 # v3 增加割线变切线的中间形态,降低动画速度很多人觉得数学动画是视觉素材,不需要像代码那样做版本管理。但我的经验恰恰相反:数学动画的内容和代码是强绑定的,改一个参数、调一个坐标范围,输出的视频就完全不同。如果你不记录版本,过两周回来看旧素材,你根本不知道这个视频是哪个版本的代码渲染的。素材管理上,我把所有渲染好的视频按模块存目录,然后每个模块里放一个README.md,记录每个视频在课堂上的适用场景、建议播放时机、以及对应的配套习题。这个文件后来成了我备课的重要参考。5. 踩坑记录:数学动画最贵的学费5.1 TeX渲染空框这是我遇到的第一个大坑。第一组动画写完公式部分,渲染出来公式的位置是一个个空框,没有任何符号。原因很简单:系统里没有安装完整的TeX发行版。Manim默认会把MathTex渲染为LaTeX公式,公式的排版走的是TeX管线。只要TeX缺失、损坏或字库不全,就会出现空框或方块。解决方案,就是把TeX装好。Windows装MiKTeX,macOS装MacTeX,Linux装texlive-full,注意别装精简版。另外有个检查技巧:第一次渲染前,先手动编译一次带公式的测试脚本,如果公式区域依然空白,大概率就是加粗字体或数学符号字体缺失。5.2 中文字体标注乱码这个坑非常折磨人。我在做概率模块时,想给动画标注“正面次数”“频率”这些中文字。渲染出来文字全部变成方框。原因是Manim默认字体配置不支持中文字符。解决思路有两种。第一种,是标注尽量用英文或LaTeX公式,比如“frequency”替代“频率”。对于数学课,这其实更符合教材风格。第二种,是给Text对象指定支持中文的字体,例如在Windows下指定SimHei或者Microsoft YaHei:label = Text("频率", font="Microsoft YaHei")但要注意:这个方法依赖目标系统是否有该字体文件。如果你要换机器渲染,字体没装就会再次乱码。我在项目后期统一采用了“英文标注+LaTeX公式”的策略,整套动画风格也更统一清爽。5.3 大场景绘制的致命卡顿有一组动画,我想让坐标平面网格随着视角放大持续展开。结果动画在播放到第20秒时严重掉帧,画面变成幻灯片。原因是我用了always_redraw包住了一个庞大的NumberPlane对象——它的网格线和刻度标签每个都参与重绘,每一帧的绘制量是爆炸级的。这类性能问题的解法是“分层”:静态层只绘制一次,动态层才做always_redraw。坐标平面那个大网格属于静态层,我用普通self.add()加入;动态的曲线、点、标签才用always_redraw。这个优化让渲染时间从22分钟降到了4分钟。做数学动画,一定要明白“每一帧都在重算”的对象要尽量少。5.4 预览帧与最终渲染不一致有一次我用低清预览模式看动画,动画流畅,于是直接开始高清渲染。结果高清版本渲染出来后,有一段公式的间距变得非常拥挤。这是因为低清和高清模式下,某些对象的位置使用的是相对像素单位,字体缩放和边距计算存在差异。我的应对方法是:所有关键动画先做-ql预览,但涉及字幕、公式、坐标刻度这类排版敏感元素,必须额外跑一次“中间画质”渲染确认。另一个利器是-s参数,可以只渲染最后一帧画面,用来检查最终排版。这是性价比最高的检查手段。5.5 动画时长与讲课节奏的匹配技术问题之外,还有一个方法论问题。最初我的动画时长都很随性,有的6秒,有的1分钟。课堂上使用时发现:太短的动画学生还没看清就结束了,太长的动画后面部分学生开始走神。后来我总结出一个节奏经验:单个知识点动画,核心过程控制在20到40秒;参数扫描类动画,控制在1到2分钟;需要用暂停来讨论的动画,片尾必须留出至少3秒的定格画面。不要指望一个动画替代你的讲解,动画只负责展示“过程”,讲解始终要由人来完成。6. 课堂实测:45组动画改变了什么6.1 三个最直观的变化第一个学期结束后,我统计了自己课堂上的三个变化。首先是抽象概念的理解时间显著缩短。之前讲“中心极限定理”时,我得花大半节课去描述“样本均值的分布如何趋近正态”,学生依然一脸茫然。用动画展示不同样本量下均值分布的叠加,几条曲线逐渐聚拢成一个钟形,学生的反馈是“原来这就是中心极限定理”。其次是习题课效率提高了。我把动画片段嵌入习题讲解中,讲题时直接拖回某个关键帧,让学生基于动画里的动态过程讨论。之前要画大量辅助线的题目,现在直接播放对应动画就能找到思路。直观效果是:习题讲解时间从平均12分钟下降到了8分钟,学生课后的正确率有了明显改善。第三个变化,是复习效率。期末复习时,我没有发传统的知识清单,而是把45组动画按章节顺序贴到一个播放列表里。让学生自主选择播放章节,每个动画开场是问题,中场是过程,结尾是结论——看完一遍,整个学期的知识框架就串起来了。6.2 哪些动画不适合在课堂上播放45组动画不是每一组都适合在课堂上播放。我做了几组“极限不存在”的可视化,比如震荡型函数在某个点附近完全没有极限。这类动画内容本身正确,但课堂上播放时容易让学生产生困惑:明明图像在震,为什么说极限不存在?我反思后发现,这类动画更适合让学有余力的学生在课下自行观看,不适合全班集体播放。原因很简单:课堂时间有限,老师要保证主线清晰,而“反例类”动画会分散注意力,除非你有至少15分钟专门讨论反例构造。“动画能增加教学资源”,不等于“每个动画都必须在课堂上放”。另一个不适用的场景是考试前突击。考试前学生的信息接收偏好是“结论密集型”:公式、定理、注意事项。过程性动画在考前播放,反而容易制造焦虑——“这个原理我好像还没完全看懂”。考前我更倾向播放静态总结页,动画留给平时新课和复习课用。6.3 使用动画的节奏:先问、再看、再推导根据一学期的试验,我摸索出一套更稳定的使用节奏:先提问,再看动画,最后回到板书推导。以“割线变切线”为例。上课我先抛出一个问题:“什么叫切线的斜率?请用一句不超过20个字的话描述。”学生回答五花八门,然后我播放动画:一条割线从右边慢慢靠近切线位置,两条线的夹角逐渐缩小,直到完全重合。这时我再回到板书,用极限的数学定义去解释刚才动画里“重合”的瞬间。这个过程的核心价值,是让动画充当“提问和理论之间的桥梁”——动画不是替代思考,而是给思考提供素材。如果直接放动画,学生只是被动观看,效果会大打折扣;如果直接讲定义,没有动画的直观支持,学生理解速度又太慢。两者结合的课堂节奏,才是这45组动画最合理的打开方式。在我的实际使用中,另一个很有效的小技巧是:播放动画前,让学生先预测“接下来会发生什么”。比如放内接多边形逼近圆之前,问学生“当边数从3变成6、12、24,周长会变大还是变小?”学生们会和身边的人争论,然后再播动画验证。这个过程会让注意力显著提升——因为有预测,就有期待,动画的每一秒都在回应期待。经验之外的最后一件事项目做到后期,我逐渐意识到一件事:数学动画最重要的不是渲染技巧,而是你对“过程”的理解。你只有在脑子里真正想明白了“割线如何变成切线”“频率如何趋向概率”“多边形的边数如何逼近无穷”,你才能用动画语言把它表达清楚。工具只是替你完成重复的帧绘制,但“哪种连续变化最有启发性”这件事,永远需要人的判断。所以如果你也想做一套自己的数学动画,我的建议是:不要一上来就追求45组。先挑一个你最熟悉、也最讲不清楚的知识点,用Manim做出一组来,放给一个朋友看,问他“你看到了什么”,再根据反馈修改。等你把第一组动画打磨到自己满意,后面的路线就清晰了。还有一个很实用的技巧留给你:所有动画场景里,尽量保持“每个屏幕只有一个核心动态对象”。背景的坐标轴和参考线可以静态放着,但同一时刻动的东西不要超过两个。这条原理解释起来也很简单——课堂教学的关键不是信息密度,而是认知焦点。让学生的注意力在一段时间里只聚焦一个变化,效果远远好于让屏幕每个角落都在动。最后,如果你好奇这些动画模板能否直接复用:Manim的社区相当活跃,官方文档里已经有大量现成的数学场景代码,很多可以直接改写成自己的版本。我的建议是,把文档里的示例当成骨架,把你自己对数学内容的理解填进去。动画形式可以相似,但每个知识点的“演进过程”是独一无二的——那才是你的课堂最宝贵的部分。