Remotion LabRemotion Lab
返回模板庫

留言互動片尾

深藍背景上大問號彈跳進場,主問題文字從下方滑入,副文字淡入說明,留言輸入框縮放現身並顯示閃爍游標,最後三則假留言依序滑入,引導觀眾在下方留言互動。

片尾留言互動CTA
提示詞(可直接修改內容)
import React from "react";
import {
  AbsoluteFill,
  interpolate,
  spring,
  useCurrentFrame,
  useVideoConfig,
} from "remotion";

// 假留言資料
const fakeComments = [
  { avatar: "#3b82f6", text: "這個教學真的很有幫助,謝謝分享!" },
  { avatar: "#10b981", text: "已訂閱!期待更多影片 🎉" },
  { avatar: "#f59e0b", text: "請問有更進階的教學嗎?" },
];

// 時間軸
const QUESTION-IN    = 0;   // 大問號進場
const MAIN-TEXT-IN   = 35;  // 主問題文字
const SUB-TEXT-IN    = 65;  // 副文字
const INPUT-IN       = 90;  // 留言框
const COMMENTS-START = 110; // 假留言依序滑入
const COMMENT-STAGGER = 13;

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

  // ── 大問號:bounce 進場 ──
  const questionSpring = spring({
    frame: frame - QUESTION-IN,
    fps,
    config: { damping: 8, stiffness: 120 },
  });
  const questionY = interpolate(questionSpring, [0, 1], [100, 0], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });
  const questionOpacity = interpolate(questionSpring, [0, 0.3], [0, 1], {
    extrapolateRight: "clamp",
  });

  // ── 主問題文字:fade in + translateY ──
  const mainTextOpacity = interpolate(frame, [MAIN-TEXT-IN, MAIN-TEXT-IN + 30], [0, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });
  const mainTextY = interpolate(frame, [MAIN-TEXT-IN, MAIN-TEXT-IN + 30], [20, 0], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });

  // ── 副文字 fade in ──
  const subTextOpacity = interpolate(frame, [SUB-TEXT-IN, SUB-TEXT-IN + 25], [0, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });

  // ── 留言框:scale 0.95→1 + opacity ──
  const inputSpring = spring({
    frame: frame - INPUT-IN,
    fps,
    config: { damping: 18, stiffness: 160 },
  });
  const inputScale = interpolate(inputSpring, [0, 1], [0.95, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });
  const inputOpacity = interpolate(inputSpring, [0, 0.5], [0, 1], {
    extrapolateRight: "clamp",
  });

  // ── 游標閃爍(每 15 frames 切換)──
  const cursorVisible = Math.floor(frame / 15) % 2 === 0;

  return (
    <AbsoluteFill
      style={{
        background: "#0f172a",
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
        justifyContent: "center",
        fontFamily: "sans-serif",
        overflow: "hidden",
      }}
    >
      {/* 大問號光暈 */}
      <div
        style={{
          position: "absolute",
          top: "50%",
          left: "50%",
          marginLeft: -200,
          marginTop: -350,
          width: 400,
          height: 400,
          borderRadius: "50%",
          background: "radial-gradient(circle, rgba(59,130,246,0.15) 0%, transparent 70%)",
          pointerEvents: "none",
        }}
      />

      {/* 大問號 */}
      <div
        style={{
          transform: `translateY(${questionY}px)`,
          opacity: questionOpacity,
          fontSize: 200,
          fontWeight: 900,
          color: "#3b82f6",
          lineHeight: 1,
          marginBottom: -20,
          userSelect: "none",
        }}
      >
        ?
      </div>

      {/* 主問題文字 */}
      <div
        style={{
          opacity: mainTextOpacity,
          transform: `translateY(${mainTextY}px)`,
          fontSize: 56,
          fontWeight: 700,
          color: "#ffffff",
          textAlign: "center",
          lineHeight: 1.3,
          marginBottom: 16,
          maxWidth: 800,
        }}
      >
        你有什麼想法嗎?
      </div>

      {/* 副文字 */}
      <div
        style={{
          opacity: subTextOpacity,
          fontSize: 28,
          color: "#94a3b8",
          fontWeight: 400,
          textAlign: "center",
          marginBottom: 40,
        }}
      >
        在下方留言告訴我們吧!
      </div>

      {/* 留言輸入框 */}
      <div
        style={{
          transform: `scale(${inputScale})`,
          opacity: inputOpacity,
          width: 700,
          height: 80,
          borderRadius: 12,
          background: "#1e293b",
          border: "1px solid #334155",
          display: "flex",
          alignItems: "center",
          padding: "0 24px",
          marginBottom: 40,
          flexShrink: 0,
        }}
      >
        <span
          style={{
            fontSize: 22,
            color: "#475569",
            fontWeight: 400,
            letterSpacing: "0.5px",
          }}
        >
          新增留言…
        </span>
        <span
          style={{
            fontSize: 24,
            color: "#3b82f6",
            fontWeight: 300,
            marginLeft: 4,
            opacity: cursorVisible ? 1 : 0,
          }}
        >
          |
        </span>
      </div>

      {/* 假留言列表 */}
      <div
        style={{
          display: "flex",
          flexDirection: "column",
          gap: 16,
          width: 700,
        }}
      >
        {fakeComments.map((comment, i) => {
          const commentSpring = spring({
            frame: frame - (COMMENTS-START + i * COMMENT-STAGGER),
            fps,
            config: { damping: 18, stiffness: 140 },
          });
          const commentY = interpolate(commentSpring, [0, 1], [30, 0], {
            extrapolateLeft: "clamp",
            extrapolateRight: "clamp",
          });
          const commentOpacity = interpolate(commentSpring, [0, 0.4], [0, 1], {
            extrapolateRight: "clamp",
          });

          return (
            <div
              key={i}
              style={{
                display: "flex",
                flexDirection: "row",
                alignItems: "center",
                gap: 16,
                transform: `translateY(${commentY}px)`,
                opacity: commentOpacity,
              }}
            >
              {/* 頭像 */}
              <div
                style={{
                  width: 44,
                  height: 44,
                  borderRadius: "50%",
                  background: comment.avatar,
                  flexShrink: 0,
                }}
              />
              {/* 文字條 */}
              <div
                style={{
                  flex: 1,
                  height: 18,
                  borderRadius: 9,
                  background: "#1e293b",
                  display: "flex",
                  alignItems: "center",
                  paddingLeft: 16,
                  paddingRight: 16,
                }}
              >
                <span
                  style={{
                    fontSize: 16,
                    color: "#94a3b8",
                    fontWeight: 400,
                    overflow: "hidden",
                    whiteSpace: "nowrap",
                  }}
                >
                  {comment.text}
                </span>
              </div>
            </div>
          );
        })}
      </div>
    </AbsoluteFill>
  );
};

登入後查看完整程式碼