Sub-Timeline Mechanism¶
JAnim introduces a sub-timeline mechanism, allowing sub-timelines to be nested within the main timeline, thereby achieving separation of complex logic and better reusability.
from janim.imports import *
class DrawingStarInNumberPlane(Timeline):
def construct(self):
plane = NumberPlane(faded_line_ratio=1)
star = Star(outer_radius=2)
dot = Dot(star.points.get_start(), color=YELLOW)
state = self.camera.copy()
self.play(Create(plane, lag_ratio=0.05))
self.play(
self.camera.anim.points.move_to(dot).scale(0.3),
FadeIn(dot, scale=0.8),
lag_ratio=0.3
)
self.play(
MoveAlongPath(dot, star),
Create(star, auto_close_path=False),
Follow(self.camera, dot, ORIGIN),
duration=3
)
self.play(
self.camera.anim.become(state),
star.anim.set(color=YELLOW, fill_alpha=0.5),
duration=2
)
class Texts(Timeline):
def construct(self):
txt = Text(
'I just wrote some text for demonstration.\n'
'It has no real meaning—it\'s only meant to show that \n'
'this text is completely decoupled from another Timeline.',
stroke_color=BLACK,
stroke_alpha=1,
stroke_background=True
)
txt.points.to_border(UL)
self.forward()
self.play(Write(txt, duration=3))
class MainTimeline(Timeline):
def construct(self):
tl1 = DrawingStarInNumberPlane().build().to_item().show()
tl2 = Texts().build().to_item(keep_last_frame=True).show()
self.forward(tl1.duration)
See also:
In the above example, we inserted two sub-timelines DrawingStarInNumberPlane and Texts into the main timeline MainTimeline.
The introduction of sub-timelines allows each sub-timeline to independently control its own animations, and we can more flexibly organize and manage animation logic in the main timeline.
Hint
In the above example, the text does not move with the animation of another timeline. This effect can also be achieved using the fix_in_frame() method, which you can refer to separately.
And sub-timelines act as a whole item in the main timeline, making it more convenient to pass them to FrameEffect and its derived classes to apply complex visual effects:
class MainTimeline(Timeline):
def construct(self):
tl1 = DrawingStarInNumberPlane().build().to_item(keep_last_frame=True).show()
effect = SimpleFrameEffect(
tl1,
shader='''
f_color = frame_texture(v_texcoord);
vec3 col = 0.5 + 0.5 * cos(time * 1.5 + v_texcoord.xyx + vec3(0,2,4));
f_color.rgb *= col;
''',
uniforms=['float time']
)
frameclip = TransformableFrameClip(effect)
tl2 = Texts().build().to_item(keep_last_frame=True).show()
self.play(
DataUpdater(
effect,
lambda data, p: data.apply_uniforms(time=p.elapsed)
),
duration=tl1.duration
)
self.forward()
self.play(
frameclip.anim.clip.set(scale=0.5, x_offset=-0.2, y_offset=-0.1)
)
self.play(
Write(frameclip.create_border_rect())
)
See also:
Moreover, the sub-Timeline mechanism gives Timeline extremely high reusability:
from janim.imports import *
class GraphDemonstration(Timeline):
def __init__(self, f, x_range, typ_code):
super().__init__()
self.f = f
self.x_range = x_range
self.typ_code = typ_code
def construct(self):
axes = Axes(axis_config=dict(include_numbers=True))
graph = axes.get_graph(self.f, self.x_range, color=RED, stroke_radius=0.05)
typ = TypstMath(
self.typ_code,
stroke_color=BLACK,
stroke_alpha=1,
stroke_background=True
).show()
typ.points.scale(1.6).to_border(UP)
def dots_updater(p):
points = graph.current().points
return Group(
Dot(points.get_start()),
Dot(points.get_end()),
fill_color=BLACK,
stroke_alpha=1,
)
self.forward()
self.play(
Create(axes, lag_ratio=0.05)
)
self.play(
Create(graph),
ItemUpdater(None, dots_updater)
)
class MainTimeline(Timeline):
def construct(self):
params_list = [
(lambda x: x**2, (-1, 1.5), 'f(x) = x^2'),
(lambda x: x**3, (-1.5, 1.5), 'f(x) = x^3'),
(lambda x: math.atan(x), (-2, 2), 'f(x) = tan^(-1) x')
]
width = 1 / len(params_list)
clip = 0.5 - width / 2
for i, params in enumerate(params_list):
offset = (-clip + i * width, 0)
tl = GraphDemonstration(*params).build().to_item(keep_last_frame=True).show()
frameclip = TransformableFrameClip(tl, clip=(clip, 0, clip, 0), offset=offset).show()
self.forward(4)
Note
Documentation needs to be improved regarding to_playback_control_item()