Facebook 貼文卡片
仿 Facebook 風格的貼文卡片動畫,白色卡片搭配藍色頂部裝飾,包含頭像、貼文文字、漸層圖片佔位、按讚反應數與互動按鈕,帶有逐步淡入與滑動效果。
社群簡約橫式
提示詞(可直接修改內容)
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion";
import React from "react";
const POST-TEXT-LINES = [
"剛剛發現了一個超厲害的動畫工具!🔥",
"用 Remotion 可以用 React 寫視頻,太神奇了!",
"大家有試過嗎?",
"",
"#Remotion #React #前端",
];
function formatCount(n: number): string {
if (n >= 1000) return (n / 1000).toFixed(1).replace(/\.0$/, "") + "K";
return String(n);
}
export const FacebookPost: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const cardProgress = spring({
frame,
fps,
config: { damping: 22, stiffness: 140 },
durationInFrames: 20,
});
const cardScale = interpolate(cardProgress, [0, 1], [0.9, 1]);
const cardOpacity = interpolate(cardProgress, [0, 0.3], [0, 1], {
extrapolateRight: "clamp",
});
const headerProgress = spring({
frame: frame - 10,
fps,
config: { damping: 20, stiffness: 180 },
durationInFrames: 15,
});
const headerY = interpolate(headerProgress, [0, 1], [-28, 0]);
const headerOpacity = interpolate(headerProgress, [0, 0.4], [0, 1], {
extrapolateRight: "clamp",
});
const lineOpacities = POST-TEXT-LINES.map((_, i) => {
const start = 20 + i * 5;
const p = spring({
frame: frame - start,
fps,
config: { damping: 25, stiffness: 160 },
durationInFrames: 10,
});
return interpolate(p, [0, 0.5], [0, 1], { extrapolateRight: "clamp" });
});
const lineTranslations = POST-TEXT-LINES.map((_, i) => {
const start = 20 + i * 5;
const p = spring({
frame: frame - start,
fps,
config: { damping: 25, stiffness: 160 },
durationInFrames: 10,
});
return interpolate(p, [0, 1], [8, 0]);
});
const imgOpacity = interpolate(frame, [28, 45], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const reactionSlide = spring({
frame: frame - 50,
fps,
config: { damping: 22, stiffness: 160 },
durationInFrames: 30,
});
const reactionY = interpolate(reactionSlide, [0, 1], [20, 0]);
const reactionOpacity = interpolate(reactionSlide, [0, 0.4], [0, 1], {
extrapolateRight: "clamp",
});
const reactionRaw = interpolate(frame, [50, 80], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const currentReactions = Math.round(reactionRaw * 1200);
const actionOpacity = interpolate(frame, [65, 78], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return (
<AbsoluteFill
style={{
background: "#0f0f0f",
justifyContent: "center",
alignItems: "center",
}}
>
<div
style={{
transform: `scale(${cardScale})`,
opacity: cardOpacity,
background: "#ffffff",
borderRadius: 12,
width: 860,
boxSizing: "border-box",
fontFamily: "sans-serif",
overflow: "hidden",
boxShadow: "0 8px 40px rgba(0,0,0,0.5)",
borderTop: "4px solid #1877f2",
}}
>
<div style={{ padding: "16px 16px 0 16px" }}>
<div
style={{
transform: `translateY(${headerY}px)`,
opacity: headerOpacity,
display: "flex",
alignItems: "center",
gap: 10,
marginBottom: 12,
}}
>
<div
style={{
width: 48,
height: 48,
borderRadius: "50%",
background: "linear-gradient(135deg, #f59e0b, #ef4444, #8b5cf6)",
flexShrink: 0,
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 19,
fontWeight: 700,
color: "#ffffff",
}}
>
陳
</div>
<div style={{ flex: 1 }}>
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<span
style={{
fontSize: 16,
fontWeight: 700,
color: "#1877f2",
lineHeight: 1.3,
}}
>
陳小華
</span>
<div
style={{
fontSize: 12,
fontWeight: 600,
color: "#1877f2",
border: "1px solid #1877f2",
borderRadius: 4,
padding: "1px 8px",
}}
>
追蹤中 ▾
</div>
</div>
<div
style={{
fontSize: 12,
color: "#999999",
marginTop: 2,
display: "flex",
alignItems: "center",
gap: 4,
}}
>
2小時 · 🌐
</div>
</div>
<div style={{ display: "flex", gap: 12, alignItems: "center" }}>
<span style={{ fontSize: 20, color: "#666666", letterSpacing: 1 }}>•••</span>
<span style={{ fontSize: 18, color: "#666666", fontWeight: 300 }}>✕</span>
</div>
</div>
<div style={{ marginBottom: 12 }}>
{POST-TEXT-LINES.map((line, i) => (
<div
key={i}
style={{
fontSize: 15,
color: "#050505",
lineHeight: 1.6,
opacity: lineOpacities[i],
transform: `translateY(${lineTranslations[i]}px)`,
minHeight: line === "" ? 6 : "auto",
}}
>
{line}
</div>
))}
</div>
</div>
<div
style={{
width: "100%",
height: 390,
background: "linear-gradient(135deg, #3b82f6 0%, #6d28d9 60%, #8b5cf6 100%)",
opacity: imgOpacity,
display: "flex",
alignItems: "center",
justifyContent: "center",
flexDirection: "column",
gap: 14,
}}
>
<div
style={{
width: 72,
height: 72,
borderRadius: 16,
background: "rgba(255,255,255,0.2)",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 36,
}}
>
🎬
</div>
<div style={{ fontSize: 20, fontWeight: 700, color: "#ffffff", fontFamily: "sans-serif" }}>
Remotion 動畫展示
</div>
<div style={{ fontSize: 14, color: "rgba(255,255,255,0.75)", fontFamily: "sans-serif" }}>
remotion.dev
</div>
</div>
<div
style={{
transform: `translateY(${reactionY}px)`,
opacity: reactionOpacity,
padding: "10px 16px 6px 16px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
}}
>
<div
style={{
display: "flex",
alignItems: "center",
gap: 2,
fontSize: 14,
color: "#666666",
}}
>
<span style={{ fontSize: 17 }}>👍</span>
<span style={{ fontSize: 17 }}>❤️</span>
<span style={{ fontSize: 17 }}>😮</span>
<span style={{ marginLeft: 4 }}>{formatCount(currentReactions)}</span>
</div>
<div style={{ fontSize: 13, color: "#666666" }}>234 則留言 · 56 次分享</div>
</div>
<div style={{ height: 1, background: "#e4e6eb", margin: "0 16px" }} />
<div
style={{
opacity: actionOpacity,
display: "flex",
padding: "4px 8px 10px 8px",
}}
>
{[
{ icon: "👍", label: "按讚" },
{ icon: "💬", label: "留言" },
{ icon: "↗", label: "分享" },
].map(({ icon, label }) => (
<div
key={label}
style={{
flex: 1,
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: 6,
padding: "8px 0",
fontSize: 15,
fontWeight: 600,
color: "#65676b",
cursor: "pointer",
borderRadius: 6,
}}
>
<span style={{ fontSize: 20 }}>{icon}</span>
<span>{label}</span>
</div>
))}
</div>
</div>
</AbsoluteFill>
);
};登入後查看完整程式碼