StealThis .dev
Remotion Medium

News Ticker (Remotion)

A broadcast-style news ticker with rotating headlines, scrolling bottom bar, LIVE indicator, and network branding — rendered with Remotion at 1280×720 30fps.

Open Remotion
remotion react typescript
Targets: TS React

Preview

Code

import {
  AbsoluteFill,
  Composition,
  interpolate,
  spring,
  useCurrentFrame,
  useVideoConfig,
} from "remotion";

const HEADLINE = "BREAKING";
const TICKER_ITEMS = [
  "Markets surge 2.4% on tech earnings beat",
  "New AI framework breaks performance records",
  "Global climate summit reaches historic agreement",
  "SpaceX announces Mars mission timeline update",
  "Quantum computing milestone: 1000 qubits achieved",
];
const ACCENT = "#dc2626";
const TICKER_SPEED = 2;

const TickerBar: React.FC<{ frame: number }> = ({ frame }) => {
  const offset = frame * TICKER_SPEED;
  const text = TICKER_ITEMS.join("  \u25CF  ");
  const repeated = `${text}  \u25CF  ${text}  \u25CF  ${text}`;

  return (
    <div
      style={{
        position: "absolute",
        bottom: 0,
        left: 0,
        right: 0,
        height: 48,
        backgroundColor: "rgba(0,0,0,0.9)",
        borderTop: `3px solid ${ACCENT}`,
        overflow: "hidden",
        display: "flex",
        alignItems: "center",
      }}
    >
      <div
        style={{
          position: "absolute",
          left: 0,
          top: 0,
          bottom: 0,
          width: 140,
          backgroundColor: ACCENT,
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          zIndex: 2,
        }}
      >
        <span
          style={{
            fontFamily: "system-ui, sans-serif",
            fontWeight: 800,
            fontSize: 16,
            color: "#ffffff",
            letterSpacing: 2,
          }}
        >
          {HEADLINE}
        </span>
      </div>
      <div style={{ marginLeft: 150, whiteSpace: "nowrap", transform: `translateX(-${offset}px)` }}>
        <span
          style={{
            fontFamily: "system-ui, sans-serif",
            fontWeight: 500,
            fontSize: 16,
            color: "rgba(255,255,255,0.9)",
            letterSpacing: 0.5,
          }}
        >
          {repeated}
        </span>
      </div>
    </div>
  );
};

const NewsCard: React.FC<{ index: number; frame: number; fps: number }> = ({
  index,
  frame,
  fps,
}) => {
  const delay = 20 + index * 25;
  const f = Math.max(0, frame - delay);
  const opacity = interpolate(f, [0, 15], [0, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });
  const x = spring({ frame: f, fps, from: 60, to: 0, config: { damping: 16, stiffness: 100 } });

  const exitStart = delay + 60;
  const exitOpacity = interpolate(frame, [exitStart, exitStart + 15], [1, 0], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });

  const item = TICKER_ITEMS[index];

  return (
    <div
      style={{
        position: "absolute",
        top: 120,
        left: 100,
        right: 100,
        opacity: opacity * exitOpacity,
        transform: `translateX(${x}px)`,
      }}
    >
      <div
        style={{
          fontFamily: "system-ui, sans-serif",
          fontWeight: 500,
          fontSize: 14,
          color: ACCENT,
          textTransform: "uppercase",
          letterSpacing: 2,
          marginBottom: 12,
        }}
      >
        Top Story
      </div>
      <div
        style={{
          fontFamily: "system-ui, sans-serif",
          fontWeight: 800,
          fontSize: 42,
          color: "#ffffff",
          lineHeight: 1.2,
          letterSpacing: -1,
        }}
      >
        {item}
      </div>
      <div
        style={{ marginTop: 20, width: 60, height: 3, backgroundColor: ACCENT, borderRadius: 2 }}
      />
    </div>
  );
};

const TimeStamp: React.FC<{ frame: number }> = ({ frame }) => {
  const opacity = interpolate(frame, [5, 20], [0, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });
  const seconds = Math.floor(frame / 30);
  const mins = Math.floor(seconds / 60);
  const secs = seconds % 60;

  return (
    <div style={{ position: "absolute", top: 30, right: 40, opacity }}>
      <div
        style={{
          fontFamily: "ui-monospace, monospace",
          fontSize: 14,
          color: "rgba(255,255,255,0.4)",
        }}
      >
        LIVE
      </div>
      <div style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 4 }}>
        <div
          style={{
            width: 8,
            height: 8,
            borderRadius: "50%",
            backgroundColor: ACCENT,
            opacity: frame % 30 < 20 ? 1 : 0.3,
          }}
        />
        <span
          style={{
            fontFamily: "ui-monospace, monospace",
            fontSize: 16,
            color: "rgba(255,255,255,0.6)",
          }}
        >
          {String(mins).padStart(2, "0")}:{String(secs).padStart(2, "0")}
        </span>
      </div>
    </div>
  );
};

export const NewsTicker: React.FC = () => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();

  // Determine which card is active
  const activeIndex = Math.min(Math.floor(frame / 85), TICKER_ITEMS.length - 1);

  const logoOpacity = interpolate(frame, [0, 15], [0, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });
  const logoScale = spring({
    frame,
    fps,
    from: 0.8,
    to: 1,
    config: { damping: 14, stiffness: 100 },
  });

  return (
    <AbsoluteFill style={{ backgroundColor: "#0f0f18" }}>
      {/* Background texture */}
      <div
        style={{
          position: "absolute",
          inset: 0,
          background:
            "radial-gradient(ellipse at 30% 20%, rgba(220,38,38,0.06) 0%, transparent 50%)",
        }}
      />

      {/* Network logo */}
      <div
        style={{
          position: "absolute",
          top: 30,
          left: 40,
          opacity: logoOpacity,
          transform: `scale(${logoScale})`,
        }}
      >
        <div
          style={{
            fontFamily: "system-ui, sans-serif",
            fontWeight: 900,
            fontSize: 28,
            color: "#ffffff",
            letterSpacing: -1,
          }}
        >
          NEWS<span style={{ color: ACCENT }}>24</span>
        </div>
      </div>

      <TimeStamp frame={frame} />

      {/* Rotating headlines */}
      {TICKER_ITEMS.map((_, i) => (
        <NewsCard key={i} index={i} frame={frame} fps={fps} />
      ))}

      <TickerBar frame={frame} />
    </AbsoluteFill>
  );
};

export const RemotionRoot: React.FC = () => (
  <Composition
    id="NewsTicker"
    component={NewsTicker}
    durationInFrames={450}
    fps={30}
    width={1280}
    height={720}
  />
);

Broadcast news layout with a network logo, LIVE timestamp indicator, rotating headline cards with stagger, and a continuously scrolling bottom ticker bar. Customize HEADLINE, TICKER_ITEMS array, and TICKER_SPEED.