Animated meme generator (deterministic, scriptable)
Build a scriptable meme video generator in HTML — top-text bottom-text reveal, punchline punch-scale, shaky-cam emphasis — and render reproducible MP4s from a CSV.
A meme video generator is a templated HTML page that takes top text, bottom text, an image, and produces a short MP4 with timed reveals — built right, it's deterministic, scriptable, and survives a million-row CSV. The format is older than the web; the technical trick is making the output reproducible and the production pipeline boring.
This post is the working template: classic top-text/bottom-text in Impact, a punch-scale on the punchline word, and a shaky-cam emphasis frame at the end. Everything driven by hf-seek.
What a meme generator actually has to do
Three jobs:
- Compose an image (or short clip) with top text and bottom text in Impact.
- Reveal the text on a beat — drop the top, rise the bottom — so the viewer reads it in the intended order.
- Emphasize the punchline so the joke lands.
That's it. Every meme generator that ships extra features (filters, stickers, watermarks, "AI captions") is fighting the format. The format is conservative on purpose; the joke is the variable.
The reveal timing
The top text drops in from above (0.2s to 0.9s) with a cubic ease-out. The bottom text rises from below (1.4s to 2.2s), also cubic ease-out. The gap between them — 0.5 seconds — is the read time for the setup. Shorten the gap and the viewer doesn't finish reading the top before the bottom arrives; lengthen it and the rhythm sags.
addEventListener('hf-seek', (e) => {
const t = e.detail.time;
const topP = Math.min(1, Math.max(0, (t - 0.2) / 0.7));
const bottomP = Math.min(1, Math.max(0, (t - 1.4) / 0.8));
const ease = p => 1 - Math.pow(1 - p, 3);
top.style.transform = `translateY(${(1 - ease(topP)) * -120}%)`;
bottom.style.transform = `translateY(${(1 - ease(bottomP)) * 120}%)`;
});The punch-scale
The punchline word is the last word of the bottom text — wrapped in its own <span> and scaled with a back-ease (slight overshoot) at t ≈ 2.8s. The scale goes from 1 to 1.65 and settles back to 1.2. The overshoot is what makes it feel like the joke landed rather than was displayed.
A common mistake is to scale the whole bottom line. Don't — the eye has already finished reading the line, and re-emphasizing all of it is redundant. Scale only the punchline word; the rest stays put.
The shaky-cam emphasis
After the punchline lands, the whole frame shakes for 1.2 seconds. The shake is sinusoidal noise, not random — random looks like a bug, sinusoidal looks like a camera operator who couldn't hold the lens.
const sh = Math.min(1, Math.max(0, (t - 4.4) / 1.2));
if (sh > 0 && sh < 1) {
const amp = 12 * Math.sin(sh * Math.PI); // envelope
const dx = Math.sin(t * 47) * amp;
const dy = Math.cos(t * 53) * amp;
const rot = Math.sin(t * 61) * amp * 0.2;
stage.style.transform = `translate(${dx}px, ${dy}px) rotate(${rot}deg)`;
}The sin(t * π) envelope ramps the shake up and back down — it starts at 0 amplitude, peaks at amplitude 12 in the middle, and lands at 0. That envelope is the reason it doesn't jolt on or off, which would read as a broken animation.
The frequencies (47, 53, 61) are intentionally co-prime so the shake never repeats visibly in the window. If you used Math.random() here, the render would be non-deterministic. With sin(t * k) for fixed k, the same t always produces the same offset — the MP4 is identical across renders. This is the core argument of deterministic rendering.
The CSV-driven pipeline
The whole point of a generator is that the template is fixed and the rows multiply. The CSV for a meme campaign is three columns wide:
slug,top,bottom_pre,punch,image
det-render,"WHEN THE RENDER IS","ACTUALLY","DETERMINISTIC","emoji-grin.png"
old-after-effects,"WHEN AE CRASHES","AGAIN AT","4AM","emoji-cry.png"
git-for-video,"WHEN YOU DIFF","TWO MP4s AND IT","WORKS","emoji-shock.png"Three rows in, three MP4s out. The renderer is invoked once per row; the data-driven recipe covers the substitution and batching.
Render to MP4
hyperframes render input.html --out clip.mp4 --duration 6 --fps 30The 6-second clip loops cleanly because the final frame returns to the post-shake resting state. For platforms that want a 1:1 aspect (Instagram feed) or 9:16 (Stories/Reels), re-render at the new dimensions:
hyperframes render meme.html --out meme-1x1.mp4 \
--width 1080 --height 1080 --duration 6 --fps 30What about the image layer?
The template assumes a single foreground subject (here, an emoji-style face). Replacing it with a photo means slotting an <img> behind the text and clipping it to the stage. Don't animate the photo — pan-and-zoom on a meme is the visual equivalent of a laugh track. The text moves; the image holds.
If you need a reaction-photo grid or a multi-panel meme, that's a different template — same primitives, more rows. See the pull-quote card pattern for the related editorial format.
Common mistakes
Animating Impact's letter spacing. Don't. Impact is a static typeface; track-out animations look like CSS demos. The reveal moves the line; the letters stay where they are.
Long top text. If the top text wraps to three lines, the meme isn't working. Cut. If the joke needs that much setup, it isn't a meme; it's a tweet.
Punch-scaling the wrong word. The punchline is almost always the last word, occasionally the verb. Scaling an article ("THE") or a preposition reads as a bug.
For the broader case for HTML-as-source see HTML is the next video codec; for the batch path see render 10k variants overnight.
FAQ
Can I use a different font than Impact?
You can — Anton, Oswald, Bebas Neue, League Gothic all read as condensed sans-serifs. But the cultural shorthand is Impact specifically. If your meme is for an audience that grew up after 2015, Anton works. For everyone else, Impact.
How do I handle very long punchline words?
Auto-scale the punchline by character count. If the word is over 12 characters, drop the scale magnitude from 1.65 to 1.3 and reduce the font size 10%. Beyond that, the line breaks and the joke dies.
Is this safe to use for branded content?
The format is, the joke might not be. Memes are a genre with active community norms — a brand using the format too literally reads as a parent trying to use slang. Use the template; write the joke as if the brand isn't the punchline.
What's the right duration?
6 seconds for a single-beat meme (setup, payoff, shake). 9 seconds if you want the punchline to linger for a screenshot. Above 12 seconds you're making a sketch, not a meme.
Can the shake be random instead of sinusoidal?
It can, but the render won't be deterministic. Use Math.random() only if you seed it from t (e.g., a Mulberry32 PRNG keyed on the frame index) — otherwise two renders of the same source produce different MP4s, which defeats the pipeline.
Close
A meme generator is the simplest possible templated-video case: short, square, two lines of text. The discipline is keeping it simple — resisting the urge to add transitions, filters, and watermarks — and letting the template carry as many jokes as you can write. The render path is the same as everything else: HTML in, MP4 out, every frame determined by hf-seek.
Start at the quickstart or open the playground and adapt the template.
Cite this postBibTeX · APA · Markdown
@misc{okafor2026animated,
author = {Marcus Okafor},
title = {Animated meme generator (deterministic, scriptable)},
year = {2026},
url = {https://hyperframes.video/blog/animated-meme-template-generator},
note = {HyperFrames blog}
}Marcus Okafor. (2026, May 21). Animated meme generator (deterministic, scriptable). HyperFrames. https://hyperframes.video/blog/animated-meme-template-generator
[Animated meme generator (deterministic, scriptable)](https://hyperframes.video/blog/animated-meme-template-generator) — Marcus Okafor, 2026
Marcus leads design and motion at HyperFrames. Before that he shipped editorial motion for newsrooms and product launches. He thinks every easing curve has a personality.
Animated app onboarding screens to MP4
Build an animated app onboarding video in HTML — three-screen carousel with sliding screens, fading headlines, scaling illustrations, advancing dots — and render to MP4.
Animated newsletter header MP4s (that fall back to a still)
Build an animated newsletter header in HTML, render it to a deterministic MP4, and ship a still PNG as the fallback for clients that strip video.
Animated pull-quote cards for editorial video
Build an animated pull quote in HTML — oversized opening glyph, word-by-word typewriter, attribution rise — and render deterministic MP4s for editorial video pipelines.
Building with HyperFrames? Come hang out.
We're on GitHub, in Discord, and the playground is one click away. Bring weird ideas — we collect them.