Meme Frame Template (Remotion)
A vertical meme-style video template with top/bottom impact-font text, animated reaction emojis, and a shaking frame effect — 1080×1920, 30 fps.
Preview
Code
import React from "react";
import {
AbsoluteFill,
Composition,
interpolate,
spring,
useCurrentFrame,
useVideoConfig,
Easing,
} from "remotion";
// ─── CONFIG ───────────────────────────────────────────────────────────────────
const CONFIG = {
topText: "WHEN THE DEPLOY",
bottomText: "ACTUALLY WORKS ON FRIDAY",
placeholderBg: "#2a2a2a",
placeholderWidth: 960,
placeholderHeight: 960,
backgroundColor: "#ffffff",
textColor: "#ffffff",
textOutlineColor: "#000000",
fontSize: 88,
fontFamily: "'Impact', 'Arial Black', 'Arial Bold', sans-serif",
shakeAmplitude: 3,
shakeFrequency: 0.8,
emoji1: "😂",
emoji1Frame: 90,
emoji2: "💀",
emoji2Frame: 120,
reactionBarFrame: 200,
reactionBar: "😂😂😂",
emojiSize: 120,
reactionBarSize: 96,
};
// ─── Helpers ──────────────────────────────────────────────────────────────────
const impactShadow = (size: number): string => {
const s = Math.max(2, Math.round(size * 0.045));
const offsets: string[] = [];
for (let dx = -s; dx <= s; dx++) {
for (let dy = -s; dy <= s; dy++) {
if (dx === 0 && dy === 0) continue;
offsets.push(`${dx}px ${dy}px 0 ${CONFIG.textOutlineColor}`);
}
}
return offsets.join(", ");
};
// ─── Background ───────────────────────────────────────────────────────────────
const Background: React.FC = () => (
<AbsoluteFill style={{ backgroundColor: CONFIG.backgroundColor }} />
);
// ─── Image Placeholder ────────────────────────────────────────────────────────
const ImagePlaceholder: React.FC = () => {
const { width, height } = useVideoConfig();
const cx = (width - CONFIG.placeholderWidth) / 2;
const cy = (height - CONFIG.placeholderHeight) / 2;
return (
<div
style={{
position: "absolute",
left: cx,
top: cy,
width: CONFIG.placeholderWidth,
height: CONFIG.placeholderHeight,
backgroundColor: CONFIG.placeholderBg,
borderRadius: 12,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
gap: 24,
}}
>
{/* Camera icon built from divs */}
<div
style={{
width: 120,
height: 90,
border: "6px solid rgba(255,255,255,0.35)",
borderRadius: 18,
position: "relative",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
{/* Lens */}
<div
style={{
width: 52,
height: 52,
border: "5px solid rgba(255,255,255,0.35)",
borderRadius: "50%",
}}
/>
{/* Shutter bump */}
<div
style={{
position: "absolute",
top: -20,
left: 16,
width: 34,
height: 16,
backgroundColor: "rgba(255,255,255,0.35)",
borderRadius: "6px 6px 0 0",
}}
/>
</div>
<span
style={{
fontFamily: "system-ui, -apple-system, sans-serif",
fontSize: 36,
color: "rgba(255,255,255,0.3)",
fontWeight: 600,
letterSpacing: 3,
textTransform: "uppercase",
}}
>
Your Meme Here
</span>
</div>
);
};
// ─── Top Caption ──────────────────────────────────────────────────────────────
const TopCaption: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const slideY = spring({
frame,
fps,
from: -200,
to: 0,
config: { damping: 14, stiffness: 120 },
});
return (
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
display: "flex",
justifyContent: "center",
alignItems: "center",
paddingTop: 80,
paddingLeft: 40,
paddingRight: 40,
transform: `translateY(${slideY}px)`,
}}
>
<span
style={{
fontFamily: CONFIG.fontFamily,
fontWeight: 900,
fontSize: CONFIG.fontSize,
color: CONFIG.textColor,
textShadow: impactShadow(CONFIG.fontSize),
textTransform: "uppercase",
textAlign: "center",
lineHeight: 1.1,
letterSpacing: 2,
}}
>
{CONFIG.topText}
</span>
</div>
);
};
// ─── Bottom Caption ───────────────────────────────────────────────────────────
const BottomCaption: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const slideY = spring({
frame,
fps,
from: 200,
to: 0,
config: { damping: 14, stiffness: 120 },
});
return (
<div
style={{
position: "absolute",
bottom: 0,
left: 0,
right: 0,
display: "flex",
justifyContent: "center",
alignItems: "center",
paddingBottom: 80,
paddingLeft: 40,
paddingRight: 40,
transform: `translateY(${slideY}px)`,
}}
>
<span
style={{
fontFamily: CONFIG.fontFamily,
fontWeight: 900,
fontSize: CONFIG.fontSize,
color: CONFIG.textColor,
textShadow: impactShadow(CONFIG.fontSize),
textTransform: "uppercase",
textAlign: "center",
lineHeight: 1.1,
letterSpacing: 2,
}}
>
{CONFIG.bottomText}
</span>
</div>
);
};
// ─── Dropped Emoji ────────────────────────────────────────────────────────────
interface DroppedEmojiProps {
emoji: string;
triggerFrame: number;
offsetX: number;
offsetY: number;
}
const DroppedEmoji: React.FC<DroppedEmojiProps> = ({
emoji,
triggerFrame,
offsetX,
offsetY,
}) => {
const frame = useCurrentFrame();
const { fps, width, height } = useVideoConfig();
const f = Math.max(0, frame - triggerFrame);
const dropY = spring({
frame: f,
fps,
from: -200,
to: height / 2 + offsetY,
config: { damping: 8, stiffness: 140, mass: 0.8 },
});
const opacity = interpolate(f, [0, 6], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const cx = width / 2 + offsetX - CONFIG.emojiSize / 2;
return (
<div
style={{
position: "absolute",
left: cx,
top: dropY - CONFIG.emojiSize / 2,
fontSize: CONFIG.emojiSize,
lineHeight: 1,
opacity,
userSelect: "none",
}}
>
{emoji}
</div>
);
};
// ─── Reaction Bar ─────────────────────────────────────────────────────────────
const ReactionBar: React.FC = () => {
const frame = useCurrentFrame();
const { fps, width, height } = useVideoConfig();
const DELAY = CONFIG.reactionBarFrame;
const f = Math.max(0, frame - DELAY);
const slideX = spring({
frame: f,
fps,
from: width + 100,
to: 0,
config: { damping: 16, stiffness: 100 },
});
const opacity = interpolate(f, [0, 8], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return (
<div
style={{
position: "absolute",
right: 60,
top: height / 2 - CONFIG.reactionBarSize / 2 + 200,
transform: `translateX(${slideX}px)`,
opacity,
display: "flex",
flexDirection: "column",
alignItems: "flex-end",
gap: 8,
}}
>
<div
style={{
fontSize: CONFIG.reactionBarSize,
lineHeight: 1,
userSelect: "none",
}}
>
{CONFIG.reactionBar}
</div>
</div>
);
};
// ─── Shaking Container ────────────────────────────────────────────────────────
const MemeFrame: React.FC = () => {
const frame = useCurrentFrame();
const shakeX = Math.sin(frame * CONFIG.shakeFrequency) * CONFIG.shakeAmplitude;
const shakeY =
Math.sin(frame * CONFIG.shakeFrequency * 1.3 + 1.2) *
CONFIG.shakeAmplitude *
0.6;
return (
<AbsoluteFill
style={{
transform: `translate(${shakeX}px, ${shakeY}px)`,
}}
>
<Background />
<ImagePlaceholder />
<TopCaption />
<BottomCaption />
<DroppedEmoji
emoji={CONFIG.emoji1}
triggerFrame={CONFIG.emoji1Frame}
offsetX={-80}
offsetY={-60}
/>
<DroppedEmoji
emoji={CONFIG.emoji2}
triggerFrame={CONFIG.emoji2Frame}
offsetX={100}
offsetY={80}
/>
<ReactionBar />
</AbsoluteFill>
);
};
// ─── Remotion Root ────────────────────────────────────────────────────────────
export const RemotionRoot: React.FC = () => (
<Composition
id="MemeFrame"
component={MemeFrame}
durationInFrames={270}
fps={30}
width={1080}
height={1920}
/>
);Meme Frame Template
A vertical meme-style video composition built for Shorts, Reels, and TikTok (1080×1920, 9 seconds). It recreates the classic image-macro format entirely in Remotion — white background, a dark grey placeholder for the meme image, and two bold Impact-style text blocks that slide in from opposite screen edges. Reaction emojis drop and bounce into frame at staggered moments using spring(), and the whole container trembles on a subtle sine-wave shake to sell the comedic energy. A row of triple-laugh emojis glides in from the right near the end for maximum engagement bait.
All copy and timing constants live in a top-level CONFIG block so the template can be repurposed in seconds — swap the two text strings, adjust the placeholder colour, and re-render.
Composition specs
| Property | Value |
|---|---|
| Resolution | 1080 × 1920 |
| FPS | 30 |
| Duration | 9 s (270 frames) |
Elements
- Top caption — white text, black multi-offset
textShadowoutline, slides in from above at frame 0 with a spring entrance - Bottom caption — same Impact-style treatment, slides in from below at frame 0
- Image placeholder — dark grey centred rectangle with a camera icon label, representing the meme’s core visual
- 😂 emoji drop — springs from -200 px to vertical centre at frame 90 with a bouncy
spring()config - 💀 emoji drop — same treatment at frame 120, horizontally offset for a staggered look
- Reaction bar — ”😂😂😂” row slides in from the right edge at frame 200
- Container shake —
Math.sin(frame × 0.8) × 3translate on the root container for a continuous subtle tremble