Remotion Medium
Map Pins (Remotion)
Animated global map with location pins dropping in with ripple effects and dashed connection lines — rendered with Remotion at 1280×720 30fps.
remotion react typescript
Targets: TS React
Preview
Code
import {
AbsoluteFill,
Composition,
interpolate,
spring,
useCurrentFrame,
useVideoConfig,
Easing,
} from "remotion";
const TITLE = "Global Presence";
const SUBTITLE = "Active in 6 locations worldwide";
const PINS = [
{ x: 22, y: 35, city: "New York", users: "2.4K", delay: 20 },
{ x: 48, y: 30, city: "London", users: "1.8K", delay: 30 },
{ x: 55, y: 38, city: "Berlin", users: "950", delay: 40 },
{ x: 72, y: 42, city: "Tokyo", users: "3.1K", delay: 50 },
{ x: 62, y: 58, city: "Mumbai", users: "1.2K", delay: 60 },
{ x: 80, y: 65, city: "Sydney", users: "780", delay: 70 },
];
const ACCENT = "#06b6d4";
const Pin: React.FC<{ pin: (typeof PINS)[number]; frame: number; fps: number }> = ({
pin,
frame,
fps,
}) => {
const f = Math.max(0, frame - pin.delay);
const drop = spring({ frame: f, fps, from: -30, to: 0, config: { damping: 10, stiffness: 200 } });
const opacity = interpolate(f, [0, 5], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const scale = spring({ frame: f, fps, from: 0, to: 1, config: { damping: 10, stiffness: 180 } });
// Ripple
const rippleScale = interpolate(f, [5, 30], [0.5, 2.5], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const rippleOpacity = interpolate(f, [5, 30], [0.4, 0], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
// Label
const labelOpacity = interpolate(f, [10, 20], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return (
<div
style={{
position: "absolute",
left: `${pin.x}%`,
top: `${pin.y}%`,
transform: `translate(-50%, -50%) translateY(${drop}px)`,
opacity,
}}
>
{/* Ripple */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
width: 40,
height: 40,
borderRadius: "50%",
border: `2px solid ${ACCENT}`,
transform: `translate(-50%, -50%) scale(${rippleScale})`,
opacity: rippleOpacity,
}}
/>
{/* Pin dot */}
<div
style={{
width: 14,
height: 14,
borderRadius: "50%",
backgroundColor: ACCENT,
boxShadow: `0 0 16px ${ACCENT}80`,
transform: `scale(${scale})`,
}}
/>
{/* Label */}
<div
style={{
position: "absolute",
top: -40,
left: "50%",
transform: "translateX(-50%)",
whiteSpace: "nowrap",
opacity: labelOpacity,
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<span
style={{
fontFamily: "system-ui, sans-serif",
fontWeight: 600,
fontSize: 14,
color: "#ffffff",
}}
>
{pin.city}
</span>
<span
style={{
fontFamily: "system-ui, sans-serif",
fontWeight: 400,
fontSize: 12,
color: ACCENT,
}}
>
{pin.users} users
</span>
</div>
</div>
);
};
export const MapPins: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const titleOpacity = interpolate(frame, [0, 15], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const titleY = spring({ frame, fps, from: -15, to: 0, config: { damping: 14, stiffness: 80 } });
// Grid lines fade in
const gridOpacity = interpolate(frame, [5, 25], [0, 0.06], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return (
<AbsoluteFill style={{ backgroundColor: "#0a0a0f" }}>
{/* Grid background */}
<div
style={{
position: "absolute",
inset: 0,
backgroundImage: `linear-gradient(rgba(255,255,255,${gridOpacity}) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,${gridOpacity}) 1px, transparent 1px)`,
backgroundSize: "60px 60px",
}}
/>
{/* Map area outline */}
<div
style={{
position: "absolute",
top: 100,
left: 60,
right: 60,
bottom: 60,
border: "1px solid rgba(255,255,255,0.04)",
borderRadius: 16,
overflow: "hidden",
}}
>
{/* Connection lines between pins */}
<svg style={{ position: "absolute", inset: 0, width: "100%", height: "100%" }}>
{PINS.slice(0, -1).map((pin, i) => {
const next = PINS[i + 1];
const lineOpacity = interpolate(
Math.max(0, frame - (next.delay + 10)),
[0, 15],
[0, 0.12],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
);
return (
<line
key={i}
x1={`${pin.x}%`}
y1={`${pin.y}%`}
x2={`${next.x}%`}
y2={`${next.y}%`}
stroke={ACCENT}
strokeWidth={1}
strokeDasharray="4 4"
opacity={lineOpacity}
/>
);
})}
</svg>
{PINS.map((pin, i) => (
<Pin key={i} pin={pin} frame={frame} fps={fps} />
))}
</div>
{/* Title */}
<div
style={{
position: "absolute",
top: 30,
left: 0,
right: 0,
textAlign: "center",
opacity: titleOpacity,
transform: `translateY(${titleY}px)`,
}}
>
<span
style={{
fontFamily: "system-ui, sans-serif",
fontWeight: 800,
fontSize: 36,
color: "#ffffff",
letterSpacing: -1,
}}
>
{TITLE}
</span>
<div style={{ marginTop: 6 }}>
<span
style={{
fontFamily: "system-ui, sans-serif",
fontWeight: 400,
fontSize: 16,
color: "rgba(255,255,255,0.4)",
}}
>
{SUBTITLE}
</span>
</div>
</div>
</AbsoluteFill>
);
};
export const RemotionRoot: React.FC = () => {
return (
<Composition
id="MapPins"
component={MapPins}
durationInFrames={210}
fps={30}
width={1280}
height={720}
/>
);
};World map visualization with pins dropping into place with ripple animations, dashed SVG connection lines between locations, and user count labels. Customize PINS array with x/y positions, city names, and user counts.