Manim 动画编排:AnimationGroup、LaggedStart、Succession 与节奏设计
Good animations are choreographed, not just played. This chapter is Manim's timing layer. Multiple animations in one play() run simultaneously — AnimationGroup is the same thing as an object, with a lag_ratio knob that staggers start times (lag_ratio=0 fully simultaneous, 1 strictly sequential). LaggedStart is sugar for lag_ratio around 0.1-0.3, the "wave" feel of 3b1b grids; LaggedStartMap applies one animation across a list with stagger. Succession chains animations where each begins when the previous finishes, useful when objects must appear in causal order. rate_func shapes progress within run_time: smooth (default ease-in-out), linear for motion that should feel mechanical, there_and_back for emphasis pulses, rush_into/rush_from for snappy entrances, wiggle for attention. Recipes include count-up numbers, staggered grid reveals, a wave of dots, and the classic 'title drops then content follows' sequencing.
编排三原语
from manim import *
# 1) 同时播放: play() 里放多个动画
self.play(Create(circle), Write(text), run_time=2)
# 2) 错峰播放: LaggedStart, 第二个动画等一会儿再开始
self.play(LaggedStart(FadeIn(a), FadeIn(b), FadeIn(c),
lag_ratio=0.25))
# 3) 严格串行: Succession, 上一个播完才轮到下一个
self.play(Succession(GrowFromCenter(a), TransformFromCopy(a, b)))
一句话区分:LaggedStart 是"重叠的先后"(波浪感),Succession 是"不重叠的先后"(因果感)。
AnimationGroup 与 lag_ratio
AnimationGroup 是更底层的构造,lag_ratio 精确控制错峰程度:
anims = [GrowFromCenter(d) for d in dots]
self.play(AnimationGroup(*anims, lag_ratio=0)) # 完全同时
self.play(AnimationGroup(*anims, lag_ratio=0.3)) # 波浪
self.play(AnimationGroup(*anims, lag_ratio=1)) # 等价串行
lag_ratio 的含义:每个动画的开始时间 = lag_ratio × 前一个动画的时长,依次顺延。0.15~0.3 是 3b1b 网格动画最常用的"呼吸感"区间。
LaggedStartMap:一行搞定列表错峰
titles = VGroup(*[Text(t, font_size=30) for t in
["问题", "建模", "求解", "验证"]])
titles.arrange(DOWN, buff=0.6).to_edge(LEFT)
self.play(LaggedStartMap(FadeIn, titles, shift=RIGHT * 0.5,
lag_ratio=0.3))
LaggedStartMap(动画类, 对象列表) 自动把同一个动画应用到列表每个成员并错峰,比手写推导简洁得多。
rate_func:单段动画的缓动曲线
| 函数 | 手感 | 场景 |
|---|---|---|
| smooth(默认) | 缓入缓出 | 绝大多数动画 |
| linear | 匀速 | 物理、函数扫描 |
| there_and_back | 去了又回 | 强调脉冲、闪一下 |
| rush_into | 急进缓出 | 快速入场 |
| rush_from | 缓入急出 | 快速离场 |
| wiggle | 抖动 | 吸引注意力 |
self.play(square.animate.scale(1.2), rate_func=there_and_back)
# 放大又缩回原样, 纯强调, 不留痕迹
数字滚动:编排 + updater 的经典组合
num = ValueTracker(0)
label = Integer(0).scale(1.6)
label.add_updater(lambda m: m.set_value(num.get_value()))
self.add(label)
self.play(num.animate.set_value(2026), run_time=2,
rate_func=rush_from) # 先慢后快, 收尾干脆
节奏设计四原则
- 一段 play 一个意图:一次播放只讲一件事(进场/移动/强调),观众才跟得上。
- 错峰 > 同时:列表内容几乎永远用 LaggedStart,同时弹出显得廉价。
- 留白即节拍:self.wait(0.5) 的短停顿比连续动画更有节奏感。
- 入场快、退场更快:入场 run_time 1~1.5s,退场 0.5~0.8s,信息密度不塌。
综合示例:章节开场
class ChapterOpen(Scene):
def construct(self):
num = Text("02", font_size=96, weight=BOLD, color=GREY_D)
title = Text("函数的变换", font="Microsoft YaHei", font_size=52)
rule = Line(LEFT * 4, RIGHT * 4).next_to(title, DOWN, buff=0.4)
grp = VGroup(num, title, rule).arrange(DOWN, buff=0.5)
self.play(FadeIn(num, shift=DOWN * 0.5), run_time=0.8)
self.play(Write(title), Create(rule), run_time=1.2)
self.play(grp.animate.to_edge(UP).scale(0.7), run_time=0.8)
self.wait()
三个 play、三种意图(入场 → 标题成型 → 移位收拢),这就是"有节奏"的最小样例。