Chapter 3 · manim-slides

The payoff chapter: your animations become presentations. The decks below are real interactive presentations — click one open, then click / press inside it to advance, exactly like your audience would.

Topics 1 Scene → Slide2 Render, present, export 3 Slide craft4 Canvas & transitions 5 Exports6 Real workflow ✅ Self-exam
🧠 Before you start (30 seconds)Try to answer these from intuition — being wrong is fine, guessing first is proven to make the real answers stick better:
Guess: what's the minimum change to turn an animation into a presentation?
Inherit from Slide instead of Scene and mark pauses with next_slide(). That's genuinely all — Topic 1.
A talk venue has no WiFi. What could go wrong with an HTML deck?
If it loads its player from a CDN, you get a blank screen. The --offline flag bundles everything — Topic 2.

1From Scene to Slide — one import, one call

Change Scene to Slide (from manim_slides) and call self.next_slide() wherever the presentation should pause and wait for you. Everything you know from Chapter 2 still works — a Slide is a Scene.

EASYYour first deck (this one is real — click through it!)
from manim import *
from manim_slides import Slide

class FirstDeck(Slide):
    def construct(self):
        title = Text("My first deck", font_size=60)
        self.play(Write(title))
        self.next_slide()          # ⏸ waits for your click

        self.play(title.animate.scale(0.5).to_edge(UP))
        body = Text("Click / press → to advance",
                    font_size=36, color=GREY)
        self.play(FadeIn(body))
        self.next_slide()          # ⏸ waits again

        self.play(FadeOut(title), FadeOut(body))

Render + convert: manim-slides render deck.py FirstDeck then manim-slides convert --to html --offline FirstDeck out.html — or press ▶ with the VS Code extension and it's all automatic.

EASYBullet points that appear on YOUR click
class TwoPoints(Slide):
    def construct(self):
        head = Text("Why animate slides?", font_size=48).to_edge(UP)
        p1 = Text("1. Motion guides attention",
                  font_size=34, color=TEAL).shift(UP * 0.5)
        p2 = Text("2. Steps appear when YOU decide",
                  font_size=34, color=YELLOW).next_to(p1, DOWN, buff=0.6)
        self.play(Write(head))
        self.next_slide()
        self.play(FadeIn(p1, shift=RIGHT))
        self.next_slide()
        self.play(FadeIn(p2, shift=RIGHT))
        self.next_slide()
        self.play(FadeOut(head), FadeOut(p1), FadeOut(p2))

The pattern for every talk: reveal → pause → reveal → pause.

LEVEL UPLooping slides — ambient motion while you talk
class LoopingLogo(Slide):
    def construct(self):
        logo = RegularPolygon(6, color=TEAL,
                              fill_opacity=0.4).scale(1.5)
        label = Text("loop=True keeps this spinning",
                     font_size=30).to_edge(DOWN)
        self.play(Create(logo), FadeIn(label))

        self.next_slide(loop=True)   # ⟳ loops until you advance
        self.play(Rotate(logo, TAU, run_time=3,
                         rate_func=linear))

        self.next_slide()
        self.play(FadeOut(logo), FadeOut(label))

loop=True replays that segment forever — perfect for title slides while the audience settles.

LEVEL UPA real mini-lecture: LaTeX + graph + updater, as slides
class MathLecture(Slide):
    def construct(self):
        title = Text("The derivative", font_size=54)
        self.play(Write(title))
        self.next_slide()

        self.play(title.animate.scale(0.55).to_edge(UP))
        definition = MathTex(
            r"f'(x) = \lim_{h \to 0} \frac{f(x+h) - f(x)}{h}",
            font_size=54)
        self.play(Write(definition))
        self.next_slide()

        self.play(definition.animate.scale(0.7).shift(UP * 1.6))
        axes = Axes(x_range=[-2.5, 2.5], y_range=[-0.5, 4],
                    x_length=8, y_length=3.2).shift(DOWN * 1.2)
        curve = axes.plot(lambda x: 0.55 * x**2 + 0.3,
                          color=YELLOW)
        self.play(Create(axes), Create(curve))
        self.next_slide()

        x = ValueTracker(-1.8)
        tangent = always_redraw(lambda: TangentLine(
            curve, alpha=(x.get_value() + 2.5) / 5,
            length=3.5, color=RED))
        self.add(tangent)
        self.play(x.animate.set_value(1.8), run_time=3,
                  rate_func=linear)
        self.next_slide()
        self.play(*[FadeOut(m) for m in
                    [title, definition, axes, curve, tangent]])

All three chapters in one deck: Chapter-1 LaTeX, Chapter-2 graphs & updaters, Chapter-3 pauses.

🎯 Your turn to write — your first Slide:
Convert a scene to slides: import Slide from manim_slides, subclass it as MyTalk, play one animation, then call next_slide().

2Rendering, presenting, exporting

Three commands cover the whole lifecycle — and if you use the VS Code extension, the first two happen automatically every time you save.

# 1. RENDER — runs Manim + records where the pauses are
manim-slides render lecture.py MathLecture

# 2. PRESENT — three ways to show it:
manim-slides present MathLecture              # native window (PySide6) — best in-person
manim-slides convert --to html --offline MathLecture out.html   # browser deck — best to share
# ...or the VS Code extension's live preview          — best while authoring

# 3. EXPORT — hand it to PowerPoint people:
manim-slides convert --to pptx MathLecture lecture.pptx
TargetBest forOffline?
Native GUI (present)Presenting in person — instant start, presenter hotkeysalways
HTML (convert --to html --offline)Sharing a link/file; works on any machine with a browserwith --offline
PowerPoint (--to pptx)Venues that demand .pptx; embeds auto-playing videosalways
Presenter hotkeys (native GUI) /Space next · previous · F fullscreen · R replay current · Q quit. In the HTML version: same arrows, plus Esc for slide overview.
🎯 Your turn to write — render and export:
Write the two commands: render MyTalk from talk.py with manim-slides, then convert it to offline HTML named talk.html.

3Slide craft — patterns that make decks feel professional

Four battle-tested patterns. None require new API — just discipline in how you use next_slide() and FadeOut.

Pattern 1 — Clean between sections

self.next_slide()
self.play(*[FadeOut(m) for m in self.mobjects])   # wipe everything
# ...start the next section on a clean canvas

Pattern 2 — Title that shrinks into a header

title = Text("Big opening title", font_size=64)
self.play(Write(title))
self.next_slide()
self.play(title.animate.scale(0.5).to_edge(UP))   # becomes the header

Pattern 3 — The loop-while-talking title slide

self.next_slide(loop=True)     # ambient motion, zero pressure
self.play(Rotate(star, TAU, run_time=4, rate_func=linear))
self.next_slide()              # advances when YOU are ready

Pattern 4 — Highlight, then un-highlight

self.play(formula[2].animate.set_color(YELLOW))   # spotlight one term
self.next_slide()
self.play(formula[2].animate.set_color(WHITE))    # release attention
Rule of thumb One idea per slide segment; if you're saying "and also" twice about the same screen, split it with another next_slide(). Pauses are free — confusion isn't.
🎯 Your turn to write — the loop-while-talking pattern:
Write a slide segment that loops: call next_slide with loop=True, play a Rotate animation, then call next_slide() again to close the loop.

4Canvas, wipes, zooms & speaker notes — the pro deck

Four upgrades in one deck (open it — it's real): a canvas header that survives slide wipes, wipe() and zoom() transitions, and speaker notes only you see in presenter view.

class ProDeck(Slide):
    def construct(self):
        header = Text("Advanced deck patterns",
                      font_size=32).to_edge(UP)
        self.add_to_canvas(header=header)  # survives wipes
        self.play(FadeIn(header))
        self.next_slide(
            notes="Canvas keeps the header everywhere.")

        p1 = Text("wipe() slides content sideways",
                  font_size=30, color=TEAL)
        self.play(FadeIn(p1))
        self.next_slide(notes="Demonstrate wipe.")

        p2 = Text("like a real slide change",
                  font_size=30, color=YELLOW)
        self.wipe(self.mobjects_without_canvas, p2)
        self.next_slide(notes="Demonstrate zoom.")

        p3 = Text("zoom() scales the next idea in",
                  font_size=30, color=GREEN)
        self.zoom(self.mobjects_without_canvas, p3)
        self.next_slide(notes="Wrap up.")
        self.play(*[FadeOut(m) for m in self.mobjects])

add_to_canvas pins mobjects across transitions; mobjects_without_canvas is everything else. notes= appears in presenter view (press S in HTML decks).

Your section header must stay visible while everything else wipes away. The intended tool?
🎯 Your turn to write — a header that survives the wipe:
Add a title to the canvas so it stays during transitions: self.add_to_canvas(title=…), then wipe to the next content excluding the canvas.

5Every export target that matters

One rendered deck, four audiences: a browser tab for you, a single HTML file for email, a PowerPoint for the conference laptop, a PDF for the handout.

# 1. live presenting (opens a window, arrow keys advance)
manim-slides present MyTalk

# 2. one self-contained HTML file — email it, open anywhere, no install
manim-slides convert --to html --offline --one-file MyTalk talk.html

# 3. PowerPoint — each slide segment becomes an auto-playing video
manim-slides convert --to pptx MyTalk talk.pptx

# 4. PDF handout — one frame per slide
manim-slides convert --to pdf MyTalk talk.pdf
Which HTML flavor? --offline bundles reveal.js locally (folder + assets dir); adding --one-file inlines everything into a single .html — biggest file, zero dependencies, survives email attachments. That single-file trick is exactly how the decks embedded in this site were made portable.
The conference PC has PowerPoint, no Python, no internet. Which export do you bring?
🎯 Your turn to write — the conference-safe exports:
Write the two conversion commands for a no-internet venue: one to a single self-contained HTML file (offline + one file), one to PowerPoint.

6The real-world workflow, start to finish

How the pieces fit on an actual working day — the exact loop the Manim Slides Preview VS Code extension automates for you (▶ renders + converts; Ctrl+S keeps the .pptx fresh).

# the loop you will actually live in:
# 1. edit talk.py          (VS Code)
# 2. render draft          manim render -ql talk.py MyTalk
# 3. preview instantly     manim-slides present MyTalk
# 4. repeat 1-3 until happy
# 5. final quality         manim render -qh talk.py MyTalk
# 6. ship both formats     manim-slides convert --to pptx MyTalk talk.pptx
#                          manim-slides convert --to html --offline --one-file MyTalk talk.html
The three mistakes everyone makes once
1️⃣ Renaming the scene class and wondering where the old slides went — slide data is stored per class name; re-render after renaming.
2️⃣ Forgetting to re-render before convert — convert uses the last rendered videos, not your latest code.
3️⃣ Paths with spaces: always quote — "d:\My Talks\talk.py" — or the CLI sees two arguments. (Your project folder d:\Alamin Maruf\... qualifies!)
You edited talk.py, then ran convert — but the pptx shows the OLD animation. Why?
🎯 Your turn to write — the full loop from memory:
The complete pipeline as three commands: render talk.py MyTalk, convert to offline HTML, present with the native GUI.

Self-examination

What does self.next_slide() actually do?
Your talk venue has no internet. Which export is safest?
What happens between self.next_slide(loop=True) and the following next_slide()?
Convert this Chapter-2 scene into a 3-pause deck: Write a title → pause → draw a sine curve on axes → pause → fade everything out. Use the sine example from Chapter 2 as your starting point.
show solution
from manim import *
from manim_slides import Slide

class SineDeck(Slide):
    def construct(self):
        title = Text("The sine wave", font_size=56)
        self.play(Write(title))
        self.next_slide()

        self.play(title.animate.scale(0.5).to_edge(UP))
        axes = Axes(x_range=[-4, 4], y_range=[-2, 2],
                    x_length=10, y_length=5)
        curve = axes.plot(lambda x: np.sin(x), color=YELLOW)
        self.play(Create(axes))
        self.play(Create(curve), run_time=2)
        self.next_slide()

        self.play(*[FadeOut(m) for m in self.mobjects])
Make a deck whose first slide loops a pulsing circle (scale up and back, there_and_back) until you advance. (Hint: next_slide(loop=True).)
show solution
class PulseTitle(Slide):
    def construct(self):
        c = Circle(radius=1.5, color=TEAL, fill_opacity=0.4)
        title = Text("Ready?", font_size=48).next_to(c, DOWN)
        self.play(Create(c), Write(title))
        self.next_slide(loop=True)
        self.play(c.animate.scale(1.25), rate_func=there_and_back,
                  run_time=1.6)
        self.next_slide()
        self.play(FadeOut(c), FadeOut(title))
Final project: build a 5-slide deck teaching the quadratic formula — title (looping), the formula (Chapter 1 LaTeX), a parabola plot with its roots marked (Chapter 2), highlight the discriminant in yellow, clean fade-out. Then export it to BOTH offline HTML and pptx.
show solution
class QuadraticTalk(Slide):
    def construct(self):
        # 1 — looping title
        title = Text("The Quadratic Formula", font_size=54)
        self.play(Write(title))
        self.next_slide(loop=True)
        self.play(title.animate.set_color(TEAL),
                  rate_func=there_and_back, run_time=2)
        self.next_slide()
        self.play(title.animate.scale(0.5).to_edge(UP))

        # 2 — the formula
        f = MathTex(r"x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}",
                    font_size=64)
        self.play(Write(f))
        self.next_slide()

        # 3 — parabola with roots
        self.play(f.animate.scale(0.6).shift(UP * 1.8))
        ax = Axes(x_range=[-4, 2], y_range=[-3, 5],
                  x_length=8, y_length=3.4).shift(DOWN * 1.2)
        curve = ax.plot(lambda x: x**2 + 2*x - 2, color=YELLOW)
        roots = VGroup(*[Dot(ax.c2p(r, 0), color=RED)
            for r in (-1 - 3**0.5, -1 + 3**0.5)])
        self.play(Create(ax), Create(curve))
        self.play(FadeIn(roots, scale=3))
        self.next_slide()

        # 4 — highlight the discriminant b²-4ac
        self.play(Indicate(f, color=YELLOW))
        self.next_slide()

        # 5 — clean exit
        self.play(*[FadeOut(m) for m in self.mobjects])

# terminal:
#   manim-slides render talk.py QuadraticTalk
#   manim-slides convert --to html --offline QuadraticTalk talk.html
#   manim-slides convert --to pptx QuadraticTalk talk.pptx
← Previous2 · Manim Finish 🎉Back to Home