JAnim 文档¶
JAnim 是一个用于创建流畅动画的库,以程序化动画为核心理念,并支持实时编辑、实时预览,并支持更多其它丰富的功能。
警告
应避免借助 AI (LLM) 询问有关 JAnim 的信息,得到的消息很可能存在严重偏差
信息可靠度:加 QQ 群 / 开 Discussion 询问作者 > 询问 DeepWiki > 询问市面上的 AI (LLM)
以下是一部分样例展示(注:可以直接点击分页标题快速折叠这部分内容)
HelloJAnimExample ¶
from janim.imports import *
class HelloJAnimExample(Timeline):
def construct(self):
# 定义物件
circle = Circle(color=BLUE)
square = Square(color=GREEN, fill_alpha=0.5)
# 进行动画
self.forward()
self.play(Create(circle))
self.play(Transform(circle, square))
self.play(Uncreate(square))
self.forward()
BasicAnimationExample ¶
from janim.imports import *
class BasicAnimationExample(Timeline):
def construct(self):
circle = Circle()
star = Star()
self.forward()
self.play(Create(circle))
self.play(circle.anim.points.shift(LEFT * 3).scale(1.5))
self.play(circle.anim.set(color=RED, fill_alpha=0.5))
self.play(SpinInFromNothing(star))
self.play(star.anim.points.shift(RIGHT * 3).scale(1.5))
self.play(star.anim.set(color=YELLOW, fill_alpha=0.5))
self.forward()
TextExample ¶
from janim.imports import *
class TextExample(Timeline):
def construct(self):
txt = Text('Here is some text', font_size=64)
desc = Group(
Text('You can also apply <c BLUE>styles</c> to the text.', format=Text.Format.RichText),
Text('You can also apply <c GREEN><fs 1.4>styles</fs></c> to the text.', format=Text.Format.RichText),
)
group = Group(txt, desc)
group.points.arrange(DOWN, buff=MED_LARGE_BUFF)
self.forward()
self.play(Write(txt))
self.play(FadeIn(desc[0], UP))
self.play(Transform(desc[0], desc[1]))
self.forward()
TypstExample ¶
from janim.imports import *
typst_doc = t_(
R'''
JAnim provides `TypstText` and `TypstMath` classes to insert Typst content.
Math expressions are also supported.
$ A = pi r^2 $
$ "area" = pi dot "radius"^2 $
$ cal(A) :=
{ x in RR | x "is natural" } $
#let x = 5
$ #x < 17 $
You can also use `TypstDoc`, which automatically align to the top of the viewport,
instead of the center.
''')
class TypstExample(Timeline):
CONFIG = Config(
typst_shared_preamble=t_(
R'''
#import "@janim/colors:0.0.0": *
#show raw: set text(BLUE)
''')
)
def construct(self):
doc = TypstDoc(typst_doc)
group = Group(
Text('TypstText', color=BLUE),
TypstText('This is a sentence with a math expression $f(x)=x^2$'),
Text('TypstMath', color=BLUE),
TypstMath('sum_(i=1)^n x_i = x_1 + x_2 + dots.c + x_n')
)
group.points.arrange_in_grid()
# 作用于文本的动画的渲染速度较慢
self.play(Write(doc), duration=4)
self.forward()
self.play(FadeOut(doc))
self.play(Write(group))
self.forward()
self.play(FadeOut(group))
TypstColorizeExample ¶
from janim.imports import *
class TypstColorizeExample(Timeline):
def construct(self):
typ = TypstMath('cos^2 theta + sin^2 theta = 1', scale=3).show()
self.forward()
self.play(typ['cos'].anim.set(color=BLUE))
self.play(typ['sin'].anim.set(color=BLUE))
self.play(typ['theta', 0].anim.set(color=GOLD))
self.play(typ['theta', 1].anim.set(color=ORANGE))
self.forward()
self.play(typ['theta', ...].anim.set(color=GREEN))
self.play(typ['space^2', ...].anim.set(color=RED))
self.forward()
AnimatingPiExample ¶
from janim.imports import *
class AnimatingPiExample(Timeline):
def construct(self):
grid = TypstMath('pi') * 100
grid.points.scale(2).arrange_in_grid(10, 10, buff=0.2)
grid.show()
self.play(grid.anim.points.shift(LEFT))
self.play(grid(VItem).anim.color.set(YELLOW))
self.forward()
self.play(grid(VItem).anim.color.set(BLUE))
self.forward()
self.play(grid.anim.points.to_center().set_height(TAU - MED_SMALL_BUFF))
self.forward()
self.play(grid.anim.points.apply_complex_fn(np.exp), duration=5)
self.forward()
self.play(
grid.anim.points.apply_point_fn(
lambda p: [
p[0] + 0.5 * math.sin(p[1]),
p[1] + 0.5 * math.sin(p[0]),
p[2]
]
),
duration=5
)
self.forward()
NumberPlaneExample ¶
from janim.imports import *
class NumberPlaneExample(Timeline):
def construct(self):
plane = NumberPlane(faded_line_ratio=1)
sin_graph = plane.get_graph(lambda x: math.sin(x))
self.forward(0.2)
self.play(Write(plane, lag_ratio=0.05))
self.play(Write(sin_graph))
self.forward()
self.play(
plane.anim.points.apply_matrix([
[3, -1],
[1, 2]
]),
sin_graph.anim(),
duration=2
)
self.forward()
UpdaterExample ¶
from janim.imports import *
class UpdaterExample(Timeline):
def construct(self):
square = Square(fill_color=BLUE_E, fill_alpha=1).show()
brace = Brace(square, UP).show()
def text_updater(p: UpdaterParams):
cmpt = brace.current().points
return cmpt.create_text(f'Width = {cmpt.brace_length:.2f}')
self.prepare(
DataUpdater(
brace,
lambda data, p: data.points.match(square.current())
),
ItemUpdater(None, text_updater),
duration=10
)
self.forward()
self.play(square.anim.points.scale(2))
self.play(square.anim.points.scale(0.5))
self.play(square.anim.points.set_width(5, stretch=True))
w0 = square.points.box.width
self.play(
DataUpdater(
square,
lambda data, p: data.points.set_width(
w0 + 0.5 * w0 * math.sin(p.alpha * p.range.duration)
)
),
duration=5
)
self.forward()
ArrowPointingExample ¶
from janim.imports import *
class ArrowPointingExample(Timeline):
def construct(self):
dot1 = Dot(LEFT * 3)
dot2 = Dot()
arrow = Arrow(dot1, dot2, color=YELLOW)
self.show(dot1, dot2, arrow)
self.play(
dot2.update.points.rotate(TAU, about_point=RIGHT * 2),
GroupUpdater(
arrow,
lambda data, p:
data.set_start_and_end(
dot1.points.box.center,
dot2.current().points.box.center
)
),
duration=4
)
参考:
CombineUpdatersExample ¶
from janim.imports import *
class CombineUpdatersExample(Timeline):
def construct(self):
square = Square()
square.points.to_border(LEFT)
# 这里每次 play 都多一个 Updater,用于演示 动画复合 的效果
self.play(
square.anim.points.to_border(RIGHT),
duration=2
)
###############################
square.points.to_border(LEFT)
self.play(
square.anim.points.to_border(RIGHT),
DataUpdater(
square,
lambda data, p: data.points.shift(UP * math.sin(p.alpha * 4 * PI)),
become_at_end=False
),
duration=2
)
###############################
square.points.to_border(LEFT)
self.play(
square.anim.points.to_border(RIGHT),
DataUpdater(
square,
lambda data, p: data.points.shift(UP * math.sin(p.alpha * 4 * PI)),
become_at_end=False
),
square.update(become_at_end=False).color.set(BLUE).r.points.rotate(-TAU),
duration=2
)
RotatingPieExample ¶
from janim.imports import *
class RotatingPieExample(Timeline):
def construct(self):
pie = Group(*[
Sector(start_angle=i * TAU / 4, angle=TAU / 4, radius=1.5, color=color, fill_alpha=1, stroke_alpha=0)
.points.shift(rotate_vector(UR * 0.05, i * TAU / 4))
.r
for i, color in enumerate([RED, PURPLE, MAROON, GOLD])
])
self.play(
GroupUpdater(
pie,
lambda data, p: data.points.rotate(p.alpha * TAU, about_point=ORIGIN),
duration=5
),
DataUpdater(
pie[0],
lambda data, p: data.points.shift(normalize(data.mark.get()) * p.alpha),
rate_func=there_and_back,
become_at_end=False,
at=2,
duration=2
)
)
MarkedItemExample ¶
from janim.imports import *
class MarkedSquare(MarkedItem, Square):
def __init__(self, side_length: float = 2.0, **kwargs):
super().__init__(side_length, **kwargs)
self.mark.set_points([RIGHT * side_length / 4, DOWN * side_length / 4])
class MarkedItemExample(Timeline):
def construct(self):
square = MarkedSquare()
tri1 = Triangle(radius=0.2, color=GREEN)
tri2 = Triangle(radius=0.2, color=BLUE)
dots = DotCloud(color=RED)
self.play(
square.update.points.rotate(TAU),
DataUpdater(
square,
lambda data, p: data.points.shift(RIGHT * math.sin(4 * math.pi * p.alpha))
),
DataUpdater(
tri1,
lambda data, p: data.mark.set(square.current().mark.get())
),
DataUpdater(
tri2,
lambda data, p: data.mark.set(square.current().mark.get(index=1))
),
DataUpdater(
dots,
lambda data, p: data.points.set(square.current().mark.get_points()),
skip_null_items=False
),
duration=4
)
FrameEffectExample ¶
from janim.imports import *
class FrameEffectExample(Timeline):
def construct(self):
squares = Square(0.5, color=BLUE, fill_alpha=0.3) * 49
squares.points.arrange_in_grid()
effect1 = SimpleFrameEffect( # (2~8s) [::2] 的方块产生渐变色
squares[::2],
shader='''
f_color = frame_texture(v_texcoord);
f_color.gb *= v_texcoord;
'''
)
effect2 = SimpleFrameEffect( # (4~8s) [1::2] 的方块产生故障效果
squares[1::2],
shader='''
vec2 uv = v_texcoord;
float glitchStrength = sin(time) * 0.02;
vec2 offset = vec2(glitchStrength, 0.0);
float r = frame_texture(uv + offset).r;
float g = frame_texture(uv).g;
float b = frame_texture(uv - offset).b;
float a = max(frame_texture(uv + offset).a, max(frame_texture(uv).a, frame_texture(uv - offset).a));
float lineNoise = step(0.5, fract(uv.y * 10.0 + time));
r *= lineNoise;
b *= lineNoise;
f_color = vec4(r, g, b, a);
''',
uniforms=['float time']
)
self.schedule(2, effect1.show)
self.play(
Rotate(squares, TAU, duration=8),
DataUpdater(
effect2,
lambda data, p: data.apply_uniforms(time=p.elapsed),
at=4,
duration=4
)
)
ThreeDShapesExample ¶
from janim.imports import *
class _ThreeDShapesExampleSub(Timeline):
def __init__(self, shape_type: str, background_color: JAnimColor):
super().__init__()
self.shape_type = shape_type
self.background_color = background_color
def construct(self):
if self.shape_type == 'smooth':
axes = ThreeDAxes((-8, 8), (-8, 8), (-8, 8)).apply_depth_test()
self.prepare(FadeIn(axes, at=1))
background = FrameRect(fill_alpha=1, fill_color=self.background_color, stroke_alpha=0, depth=100)
background.fix_in_frame()
self.prepare(FadeIn(background, at=1.5, duration=3))
for shape in [Torus(2, 1), Cylinder(2, 4), Cone(2, 4)]:
item = shape.into(self.shape_type).show()
self.play(self.RotatingCamera(), duration=4)
item.hide()
def RotatingCamera(self):
return AnimGroup(
DataUpdater(
self.camera,
lambda data, p: data.points.rotate(TAU * p.alpha, axis=RIGHT),
rate_func=linear
),
DataUpdater(
self.camera,
lambda data, p: data.points.rotate(TAU * p.alpha, axis=OUT),
rate_func=linear
),
)
class ThreeDShapesExample(Timeline):
def construct(self):
subs = [
_ThreeDShapesExampleSub(type, color).build().to_item()
for type, color in zip(
['checker', 'wire', 'smooth', 'dots'],
['#000022', '#000033', '#000033', '#000022']
)
]
subs[0].show()
effects = [
RectClip(sub, anchor=ORIGIN)
for sub in subs
]
effects[0].show().depth.set(-1)
dirs = [UL, UR, DL, DR]
self.forward()
for sub, effect, dir in zip(subs, effects, dirs):
self.show(sub, effect)
self.prepare(
effect.anim
.points.scale(0.5).to_border(dir, buff=0)
.r.transform.set(scale=0.5)
)
self.forward_to(subs[0].duration)
MaskExample ¶
from janim.imports import *
class MaskExample(Timeline):
def construct(self):
## 第一部分:文本在矩形遮罩中上浮的动画
txt = Text("Mask Example!")
txt.points.scale(2).move_to(DOWN * 0.5)
# 创建一个位于文本上方的矩形遮罩
mask1_shape = Rect(txt.points.box.width, txt.points.box.height)
mask1_shape.points.next_to(txt, UP, buff=0.5)
mask1 = ShapeMask(txt, shape=mask1_shape).show()
# 文本逐个上浮
self.play(
*[
char.anim.points.shift(UP * (0.5 + txt.points.box.height))
for char in txt[0]
],
lag_ratio=0.1,
)
self.forward()
## 第二部分:遮罩与文字的变形
# 将遮罩变为圆形
mask2 = ShapeMask(txt, shape=Circle())
self.play(Transform(mask1, mask2))
self.forward()
# 遮罩的羽化
rect = Rect(3, 3, color=LIGHT_BROWN, fill_alpha=1, depth=10)
self.play(FadeIn(rect))
self.play(
mask2.anim.points.shift(UP * 0.5)
.r.feather.set(0.1),
)
self.play(
Succession(
txt.anim.points.shift(LEFT * 1),
txt.anim.points.shift(RIGHT * 1),
)
)
self.forward()
# 持有物件的变换
txt2 = Text("The mask should be hold")
txt2.points.scale(2)
mask2.apply(txt2)
self.play(TransformMatchingDiff(txt, txt2))
self.forward()
# 遮罩本体的淡出
self.play(
FadeOut(rect),
FadeOut(mask2),
)
self.forward()
self.play(FadeOut(txt2))
self.forward()
## 第三部分:遮罩与布尔运算
dot1 = Dot(LEFT, 1.5, color=YELLOW, fill_alpha=0.25).show()
dot2 = Dot(RIGHT, 1.5, color=YELLOW, fill_alpha=0.25).show()
txt3 = Text('Some Example Text Here', font_size=40).show()
# 使用 boolean_ops 创建遮罩的交并集
mask_union = ShapeMask(
txt3,
shape=boolean_ops.Union(dot1, dot2)
)
mask_intersection = ShapeMask(
txt3,
shape=boolean_ops.Intersection(dot1, dot2)
)
self.forward()
self.play(FadeIn(mask_union))
self.forward(0.5)
self.play(Transform(mask_union, mask_intersection), duration=0.5)
self.forward(0.5)
self.play(Transform(mask_intersection, mask_union), duration=0.5)
self.forward(0.5)
self.play(FadeOut(Group(dot1, dot2, txt3)))
self.forward()
## 第四部分:复杂形状遮罩与特殊效果
# 复杂形状遮罩
txt_fashion = Text('Fashion')
txt_fashion.points.scale(10)
dots = DotCloud(
*[
[i * 0.3 + 0.15, j * 0.3, 0]
for j in range(20, -40, -1)
for i in range(-23, 23)
],
radius=0.1,
color=PURPLE_E,
)
mask3 = ShapeMask(dots, shape=txt_fashion).show()
self.prepare(
dots.anim(rate_func=linear).points.shift(UP * 3),
duration=5.0,
)
self.play(FadeIn(dots))
self.forward(1.5)
self.play(mask3.anim.invert.set(1.0))
self.forward(1.5)
BallsCollisionExample ¶
from janim.imports import *
class Ball(Dot):
speed = CustomData()
def __init__(self, radius: float):
super().__init__(radius=radius, color=BLUE)
self.speed.set(ORIGIN)
class BallsCollisionExample(Timeline):
def construct(self):
# 有关配置
left = -4
right = 4
bottom = -3
top = 3
radius = 0.25
ball_count = 25
# 容器边框
Polygon([left, top, 0], [left, bottom, 0], [right, bottom, 0], [right, top, 0], fill_alpha=0.2).show()
# 内部的球
balls = Ball(radius) * ball_count
# 生成互不重叠的初始位置
positions = []
rng = np.random.default_rng(1234)
for ball in balls:
# 初始位置
while True:
pos = np.array([
rng.uniform(left + radius, right - radius),
rng.uniform(bottom + radius, top - radius),
0,
])
if all(np.linalg.norm(pos - other) >= 2 * radius for other in positions):
break
ball.points.move_to(pos)
positions.append(pos)
# 初始速度
ball.speed.set(
np.array([
rng.uniform(-3, 3),
rng.uniform(-3, 3),
0,
])
)
def updater(group: Group[Ball], p) -> None:
dt = p.dt
# 1. 根据速度移动
for ball in group:
ball.points.shift(ball.speed.get() * dt)
# 2. 与容器边界碰撞
for ball in group:
pos = ball.points.box.center
speed = ball.speed.get().copy()
if pos[0] - radius < left:
ball.points.set_x(left + radius)
speed[0] = abs(speed[0])
elif pos[0] + radius > right:
ball.points.set_x(right - radius)
speed[0] = -abs(speed[0])
if pos[1] - radius < bottom:
ball.points.set_y(bottom + radius)
speed[1] = abs(speed[1])
elif pos[1] + radius > top:
ball.points.set_y(top - radius)
speed[1] = -abs(speed[1])
ball.speed.set(speed)
# 3. 小球之间的完全弹性碰撞
for i in range(len(group)):
for j in range(i + 1, len(group)):
ball1 = group[i]
ball2 = group[j]
p1 = ball1.points.box.center
p2 = ball2.points.box.center
delta = p2 - p1
dist = np.linalg.norm(delta)
min_dist = 2 * radius
if dist >= min_dist:
continue
# 两个球存在重合时,给出碰撞方向
if dist < 1e-8:
normal = np.array([1.0, 0.0, 0.0])
dist = 0.0
else:
normal = delta / dist
v1 = ball1.speed.get()
v2 = ball2.speed.get()
# 相对速度
relative_velocity = v2 - v1
velocity_along_normal = np.dot(relative_velocity, normal)
# 只有相互靠近时才处理碰撞
if velocity_along_normal < 0:
# 相同质量的完全弹性碰撞
impulse = velocity_along_normal * normal
ball1.speed.set(v1 + impulse)
ball2.speed.set(v2 - impulse)
# 消除两个球之间的重叠
overlap = min_dist - dist
if overlap > 0:
correction = normal * (overlap / 2)
ball1.points.shift(-correction)
ball2.points.shift(correction)
self.play(
GroupStepUpdater(balls, updater),
duration=4,
)
ball_follow = balls[6]
self.forward(0.5)
self.play(
self.camera.anim.points.scale(0.5).move_to(ball_follow),
ball_follow.anim.set(color=YELLOW),
)
self.forward(0.5)
self.play(
GroupStepUpdater(balls, updater),
Follow(self.camera, ball_follow, ORIGIN),
duration=6,
)
JAnim - 编写开源动画引擎的一次尝试by jkjkil-jiang
嗯,这个本身就是用 JAnim 制作的
【伪科普/整活向】如何计算 1 + 1by _F_X_P
视频是如何存储在你的设备里的 - 视频编码简述by jkjkil-jiang
【无痛线代】彻底搞懂SVD!矩阵究竟怎么就奇异了?by 漫士沉思录 & Minepig233
【漫士】这是世界上增长最快的数列,不服来战by 漫士沉思录 & LifeCheckpoint
Python 图形编程 | 入门篇 第一部分 绘制一个三角形 | 1-基本介绍by jkjkil-jiang
Python 图形编程 | 入门篇 第一部分 绘制一个矩形 | 2-OpenGL具体介绍by jkjkil-jiang
Python 图形编程 | 入门篇 第一部分 绘制一个矩形 | 5-你好,三角形by jkjkil-jiang
Python 图形编程 | 入门篇 第二部分 完善你的三角形 | 6-索引缓冲对象by jkjkil-jiang
Python 图形编程 | 入门篇 第二部分 完善你的三角形 | 7-着色器by jkjkil-jiang
Python 图形编程 | 入门篇 第二部分 完善你的三角形 | 8-更多顶点属性by jkjkil-jiang
Python 图形编程 | 入门篇 第三部分 纹理 | 9-纹理的基本概念by jkjkil-jiang
基于 JAnim 的舞萌DX播放器by jkjkil-jiang
【伪·Rizline自制谱】绝涮双娇by 破壁人五号
数千行代码,带来拜年纪燃曲《天星问》Rizline自制谱!!1(动画)by 破壁人五号
「不想死于像这样活着」 | 【伪·Rizline自制谱】人造卫星by 破壁人五号
【伪·Rizline自制谱】ギターと孤独と蒼い惑星by 破壁人五号
Python 图形编程 | 入门篇 第一部分 绘制一个矩形 | 3-安装与创建窗口by jkjkil-jiang
除了 00:00 - 01:25 的“类 Slide 演示”之外,作为视频叠加动画出现的区段:
03:18 - 03:31 : 演示了
create_window函数的参数含义03:57 - 04:48 : 演示了
viewport的概念
Python 图形编程 | 入门篇 第一部分 绘制一个矩形 | 4-处理输入与背景颜色by jkjkil-jiang
除了 00:00 - 00:25 的“类 Slide 演示”之外,作为视频叠加动画出现的区段:
00:50 - 01:02 : 演示了
get_key的用法01:10 - 01:17 : 对“渲染循环”部分代码的强调
02:01 - 02:18 : 演示了
RGB颜色的概念
Python 图形编程 | 入门篇 第三部分 纹理 | 10-加载与创建纹理by jkjkil-jiang
作为视频叠加动画出现的区段(由于区段太多,省略了一些无关紧要的部分):
00:58 - 01:12 : 演示了
img的成员02:43 - 02:52 : 跟随光标编辑出现的备注说明
02:52 - 02:57 : 示意了颜色采样
03:28 - 03:36 : 示意了采样的参数
03:54 - 04:14 : 示意了绑定纹理
06:04 - 06:22 : 演示了
mix函数的颜色混合效果07:42 - 08:01 : 说明了纹理上下颠倒的原因
如果你在安装时遇到困难,或是在使用时有任何问题,可以在 Github 的 Discussions 页面 提问,或加入 QQ 群 970174336
更多实用教程
其它教程
参考文档