Plotly's Frame-Based Animation API: When a Slider Beats a Dashboard
Plotly's frame-based animation API adds a playable slider to any chart with a few lines of Python. Here's a worked example, plus the file-size trade-offs that decide when it's the right tool.
26 Sept 2026, 14:35 UTC

You have a dataset that changes over time — sensor readings, simulation steps, monthly metrics — and a static chart forces you to either pick one snapshot or cram everything into a spaghetti of overlapping lines. Plotly's animation API solves this with a surprisingly small amount of code: you define a list of frames, one per time step, and Plotly renders a slider and play button that walk through them in the browser. No server, no JavaScript by hand, no video encoding.
The thesis of this post: frame-based animation is the right tool when your reader needs to explore a time dimension interactively, and the wrong tool when you need a video, a tiny file, or hundreds of frames. Here's how it works and where it breaks.
How the animation model actually works
A Plotly animation is not a video. It is a single figure whose frames attribute holds a list of partial figure states. Each frame contains the trace data (and optionally layout) for one step. The layout carries sliders and updatemenus definitions that tell Plotly how to transition between frames. When the user presses play, the browser swaps trace data frame by frame — everything happens client-side in the rendered HTML.
This has two practical consequences. First, every frame's data is embedded in the output file, so file size grows roughly linearly with frame count. Second, because frames are just data swaps, you get hover tooltips, zoom, and pan for free in every frame — something a rendered video can never offer.
A worked example in Python
The following builds a playable animation of a point orbiting a circle. Run it in a Jupyter notebook or any Python environment with Plotly 4.x or later installed (pip install plotly). No special permissions are needed; fig.show() renders inline in a notebook or opens a browser tab from a script.
import numpy as np
import plotly.graph_objects as go
t = np.linspace(0, 2 * np.pi, 40)
fig = go.Figure(
data=[go.Scatter(x=[np.cos(t[0])], y=[np.sin(t[0])],
mode="markers", marker=dict(size=14))],
layout=go.Layout(
xaxis=dict(range=[-1.5, 1.5]),
yaxis=dict(range=[-1.5, 1.5], scaleanchor="x"),
updatemenus=[dict(
type="buttons",
buttons=[dict(label="Play", method="animate",
args=[None, {"frame": {"duration": 80,
"redraw": True},
"fromcurrent": True}]),
dict(label="Pause", method="animate",
args=[[None], {"frame": {"duration": 0},
"mode": "immediate"}])])],
sliders=[dict(
steps=[dict(method="animate",
args=[[f"frame-{i}"],
{"mode": "immediate",
"frame": {"duration": 0}}],
label=str(i)) for i in range(len(t))])]),
frames=[go.Frame(data=[go.Scatter(x=[np.cos(ti)], y=[np.sin(ti)])],
name=f"frame-{i}") for i in range(len(t))])
fig.show()Three details matter here. Each frame's name must match the slider step's target (frame-{i}) or the slider won't find it. scaleanchor="x" keeps the aspect ratio square so the orbit doesn't distort. And the Pause button passes [None] as the frame target, which is the documented way to halt playback. To verify it works, check that the slider appears below the plot and that dragging it moves the marker; then press Play and confirm smooth motion.
Saving and sharing the result
fig.write_html("orbit.html") produces a self-contained file you can email or host anywhere — the animation runs entirely in the viewer's browser. Be aware of what this does not do: it does not produce a video or an animated GIF. Static export via fig.write_image() (which requires the separate kaleido package) captures only the initial frame. If your deliverable is a video for a slide deck, you need a different tool — matplotlib's FuncAnimation with an ffmpeg writer, or a screen recording of the Plotly output.
The trade-off: file size and frame count
Because every frame duplicates its trace data into the HTML, costs scale fast. A 40-frame animation of a single point is trivial; a 500-frame animation of a 10,000-point scatter can produce a file in the tens of megabytes and cause visible lag on weaker machines. Practical mitigations:
- Downsample before animating — viewers rarely perceive more than 20–30 frames per second, and far fewer distinct steps.
- Only include the traces that actually change in each frame; static traces stay in the base
data. - Set
redraw: Falsein the frame options when axis ranges don't change, which skips full layout recalculation.
A quick check: after write_html, look at the file size and open it in Chrome with a throttled CPU (DevTools performance settings). If dragging the slider stutters, cut frame count before shipping.
Closing: pick the animation for exploration, not presentation
Reach for Plotly frames when your audience will interact — scrubbing a slider through simulation steps beats flipping through 40 static PNGs. Reach for something else when you need a video, a print artifact, or smooth playback of thousands of frames. Start with the minimal example above, confirm the slider works in your target browser, then scale the frame count up gradually while watching file size. That one measurement will tell you whether the approach fits your dataset before you've invested in polishing it.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.