Remotion LabRemotion Lab
返回模板庫

Reddit 貼文卡片

仿 Reddit 深色模式的貼文卡片動畫,左欄橙色投票箭頭與計數,右欄顯示 Subreddit 資訊、獎章、標題與鏈結預覽,帶有滑入與逐步顯現動畫。

社群簡約橫式
提示詞(可直接修改內容)
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion";
import React from "react";

const TOTAL-VOTES = 4200;
const AWARDS = ["🏆", "🥇", "🌟"];

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

  const cardProgress = spring({
    frame,
    fps,
    config: { damping: 22, stiffness: 140 },
    durationInFrames: 20,
  });
  const cardY = interpolate(cardProgress, [0, 1], [60, 0]);
  const cardOpacity = interpolate(cardProgress, [0, 0.3], [0, 1], {
    extrapolateRight: "clamp",
  });

  const headerOpacity = interpolate(frame, [10, 25], [0, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });

  const arrowProgress = spring({
    frame: frame - 15,
    fps,
    config: { damping: 18, stiffness: 200 },
    durationInFrames: 15,
  });
  const arrowScale = interpolate(arrowProgress, [0, 1], [0, 1]);
  const arrowOpacity = interpolate(arrowProgress, [0, 0.4], [0, 1], {
    extrapolateRight: "clamp",
  });

  const voteRaw = interpolate(frame, [20, 60], [0, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });
  const currentVotes = Math.round(voteRaw * TOTAL-VOTES);

  function formatVotes(n: number): string {
    if (n >= 1000) return (n / 1000).toFixed(1).replace(/\.0$/, "") + "k";
    return String(n);
  }

  const titleProgress = spring({
    frame: frame - 25,
    fps,
    config: { damping: 22, stiffness: 160 },
    durationInFrames: 20,
  });
  const titleX = interpolate(titleProgress, [0, 1], [-40, 0]);
  const titleOpacity = interpolate(titleProgress, [0, 0.4], [0, 1], {
    extrapolateRight: "clamp",
  });

  const awardScales = AWARDS.map((_, i) => {
    const start = 35 + i * 7;
    const p = spring({
      frame: frame - start,
      fps,
      config: { damping: 14, stiffness: 280 },
      durationInFrames: 12,
    });
    return interpolate(p, [0, 1], [0, 1]);
  });
  const awardOpacities = AWARDS.map((_, i) => {
    const start = 35 + i * 7;
    const p = spring({
      frame: frame - start,
      fps,
      config: { damping: 14, stiffness: 280 },
      durationInFrames: 12,
    });
    return interpolate(p, [0, 0.4], [0, 1], { extrapolateRight: "clamp" });
  });

  const actionOpacity = interpolate(frame, [55, 72], [0, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });

  return (
    <AbsoluteFill
      style={{
        background: "#0f0f0f",
        justifyContent: "center",
        alignItems: "center",
      }}
    >
      <div
        style={{
          transform: `translateY(${cardY}px)`,
          opacity: cardOpacity,
          background: "#1a1a1b",
          borderRadius: 8,
          border: "1px solid #343536",
          width: 900,
          boxSizing: "border-box",
          fontFamily: "sans-serif",
          boxShadow: "0 8px 40px rgba(0,0,0,0.6)",
          display: "flex",
          flexDirection: "row",
        }}
      >
        <div
          style={{
            width: 50,
            background: "#161617",
            borderRadius: "8px 0 0 8px",
            display: "flex",
            flexDirection: "column",
            alignItems: "center",
            padding: "16px 0",
            gap: 6,
          }}
        >
          <div
            style={{
              transform: `scale(${arrowScale})`,
              opacity: arrowOpacity,
              fontSize: 20,
              color: "#ff4500",
              cursor: "pointer",
              lineHeight: 1,
            }}
          >

          </div>
          <div
            style={{
              fontSize: 13,
              fontWeight: 700,
              color: "#ff4500",
              lineHeight: 1.2,
              textAlign: "center",
              minWidth: 32,
            }}
          >
            {formatVotes(currentVotes)}
          </div>
          <div
            style={{
              transform: `scale(${arrowScale})`,
              opacity: arrowOpacity,
              fontSize: 20,
              color: "#818384",
              cursor: "pointer",
              lineHeight: 1,
            }}
          >

          </div>
        </div>

        <div style={{ flex: 1, padding: "14px 16px 14px 16px" }}>
          <div
            style={{
              opacity: headerOpacity,
              display: "flex",
              alignItems: "center",
              gap: 8,
              marginBottom: 10,
              flexWrap: "wrap",
            }}
          >
            <span style={{ fontSize: 14, fontWeight: 700, color: "#d7dadc" }}>
              r/webdev
            </span>
            <span style={{ fontSize: 13, color: "#818384" }}>
              · Posted by{" "}
              <span style={{ color: "#d7dadc" }}>u/remotion-fan</span>
              {" "}· 5 小時前
            </span>
            <div
              style={{
                marginLeft: "auto",
                fontSize: 12,
                fontWeight: 700,
                color: "#ff4500",
                border: "1px solid #ff4500",
                borderRadius: 20,
                padding: "3px 14px",
              }}
            >
              🔔 加入
            </div>
          </div>

          <div style={{ display: "flex", gap: 8, marginBottom: 10, alignItems: "center" }}>
            {AWARDS.map((award, i) => (
              <div
                key={i}
                style={{
                  transform: `scale(${awardScales[i]})`,
                  opacity: awardOpacities[i],
                  fontSize: 18,
                  background: "rgba(255,255,255,0.06)",
                  borderRadius: 4,
                  padding: "2px 8px",
                  display: "flex",
                  alignItems: "center",
                  gap: 4,
                }}
              >
                <span>{award}</span>
                <span style={{ fontSize: 11, color: "#818384" }}>×1</span>
              </div>
            ))}
          </div>

          <div
            style={{
              transform: `translateX(${titleX}px)`,
              opacity: titleOpacity,
              fontSize: 22,
              fontWeight: 700,
              color: "#d7dadc",
              lineHeight: 1.4,
              marginBottom: 14,
            }}
          >
            用 React 寫影片?Remotion 讓這件事成真了
          </div>

          <div
            style={{
              opacity: titleOpacity,
              border: "1px solid #343536",
              borderRadius: 4,
              padding: "10px 14px",
              marginBottom: 14,
              display: "flex",
              alignItems: "center",
              gap: 10,
            }}
          >
            <div
              style={{
                width: 36,
                height: 36,
                borderRadius: 6,
                background: "linear-gradient(135deg, #ff4500, #ff6534)",
                display: "flex",
                alignItems: "center",
                justifyContent: "center",
                fontSize: 18,
                flexShrink: 0,
              }}
            >
              🎬
            </div>
            <div>
              <div style={{ fontSize: 14, color: "#d7dadc", fontWeight: 500 }}>
                Remotion — Make videos in React
              </div>
              <div style={{ fontSize: 12, color: "#818384", marginTop: 2 }}>
                remotion.dev
              </div>
            </div>
          </div>

          <div style={{ opacity: actionOpacity, display: "flex", gap: 4, alignItems: "center" }}>
            {[
              { icon: "💬", label: "342 則留言" },
              { icon: "↗", label: "分享" },
              { icon: "🔖", label: "儲存" },
              { icon: "•••", label: "更多" },
            ].map(({ icon, label }) => (
              <div
                key={label}
                style={{
                  display: "flex",
                  alignItems: "center",
                  gap: 5,
                  padding: "6px 10px",
                  borderRadius: 2,
                  fontSize: 13,
                  fontWeight: 700,
                  color: "#818384",
                  cursor: "pointer",
                }}
              >
                <span style={{ fontSize: 15 }}>{icon}</span>
                <span>{label}</span>
              </div>
            ))}
          </div>
        </div>
      </div>
    </AbsoluteFill>
  );
};

登入後查看完整程式碼